feat: sqlite attach (#8396)

* Updated to latest master. Cleans up codestyle, ignores yarn

* re-adds the portable path tests

* mapping bug fix that appears with unrecognised existing tables

* Review comments

* review comments

* test breakage with node 12
This commit is contained in:
matt penrice 2022-02-16 16:29:04 +01:00 committed by GitHub
parent 31f0b5535a
commit 9e844d9ff7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 10662 additions and 70 deletions

4
.gitignore vendored
View File

@ -9,3 +9,7 @@ node_modules/
ormlogs.log
npm-debug.log
/test/github-issues/799/tmp/*
# ignore yarn2 artifacts but allow yarn.lock (forces yarn1 compat which is node_modules)
.yarn/
.yarn*

View File

@ -21,25 +21,25 @@
"types": "./index.d.ts",
"type": "commonjs",
"browser": {
"./browser/driver/aurora-data-api/AuroraDataApiDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/cockroachdb/CockroachDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/postgres/PostgresDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/oracle/OracleDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/sap/SapDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/mysql/MysqlDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/sqlserver/SqlServerDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/mongodb/MongoDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/mongodb/MongoQueryRunner.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/entity-manager/MongoEntityManager.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/repository/MongoRepository.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/sqlite/SqliteDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/better-sqlite3/BetterSqlite3Driver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/util/DirectoryExportedClassesLoader.js": "./browser/platform/BrowserDirectoryExportedClassesLoader.js",
"./browser/logger/FileLogger.js": "./browser/platform/BrowserFileLoggerDummy.js",
"./browser/connection/ConnectionOptionsReader.js": "./browser/platform/BrowserConnectionOptionsReaderDummy.js",
"./browser/connection/options-reader/ConnectionOptionsXmlReader.js": "./browser/platform/BrowserConnectionOptionsReaderDummy.js",
"./browser/connection/options-reader/ConnectionOptionsYmlReader.js": "./browser/platform/BrowserConnectionOptionsReaderDummy.js",
"./browser/driver/aurora-data-api/AuroraDataApiDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/better-sqlite3/BetterSqlite3Driver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/cockroachdb/CockroachDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/mongodb/MongoDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/mongodb/MongoQueryRunner.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/mysql/MysqlDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/oracle/OracleDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/postgres/PostgresDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/sap/SapDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/sqlite/SqliteDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/driver/sqlserver/SqlServerDriver.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/entity-manager/MongoEntityManager.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/logger/FileLogger.js": "./browser/platform/BrowserFileLoggerDummy.js",
"./browser/platform/PlatformTools.js": "./browser/platform/BrowserPlatformTools.js",
"./browser/repository/MongoRepository.js": "./browser/platform/BrowserDisabledDriversDummy.js",
"./browser/util/DirectoryExportedClassesLoader.js": "./browser/platform/BrowserDirectoryExportedClassesLoader.js",
"./index.js": "./browser/index.js"
},
"repository": {
@ -223,9 +223,7 @@
"lint": "eslint -c ./.eslintrc.js src/**/*.ts test/**/*.ts sample/**/*.ts",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 2"
},
"bin": {
"typeorm": "./cli.js"
},
"bin": "./cli.js",
"funding": "https://opencollective.com/typeorm",
"collective": {
"type": "opencollective",

View File

@ -121,6 +121,12 @@ export interface BaseConnectionOptions {
*/
readonly extra?: any;
/**
* Holds reference to the baseDirectory where configuration file are expected
* @internal
*/
baseDirectory?: string;
/**
* Allows to setup cache options.
*/

View File

@ -33,6 +33,7 @@ import {SqljsEntityManager} from "../entity-manager/SqljsEntityManager";
import {RelationLoader} from "../query-builder/RelationLoader";
import {EntitySchema} from "../entity-schema/EntitySchema";
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
import {AbstractSqliteDriver} from "../driver/sqlite-abstract/AbstractSqliteDriver";
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
import {ObjectUtils} from "../util/ObjectUtils";
import {IsolationLevel} from "../driver/types/IsolationLevel";
@ -260,15 +261,23 @@ export class Connection {
async dropDatabase(): Promise<void> {
const queryRunner = this.createQueryRunner();
try {
if (this.driver instanceof SqlServerDriver || this.driver instanceof MysqlDriver || this.driver instanceof AuroraDataApiDriver) {
const databases: string[] = this.driver.database ? [this.driver.database] : [];
if (this.driver instanceof SqlServerDriver || this.driver instanceof MysqlDriver || this.driver instanceof AuroraDataApiDriver || this.driver instanceof AbstractSqliteDriver) {
const databases: string[] = [];
this.entityMetadatas.forEach(metadata => {
if (metadata.database && databases.indexOf(metadata.database) === -1)
databases.push(metadata.database);
});
if (databases.length === 0 && this.driver.database) {
databases.push(this.driver.database);
};
for (const database of databases) {
await queryRunner.clearDatabase(database);
if (databases.length === 0) {
await queryRunner.clearDatabase();
}
else {
for (const database of databases) {
await queryRunner.clearDatabase(database);
}
}
} else {
await queryRunner.clearDatabase();

View File

@ -6,6 +6,7 @@ import {ConnectionOptionsEnvReader} from "./options-reader/ConnectionOptionsEnvR
import {ConnectionOptionsYmlReader} from "./options-reader/ConnectionOptionsYmlReader";
import {ConnectionOptionsXmlReader} from "./options-reader/ConnectionOptionsXmlReader";
import { TypeORMError } from "../error";
import { isAbsolute } from "../util/PathUtils";
import {importOrRequireFile} from "../util/ImportUtils";
/**
@ -151,6 +152,7 @@ export class ConnectionOptionsReader {
connectionOptions = [connectionOptions];
connectionOptions.forEach(options => {
options.baseDirectory = this.baseDirectory;
if (options.entities) {
const entities = (options.entities as any[]).map(entity => {
if (typeof entity === "string" && entity.substr(0, 1) !== "/")
@ -181,7 +183,7 @@ export class ConnectionOptionsReader {
// make database path file in sqlite relative to package.json
if (options.type === "sqlite" || options.type === "better-sqlite3") {
if (typeof options.database === "string" &&
if (typeof options.database === "string" && !isAbsolute(options.database) &&
options.database.substr(0, 1) !== "/" && // unix absolute
options.database.substr(1, 2) !== ":\\" && // windows absolute
options.database !== ":memory:") {

View File

@ -10,6 +10,7 @@ import { AbstractSqliteDriver } from "../sqlite-abstract/AbstractSqliteDriver";
import { BetterSqlite3ConnectionOptions } from "./BetterSqlite3ConnectionOptions";
import { BetterSqlite3QueryRunner } from "./BetterSqlite3QueryRunner";
import {ReplicationMode} from "../types/ReplicationMode";
import { filepathToName, isAbsolute } from "../../util/PathUtils";
/**
* Organizes communication with sqlite DBMS.
@ -79,6 +80,34 @@ export class BetterSqlite3Driver extends AbstractSqliteDriver {
return super.normalizeType(column);
}
async afterConnect(): Promise<void> {
return this.attachDatabases();
}
/**
* For SQLite, the database may be added in the decorator metadata. It will be a filepath to a database file.
*/
buildTableName(tableName: string, _schema?: string, database?: string): string {
if (!database) return tableName;
if (this.getAttachedDatabaseHandleByRelativePath(database)) return `${this.getAttachedDatabaseHandleByRelativePath(database)}.${tableName}`;
if (database === this.options.database) return tableName;
// we use the decorated name as supplied when deriving attach handle (ideally without non-portable absolute path)
const identifierHash = filepathToName(database);
// decorated name will be assumed relative to main database file when non absolute. Paths supplied as absolute won't be portable
const absFilepath = isAbsolute(database) ? database : path.join(this.getMainDatabasePath(), database);
this.attachedDatabases[database] = {
attachFilepathAbsolute: absFilepath,
attachFilepathRelative: database,
attachHandle: identifierHash,
};
return `${identifierHash}.${tableName}`;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
@ -89,7 +118,7 @@ export class BetterSqlite3Driver extends AbstractSqliteDriver {
protected async createDatabaseConnection() {
// not to create database directory if is in memory
if (this.options.database !== ":memory:")
await this.createDatabaseDirectory(this.options.database);
await this.createDatabaseDirectory(path.dirname(this.options.database));
const {
database,
@ -137,8 +166,28 @@ export class BetterSqlite3Driver extends AbstractSqliteDriver {
/**
* Auto creates database directory if it does not exist.
*/
protected async createDatabaseDirectory(fullPath: string): Promise<void> {
await mkdirp(path.dirname(fullPath));
protected async createDatabaseDirectory(dbPath: string): Promise<void> {
await mkdirp(dbPath);
}
/**
* Performs the attaching of the database files. The attachedDatabase should have been populated during calls to #buildTableName
* during EntityMetadata production (see EntityMetadata#buildTablePath)
*
* https://sqlite.org/lang_attach.html
*/
protected async attachDatabases() {
// @todo - possibly check number of databases (but unqueriable at runtime sadly) - https://www.sqlite.org/limits.html#max_attached
for await (const {attachHandle, attachFilepathAbsolute} of Object.values(this.attachedDatabases)) {
await this.createDatabaseDirectory(path.dirname(attachFilepathAbsolute));
await this.connection.query(`ATTACH "${attachFilepathAbsolute}" AS "${attachHandle}"`);
}
}
protected getMainDatabasePath(): string {
const optionsDb = this.options.database;
return path.dirname(isAbsolute(optionsDb) ? optionsDb : path.join(this.options.baseDirectory!, optionsDb));
}
}

View File

@ -128,4 +128,19 @@ export class BetterSqlite3QueryRunner extends AbstractSqliteQueryRunner {
throw new QueryFailedError(query, parameters, err);
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
protected async loadTableRecords(tablePath: string, tableOrIndex: "table" | "index") {
const [database, tableName] = this.splitTablePath(tablePath);
const res = await this.query(`SELECT ${database ? `'${database}'` : null} as database, * FROM ${this.escapePath(`${database ? `${database}.` : ""}sqlite_master`)} WHERE "type" = '${tableOrIndex}' AND "${tableOrIndex === "table" ? "name" : "tbl_name"}" IN ('${tableName}')`);
return res;
}
protected async loadPragmaRecords(tablePath: string, pragma: string) {
const [database, tableName] = this.splitTablePath(tablePath);
const res = await this.query(`PRAGMA ${database ? `"${database}".` : ""}${pragma}("${tableName}")`);
return res;
}
}

View File

@ -20,6 +20,13 @@ import { Table } from "../../schema-builder/table/Table";
import { View } from "../../schema-builder/view/View";
import { TableForeignKey } from "../../schema-builder/table/TableForeignKey";
type DatabasesMap = Record<string, {
attachFilepathAbsolute: string
attachFilepathRelative: string
attachHandle: string
}>;
/**
* Organizes communication with sqlite DBMS.
*/
@ -209,6 +216,15 @@ export abstract class AbstractSqliteDriver implements Driver {
*/
maxAliasLength?: number;
// -------------------------------------------------------------------------
// Protected Properties
// -------------------------------------------------------------------------
/**
* Any attached databases (excepting default 'main')
*/
attachedDatabases: DatabasesMap = {};
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
@ -257,6 +273,18 @@ export abstract class AbstractSqliteDriver implements Driver {
});
}
hasAttachedDatabases(): boolean {
return !!Object.keys(this.attachedDatabases).length;
}
getAttachedDatabaseHandleByRelativePath(path: string): string | undefined {
return this.attachedDatabases?.[path]?.attachHandle
}
getAttachedDatabasePathRelativeByHandle(handle: string): string | undefined {
return Object.values(this.attachedDatabases).find(({attachHandle}) => handle === attachHandle)?.attachFilepathRelative
}
/**
* Creates a schema builder used to build and sync a schema.
*/
@ -429,7 +457,7 @@ export abstract class AbstractSqliteDriver implements Driver {
const driverSchema = undefined
if (target instanceof Table || target instanceof View) {
const parsed = this.parseTableName(target.name);
const parsed = this.parseTableName(target.schema ? `"${target.schema}"."${target.name}"` : target.name);
return {
database: target.database || parsed.database || driverDatabase,
@ -468,8 +496,9 @@ export abstract class AbstractSqliteDriver implements Driver {
tableName: parts[2]
};
} else if (parts.length === 2) {
const database = this.getAttachedDatabasePathRelativeByHandle(parts[0]) ?? driverDatabase
return {
database: driverDatabase,
database: database,
schema: parts[0],
tableName: parts[1]
};

View File

@ -190,7 +190,7 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
*/
async hasColumn(tableOrName: Table|string, columnName: string): Promise<boolean> {
const tableName = tableOrName instanceof Table ? tableOrName.name : tableOrName;
const sql = `PRAGMA table_info("${tableName}")`;
const sql = `PRAGMA table_info(${this.escapePath(tableName)})`;
const columns: ObjectLiteral[] = await this.query(sql);
return !!columns.find(column => column["name"] === columnName);
}
@ -319,8 +319,8 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
newTable.name = newTableName;
// rename table
const up = new Query(`ALTER TABLE "${oldTable.name}" RENAME TO "${newTableName}"`);
const down = new Query(`ALTER TABLE "${newTableName}" RENAME TO "${oldTable.name}"`);
const up = new Query(`ALTER TABLE ${this.escapePath(oldTable.name)} RENAME TO ${this.escapePath(newTableName)}`);
const down = new Query(`ALTER TABLE ${this.escapePath(newTableName)} RENAME TO ${this.escapePath(oldTable.name)}`);
await this.executeQueries(up, down);
// rename old table;
@ -721,21 +721,27 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
* Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
*/
async clearTable(tableName: string): Promise<void> {
await this.query(`DELETE FROM "${tableName}"`);
await this.query(`DELETE FROM ${this.escapePath(tableName)}`);
}
/**
* Removes all tables from the currently connected database.
*/
async clearDatabase(): Promise<void> {
async clearDatabase(database?: string): Promise<void> {
let dbPath: string | undefined = undefined;
if (database && this.driver.getAttachedDatabaseHandleByRelativePath(database)) {
dbPath = this.driver.getAttachedDatabaseHandleByRelativePath(database);
}
await this.query(`PRAGMA foreign_keys = OFF;`);
await this.startTransaction();
try {
const selectViewDropsQuery = `SELECT 'DROP VIEW "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'view'`;
const selectViewDropsQuery = dbPath ? `SELECT 'DROP VIEW "${dbPath}"."' || name || '";' as query FROM "${dbPath}"."sqlite_master" WHERE "type" = 'view'` : `SELECT 'DROP VIEW "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'view'`;
const dropViewQueries: ObjectLiteral[] = await this.query(selectViewDropsQuery);
await Promise.all(dropViewQueries.map(q => this.query(q["query"])));
const selectTableDropsQuery = `SELECT 'DROP TABLE "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'table' AND "name" != 'sqlite_sequence'`;
const selectTableDropsQuery = dbPath ? `SELECT 'DROP TABLE "${dbPath}"."' || name || '";' as query FROM "${dbPath}"."sqlite_master" WHERE "type" = 'table' AND "name" != 'sqlite_sequence'` : `SELECT 'DROP TABLE "' || name || '";' as query FROM "sqlite_master" WHERE "type" = 'table' AND "name" != 'sqlite_sequence'`;
const dropTableQueries: ObjectLiteral[] = await this.query(selectTableDropsQuery);
await Promise.all(dropTableQueries.map(q => this.query(q["query"])));
await this.commitTransaction();
@ -778,6 +784,21 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
});
}
protected async loadTableRecords(tablePath: string, tableOrIndex: "table" | "index") {
let database: string | undefined = undefined
const [schema, tableName] = this.splitTablePath(tablePath);
if (schema && this.driver.getAttachedDatabasePathRelativeByHandle(schema)) {
database = this.driver.getAttachedDatabasePathRelativeByHandle(schema)
}
const res = await this.query(`SELECT ${database ? `'${database}'` : null} as database, ${schema ? `'${schema}'` : null} as schema, * FROM ${schema ? `"${schema}".` : ""}${this.escapePath(`sqlite_master`)} WHERE "type" = '${tableOrIndex}' AND "${tableOrIndex === "table" ? "name" : "tbl_name"}" IN ('${tableName}')`);
return res;
}
protected async loadPragmaRecords(tablePath: string, pragma: string) {
const [, tableName] = this.splitTablePath(tablePath);
const res = await this.query(`PRAGMA ${pragma}("${tableName}")`);
return res;
}
/**
* Loads all tables (with given names) from the database and creates a Table from them.
*/
@ -787,15 +808,18 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
return [];
}
const dbTables: { name: string, sql: string }[] = [];
let dbTables: { database?: string, name: string, sql: string }[] = [];
let dbIndicesDef: ObjectLiteral[];
if (!tableNames) {
const tablesSql = `SELECT * FROM "sqlite_master" WHERE "type" = 'table'`;
dbTables.push(...await this.query(tablesSql))
} else {
const tableNamesString = tableNames.map(tableName => `'${tableName}'`).join(", ");
const tablesSql = `SELECT * FROM "sqlite_master" WHERE "type" = 'table' AND "name" IN (${tableNamesString})`;
dbTables.push(...await this.query(tablesSql));
const tableNamesString = dbTables.map(({ name }) => `'${name}'`).join(", ");
dbIndicesDef = await this.query(`SELECT * FROM "sqlite_master" WHERE "type" = 'index' AND "tbl_name" IN (${tableNamesString})`);
} else {
dbTables = (await Promise.all(tableNames.map(tableName => this.loadTableRecords(tableName, "table")))).reduce((acc, res) => ([...acc, ...res]), []).filter(Boolean);
dbIndicesDef = (await Promise.all((tableNames ?? []).map(tableName => this.loadTableRecords(tableName, "index")))).reduce((acc, res) => ([...acc, ...res]), []).filter(Boolean);
}
// if tables were not found in the db, no need to proceed
@ -803,23 +827,18 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
return [];
}
// load indices
const tableNamesString = dbTables.map(({ name }) => `'${name}'`).join(", ");
const dbIndicesDef: ObjectLiteral[] = await this.query(`SELECT * FROM "sqlite_master" WHERE "type" = 'index' AND "tbl_name" IN (${tableNamesString})`);
// create table schemas for loaded tables
return Promise.all(dbTables.map(async dbTable => {
const table = new Table();
table.name = dbTable["name"];
const tablePath = dbTable['database'] && this.driver.getAttachedDatabaseHandleByRelativePath(dbTable['database']) ? `${this.driver.getAttachedDatabaseHandleByRelativePath(dbTable['database'])}.${dbTable['name']}` : dbTable['name']
const table = new Table({name: tablePath});
const sql = dbTable["sql"];
// load columns and indices
const [dbColumns, dbIndices, dbForeignKeys]: ObjectLiteral[][] = await Promise.all([
this.query(`PRAGMA table_info("${dbTable["name"]}")`),
this.query(`PRAGMA index_list("${dbTable["name"]}")`),
this.query(`PRAGMA foreign_key_list("${dbTable["name"]}")`),
this.loadPragmaRecords(tablePath, `table_info`),
this.loadPragmaRecords(tablePath, `index_list`),
this.loadPragmaRecords(tablePath, `foreign_key_list`),
]);
// find column name with auto increment
@ -937,7 +956,6 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
const indexColumns = indexInfos
.sort((indexInfo1, indexInfo2) => parseInt(indexInfo1["seqno"]) - parseInt(indexInfo2["seqno"]))
.map(indexInfo => indexInfo["name"]);
if (indexColumns.length === 1) {
const column = table.columns.find(column => {
return !!indexColumns.find(indexColumn => indexColumn === column.name);
@ -950,8 +968,8 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
const foundMapping = uniqueMappings.find(mapping => {
return mapping!.columns.every(column =>
indexColumns.indexOf(column) !== -1
)
})
);
});
return new TableUnique({
name: foundMapping ? foundMapping.name : this.connection.namingStrategy.uniqueConstraintName(table, indexColumns),
@ -981,11 +999,12 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
const indexColumns = indexInfos
.sort((indexInfo1, indexInfo2) => parseInt(indexInfo1["seqno"]) - parseInt(indexInfo2["seqno"]))
.map(indexInfo => indexInfo["name"]);
const dbIndexPath = `${dbTable["database"] ? `${dbTable["database"]}.` : ''}${dbIndex!["name"]}`;
const isUnique = dbIndex!["unique"] === "1" || dbIndex!["unique"] === 1;
return new TableIndex(<TableIndexOptions>{
table: table,
name: dbIndex!["name"],
name: dbIndexPath,
columnNames: indexColumns,
isUnique: isUnique,
where: condition ? condition[1] : undefined
@ -1010,7 +1029,8 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
throw new TypeORMError(`Sqlite does not support AUTOINCREMENT on composite primary key`);
const columnDefinitions = table.columns.map(column => this.buildCreateColumnSql(column, skipPrimary)).join(", ");
let sql = `CREATE TABLE "${table.name}" (${columnDefinitions}`;
const [database] = this.splitTablePath(table.name);
let sql = `CREATE TABLE ${this.escapePath(table.name)} (${columnDefinitions}`;
// need for `addColumn()` method, because it recreates table.
table.columns
@ -1044,13 +1064,21 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
}
if (table.foreignKeys.length > 0 && createForeignKeys) {
const foreignKeysSql = table.foreignKeys.map(fk => {
const foreignKeysSql = table.foreignKeys.filter(fk => {
const [referencedDatabase] = this.splitTablePath(fk.referencedTableName);
if (referencedDatabase !== database) {
return false;
}
return true;
})
.map(fk => {
const [, referencedTable] = this.splitTablePath(fk.referencedTableName);
const columnNames = fk.columnNames.map(columnName => `"${columnName}"`).join(", ");
if (!fk.name)
fk.name = this.connection.namingStrategy.foreignKeyName(table, fk.columnNames, this.getTablePath(fk), fk.referencedColumnNames);
const referencedColumnNames = fk.referencedColumnNames.map(columnName => `"${columnName}"`).join(", ");
let constraint = `CONSTRAINT "${fk.name}" FOREIGN KEY (${columnNames}) REFERENCES "${fk.referencedTableName}" (${referencedColumnNames})`;
let constraint = `CONSTRAINT "${fk.name}" FOREIGN KEY (${columnNames}) REFERENCES "${referencedTable}" (${referencedColumnNames})`;
if (fk.onDelete)
constraint += ` ON DELETE ${fk.onDelete}`;
if (fk.onUpdate)
@ -1082,7 +1110,7 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
*/
protected dropTableSql(tableOrName: Table|string, ifExist?: boolean): Query {
const tableName = tableOrName instanceof Table ? tableOrName.name : tableOrName;
const query = ifExist ? `DROP TABLE IF EXISTS "${tableName}"` : `DROP TABLE "${tableName}"`;
const query = ifExist ? `DROP TABLE IF EXISTS ${this.escapePath(tableName)}` : `DROP TABLE ${this.escapePath(tableName)}`;
return new Query(query);
}
@ -1124,7 +1152,8 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
*/
protected createIndexSql(table: Table, index: TableIndex): Query {
const columns = index.columnNames.map(columnName => `"${columnName}"`).join(", ");
return new Query(`CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX "${index.name}" ON "${table.name}" (${columns}) ${index.where ? "WHERE " + index.where : ""}`);
const [database, tableName] = this.splitTablePath(table.name);
return new Query(`CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX ${database ? `"${database}".` : ""}${this.escapePath(index.name!)} ON "${tableName}" (${columns}) ${index.where ? "WHERE " + index.where : ""}`);
}
/**
@ -1132,7 +1161,7 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
*/
protected dropIndexSql(indexOrName: TableIndex|string): Query {
let indexName = indexOrName instanceof TableIndex ? indexOrName.name : indexOrName;
return new Query(`DROP INDEX "${indexName}"`);
return new Query(`DROP INDEX ${this.escapePath(indexName!)}`);
}
/**
@ -1173,7 +1202,9 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
});
// change table name into 'temporary_table'
newTable.name = "temporary_" + newTable.name;
let [databaseNew, tableNameNew] = this.splitTablePath(newTable.name);
let [, tableNameOld] = this.splitTablePath(oldTable.name);
newTable.name = tableNameNew = `${databaseNew ? `${databaseNew}.` : ""}temporary_${tableNameNew}`;
// create new table
upQueries.push(this.createTableSql(newTable, true));
@ -1194,8 +1225,8 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
}).map(column => `"${column.name}"`).join(", ");
}
upQueries.push(new Query(`INSERT INTO "${newTable.name}"(${newColumnNames}) SELECT ${oldColumnNames} FROM "${oldTable.name}"`));
downQueries.push(new Query(`INSERT INTO "${oldTable.name}"(${oldColumnNames}) SELECT ${newColumnNames} FROM "${newTable.name}"`));
upQueries.push(new Query(`INSERT INTO ${this.escapePath(newTable.name)}(${newColumnNames}) SELECT ${oldColumnNames} FROM ${this.escapePath(oldTable.name)}`));
downQueries.push(new Query(`INSERT INTO ${this.escapePath(oldTable.name)}(${oldColumnNames}) SELECT ${newColumnNames} FROM ${this.escapePath(newTable.name)}`));
}
// drop old table
@ -1203,8 +1234,8 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
downQueries.push(this.createTableSql(oldTable, true));
// rename old table
upQueries.push(new Query(`ALTER TABLE "${newTable.name}" RENAME TO "${oldTable.name}"`));
downQueries.push(new Query(`ALTER TABLE "${oldTable.name}" RENAME TO "${newTable.name}"`));
upQueries.push(new Query(`ALTER TABLE ${this.escapePath(newTable.name)} RENAME TO ${this.escapePath(tableNameOld)}`));
downQueries.push(new Query(`ALTER TABLE ${this.escapePath(oldTable.name)} RENAME TO ${this.escapePath(tableNameNew)}`));
newTable.name = oldTable.name;
@ -1221,4 +1252,19 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen
this.replaceCachedTable(oldTable, newTable);
}
/**
* tablePath e.g. "myDB.myTable", "myTable"
*/
protected splitTablePath(tablePath: string): [string | undefined, string] {
return ((tablePath.indexOf(".") !== -1) ? tablePath.split(".") : [undefined, tablePath]) as [string | undefined, string];
}
/**
* Escapes given table or view path. Tolerates leading/trailing dots
*/
protected escapePath(target: Table|View|string, disableEscape?: boolean): string {
const tableName = target instanceof Table || target instanceof View ? target.name : target;
return tableName.replace(/^\.+|\.+$/g, "").split(".").map(i => disableEscape ? i : `"${i}"`).join(".");
}
}

View File

@ -10,6 +10,8 @@ import { ColumnType } from "../types/ColumnTypes";
import { QueryRunner } from "../../query-runner/QueryRunner";
import { AbstractSqliteDriver } from "../sqlite-abstract/AbstractSqliteDriver";
import {ReplicationMode} from "../types/ReplicationMode";
import {filepathToName, isAbsolute} from "../../util/PathUtils";
/**
* Organizes communication with sqlite DBMS.
@ -81,6 +83,34 @@ export class SqliteDriver extends AbstractSqliteDriver {
return super.normalizeType(column);
}
async afterConnect(): Promise<void> {
return this.attachDatabases();
}
/**
* For SQLite, the database may be added in the decorator metadata. It will be a filepath to a database file.
*/
buildTableName(tableName: string, _schema?: string, database?: string): string {
if (!database) return tableName;
if (this.getAttachedDatabaseHandleByRelativePath(database)) return `${this.getAttachedDatabaseHandleByRelativePath(database)}.${tableName}`;
if (database === this.options.database) return tableName;
// we use the decorated name as supplied when deriving attach handle (ideally without non-portable absolute path)
const identifierHash = filepathToName(database);
// decorated name will be assumed relative to main database file when non absolute. Paths supplied as absolute won't be portable
const absFilepath = isAbsolute(database) ? database : path.join(this.getMainDatabasePath(), database);
this.attachedDatabases[database] = {
attachFilepathAbsolute: absFilepath,
attachFilepathRelative: database,
attachHandle: identifierHash,
};
return `${identifierHash}.${tableName}`;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
@ -144,4 +174,24 @@ export class SqliteDriver extends AbstractSqliteDriver {
await mkdirp(path.dirname(fullPath));
}
/**
* Performs the attaching of the database files. The attachedDatabase should have been populated during calls to #buildTableName
* during EntityMetadata production (see EntityMetadata#buildTablePath)
*
* https://sqlite.org/lang_attach.html
*/
protected async attachDatabases() {
// @todo - possibly check number of databases (but unqueriable at runtime sadly) - https://www.sqlite.org/limits.html#max_attached
for await (const {attachHandle, attachFilepathAbsolute} of Object.values(this.attachedDatabases)) {
await this.createDatabaseDirectory(attachFilepathAbsolute);
await this.connection.query(`ATTACH "${attachFilepathAbsolute}" AS "${attachHandle}"`);
}
}
protected getMainDatabasePath(): string {
const optionsDb = this.options.database;
return path.dirname(isAbsolute(optionsDb) ? optionsDb : path.join(process.cwd(), optionsDb));
}
}

32
src/util/PathUtils.ts Normal file
View File

@ -0,0 +1,32 @@
import { hash } from "./StringUtils";
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
const UNC_WINDOWS_PATH_REGEXP = /^\\\\(\.\\)?(.*)$/;
export function toPortablePath(filepath: string): string {
if (process.platform !== `win32`)
return filepath;
if (filepath.match(WINDOWS_PATH_REGEXP))
filepath = filepath.replace(WINDOWS_PATH_REGEXP, `/$1`);
else if (filepath.match(UNC_WINDOWS_PATH_REGEXP))
filepath = filepath.replace(UNC_WINDOWS_PATH_REGEXP, (match, p1, p2) => `/unc/${p1 ? `.dot/` : ``}${p2}`);
return filepath.replace(/\\/g, `/`);
}
/**
* Create deterministic valid database name (class, database) of fixed length from any filepath. Equivalent paths for windows/posix systems should
* be equivalent to enable portability
*/
export function filepathToName(filepath: string): string {
const uniq = toPortablePath(filepath).toLowerCase();
return hash(uniq, {length: 63});
}
/**
* Cross platform isAbsolute
*/
export function isAbsolute(filepath: string): boolean {
return !!filepath.match(/^(?:[a-z]:|[\\]|[\/])/i);
}

View File

@ -75,6 +75,7 @@ describe("column kinds > create date column", () => {
expect(loadedPostAfterUpdate!.createdAt.toString()).to.be.eql(loadedPostBeforeUpdate!.createdAt.toString());
})));
it("create date column should set a custom date when specified", () => Promise.all(connections.map(async connection => {
const postRepository = connection.getRepository(Post);

View File

@ -94,5 +94,5 @@ describe("ConnectionOptionsReader", () => {
const connectionOptionsReader = new ConnectionOptionsReader({ root: path.join(__dirname, "configs/yaml"), configName: "test-yaml" });
const fileOptions: ConnectionOptions = await connectionOptionsReader.get("file");
expect(fileOptions.database).to.have.string("/test-yaml");
})
});
});

View File

@ -0,0 +1,13 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
@Entity({ database: "filename-sqlite.db" })
export class Answer {
@PrimaryGeneratedColumn()
id: number;
@Column()
text: string;
}

View File

@ -0,0 +1,19 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
import {Post} from "./Post";
@Entity({ database: "./subdir/relative-subdir-sqlite.db" })
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToOne(type => Post)
post: Post;
}

View File

@ -0,0 +1,14 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
@Entity({ database: "./subdir/relative-subdir-sqlite.db" })
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}

View File

@ -0,0 +1,14 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {Column} from "../../../../../src/decorator/columns/Column";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}

View File

@ -0,0 +1,149 @@
import "reflect-metadata";
import { expect } from 'chai';
import { Connection } from "../../../../src/connection/Connection";
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../../utils/test-utils";
import { Answer } from "./entity/Answer";
import { Category } from "./entity/Category";
import { Post } from "./entity/Post";
import { User } from "./entity/User";
import { filepathToName } from "../../../../src/util/PathUtils";
import rimraf from "rimraf";
import path from "path";
import fs from "fs";
const VALID_NAME_REGEX = /^(?!sqlite_).{1,63}$/
describe("multi-database > basic-functionality", () => {
describe("filepathToName()", () => {
for (const platform of [`darwin`, `win32`]) {
let realPlatform: string;
beforeEach(() => {
realPlatform = process.platform;
Object.defineProperty(process, `platform`, {
configurable: true,
value: platform,
});
});
afterEach(() => {
Object.defineProperty(process, `platform`, {
configurable: true,
value: realPlatform,
});
});
it(`produces deterministic, unique, and valid table names for relative paths; leaves absolute paths unchanged (${platform})`, () => {
const testMap = [
["FILENAME.db", "filename.db"],
["..\\FILENAME.db", "../filename.db"],
["..\\longpathdir\\longpathdir\\longpathdir\\longpathdir\\longpathdir\\longpathdir\\longpathdir\\FILENAME.db", "../longpathdir/longpathdir/longpathdir/longpathdir/longpathdir/longpathdir/longpathdir/filename.db"],
["C:\\dir\FILENAME.db", "C:\\dir\FILENAME.db"],
["/dir/filename.db", "/dir/filename.db"],
];
for (const [winOs, otherOs] of testMap) {
const winOsRes = filepathToName(winOs);
const otherOsRes = filepathToName(otherOs);
expect(winOsRes).to.equal(otherOsRes);
expect(winOsRes).to.match(VALID_NAME_REGEX, `'${winOs}' is invalid table name`);
}
});
}
});
describe("multiple databases", () => {
let connections: Connection[];
const tempPath = path.resolve(__dirname, "../../../../../../temp");
const attachAnswerPath = path.join(tempPath, "filename-sqlite.db");
const attachAnswerHandle = filepathToName("filename-sqlite.db");
const attachCategoryPath = path.join(tempPath, "./subdir/relative-subdir-sqlite.db");
const attachCategoryHandle = filepathToName("./subdir/relative-subdir-sqlite.db");
before(async () => {
connections = await createTestingConnections({
entities: [Answer, Category, Post, User],
// enabledDrivers: ["sqlite", "better-sqlite3"],
enabledDrivers: ["sqlite"],
name: "sqlite",
});
});
beforeEach(() => reloadTestingDatabases(connections));
after(async () => {
await closeTestingConnections(connections);
return new Promise(resolve => rimraf(`${tempPath}/**/*.db`, {}, () => resolve()));
});
it("should correctly attach and create database files", () => Promise.all(connections.map(async connection => {
const expectedMainPath = path.join(tempPath, (connections[0].options.database as string).match(/^.*[\\|\/](?<filename>[^\\|\/]+)$/)!.groups!["filename"]);
expect(fs.existsSync(expectedMainPath)).to.be.true;
expect(fs.existsSync(attachAnswerPath)).to.be.true;
expect(fs.existsSync(attachCategoryPath)).to.be.true;
})));
it("should prefix tableName when custom database used in Entity decorator", () => Promise.all(connections.map(async connection => {
const queryRunner = connection.createQueryRunner();
const tablePathAnswer = `${attachAnswerHandle}.answer`;
const table = await queryRunner.getTable(tablePathAnswer);
await queryRunner.release();
const answer = new Answer();
answer.text = "Answer #1";
await connection.getRepository(Answer).save(answer);
const sql = connection.createQueryBuilder(Answer, "answer")
.where("answer.id = :id", {id: 1})
.getSql();
sql.should.be.equal(`SELECT "answer"."id" AS "answer_id", "answer"."text" AS "answer_text" FROM "${attachAnswerHandle}"."answer" "answer" WHERE "answer"."id" = ?`);
table!.name.should.be.equal(tablePathAnswer);
})));
it("should not affect tableName when using default main database", () => Promise.all(connections.map(async connection => {
const queryRunner = connection.createQueryRunner();
const tablePathUser = `user`;
const table = await queryRunner.getTable(tablePathUser);
await queryRunner.release();
const user = new User();
user.name = "User #1";
await connection.getRepository(User).save(user);
const sql = connection.createQueryBuilder(User, "user")
.where("user.id = :id", {id: 1})
.getSql();
sql.should.be.equal(`SELECT "user"."id" AS "user_id", "user"."name" AS "user_name" FROM "user" "user" WHERE "user"."id" = ?`);
table!.name.should.be.equal(tablePathUser);
})));
it("should create foreign keys for relations within the same database", () => Promise.all(connections.map(async connection => {
const queryRunner = connection.createQueryRunner();
const tablePathCategory = `${attachCategoryHandle}.category`;
const tablePathPost = `${attachCategoryHandle}.post`;
const tableCategory = (await queryRunner.getTable(tablePathCategory))!;
const tablePost = (await queryRunner.getTable(tablePathPost))!;
await queryRunner.release();
expect(tableCategory.foreignKeys.length).to.eq(1);
expect(tableCategory.foreignKeys[0].columnNames.length).to.eq(1); // before the fix this was 2, one for each schema
expect(tableCategory.foreignKeys[0].columnNames[0]).to.eq("postId");
expect(tablePost.foreignKeys.length).to.eq(0);
})));
});
});

View File

@ -3,7 +3,7 @@ import * as assert from "assert";
import {createConnection, getConnectionOptions} from "../../../src/index";
import {Connection} from "../../../src/connection/Connection";
describe("github issues > #798 sqlite: 'database' path in ormconfig.json is not relative", () => {
describe.only("github issues > #798 sqlite: 'database' path in ormconfig.json is not relative", () => {
let connection: Connection;
const oldCwd = process.cwd();

162
test/unit/path-utils.ts Normal file
View File

@ -0,0 +1,162 @@
import { toPortablePath, isAbsolute } from "../../src/util/PathUtils";
import { expect } from "chai";
describe(`path-utils`, () => {
describe("isAbsolute", () => {
it("discriminates cross platform relative paths", () => {
const testMap: [string, boolean][] = [
["FILENAME.db", false],
["./FILENAME.db", false],
[".FILENAME.db", false],
["path/FILENAME.db", false],
["pathFILENAME.db", false],
["..FILENAME.db", false],
["../filename.db", false],
["C:\\dirFILENAME.db", true],
["/dir/filename.db", true],
];
for (const [aPath, expected] of testMap) {
expect(isAbsolute(aPath), `${aPath} did not match ${expected}`).to.equal(expected);
}
});
});
describe("toPortablePath", () => {
for (const platform of [`darwin`, `win32`]) {
let realPlatform: string;
describe(`Platform ${platform}`, () => {
beforeEach(() => {
realPlatform = process.platform;
Object.defineProperty(process, `platform`, {
configurable: true,
value: platform,
});
});
afterEach(() => {
Object.defineProperty(process, `platform`, {
configurable: true,
value: realPlatform,
});
});
describe(`toPortablePath`, () => {
if (platform !== `win32`) {
it(`should change paths on non-Windows platform`, () => {
const inputPath = `C:\\Users\\user\\proj`;
const outputPath = inputPath;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
} else {
it(`shouldn't change absolute posix paths when producing portable path`, () => {
const inputPath = `/home/user/proj`;
const outputPath = inputPath;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`shouldn't change absolute paths that are already portable`, () => {
const inputPath = `/c:/Users/user/proj`;
const outputPath = `/c:/Users/user/proj`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should normalize the slashes in relative Windows paths`, () => {
const inputPath = `..\\Users\\user/proj`;
const outputPath = `../Users/user/proj`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should transform Windows paths into their posix counterparts (uppercase drive)`, () => {
const inputPath = `C:\\Users\\user\\proj`;
const outputPath = `/C:/Users/user/proj`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should transform Windows paths into their posix counterparts (lowercase drive)`, () => {
const inputPath = `c:\\Users\\user\\proj`;
const outputPath = `/c:/Users/user/proj`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should transform Windows paths into their posix counterparts (forward slashes)`, () => {
const inputPath = `C:/Users/user/proj`;
const outputPath = `/C:/Users/user/proj`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should support Windows paths that contain both backslashes and forward slashes`, () => {
const inputPath = `C:/Users\\user/proj`;
const outputPath = `/C:/Users/user/proj`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should support drive: Windows paths`, () => {
const inputPath = `C:`;
const outputPath = `/C:`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should support UNC Windows paths (\\\\[server]\\[sharename]\\)`, () => {
const inputPath = `\\\\Server01\\user\\docs\\Letter.txt`;
const outputPath = `/unc/Server01/user/docs/Letter.txt`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should support Long UNC Windows paths (\\\\?\\[server]\\[sharename]\\)`, () => {
const inputPath = `\\\\?\\Server01\\user\\docs\\Letter.txt`;
const outputPath = `/unc/?/Server01/user/docs/Letter.txt`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should support Long UNC Windows paths (\\\\?\\UNC\\[server]\\[sharename]\\)`, () => {
const inputPath = `\\\\?\\UNC\\Server01\\user\\docs\\Letter.txt`;
const outputPath = `/unc/?/UNC/Server01/user/docs/Letter.txt`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should support Long UNC Windows paths (\\\\?\\[drive_spec]:\\)`, () => {
const inputPath = `\\\\?\\C:\\user\\docs\\Letter.txt`;
const outputPath = `/unc/?/C:/user/docs/Letter.txt`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
it(`should support Long UNC Windows paths with dot (\\\\.\\[physical_device]\\)`, () => {
const inputPath = `\\\\.\\PhysicalDevice\\user\\docs\\Letter.txt`;
const outputPath = `/unc/.dot/PhysicalDevice/user/docs/Letter.txt`;
expect(toPortablePath(inputPath)).to.equal(
outputPath
);
});
}
});
});
}
});
});

View File

@ -7,6 +7,7 @@ import {NamingStrategyInterface} from "../../src/naming-strategy/NamingStrategyI
import {QueryResultCache} from "../../src/cache/QueryResultCache";
import {Logger} from "../../src/logger/Logger";
import {CockroachDriver} from "../../src/driver/cockroachdb/CockroachDriver";
import path from "path";
/**
* Interface in which data is stored in ormconfig.json of the project.
@ -172,16 +173,16 @@ export function setupSingleTestingConnection(driverType: DatabaseType, options:
/**
* Loads test connection options from ormconfig.json file.
*/
export function getTypeOrmConfig(): TestingConnectionOptions[] {
function getOrmFilepath(): string {
try {
try {
// first checks build/compiled
// useful for docker containers in order to provide a custom config
return require(__dirname + "/../../ormconfig.json");
return require.resolve(__dirname + "/../../ormconfig.json");
} catch (err) {
// fallbacks to the root config
return require(__dirname + "/../../../../ormconfig.json");
return require.resolve(__dirname + "/../../../../ormconfig.json");
}
} catch (err) {
@ -191,6 +192,10 @@ export function getTypeOrmConfig(): TestingConnectionOptions[] {
}
}
export function getTypeOrmConfig(): TestingConnectionOptions[] {
return require(getOrmFilepath());
}
/**
* Creates a testing connections options based on the configuration in the ormconfig.json
* and given options that can override some of its configuration for the test-specific use case.
@ -241,6 +246,9 @@ export function setupTestingConnections(options?: TestingOptions): ConnectionOpt
newOptions.namingStrategy = options.namingStrategy;
if (options && options.metadataTableName)
newOptions.metadataTableName = options.metadataTableName;
newOptions.baseDirectory = path.dirname(getOrmFilepath());
return newOptions;
});
}

9972
yarn.lock Normal file

File diff suppressed because it is too large Load Diff