mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
Merge pull request #73 from typeorm/persistance-refactoring
Persistance refactoring
This commit is contained in:
commit
95164dcbbd
29
CHANGELOG.md
29
CHANGELOG.md
@ -1,4 +1,29 @@
|
||||
# 0.0.2 (in development)
|
||||
# 0.0.3
|
||||
|
||||
* completely refactored persistence mechanism:
|
||||
* added experimental support of `{ nullable: true }` in relations
|
||||
* cascade operations should work better now
|
||||
* optimized all queries
|
||||
* entities with recursive entities should be persisted correctly now
|
||||
* now `undefined` properties are skipped in the persistence operation, as well as `undefined` relations.
|
||||
* added platforms abstractions to allow typeorm to work on multiple platforms
|
||||
* added experimental support of typeorm in the browser
|
||||
* breaking changes in `QueryBuilder`:
|
||||
* `getSingleResult()` renamed to `getOne()`
|
||||
* `getResults()` renamed to `getMany()`
|
||||
* `getResultsAndCount()` renamed to `getManyAndCount()`
|
||||
* in the innerJoin*/leftJoin* methods now no need to specify `ON`
|
||||
* in the innerJoin*/leftJoin* methods no longer supports parameters, use `addParameters` or `setParameter` instead.
|
||||
* `setParameters` is now works just like `addParameters` (because previous behaviour confused users),
|
||||
`addParameters` now is deprecated
|
||||
* `getOne` returns `Promise<Entity|undefined>`
|
||||
* breaking changes in `Repository` and `EntityManager`:
|
||||
* `findOne` and `findOneById` now return `Promise<Entity|undefined>` instead of `Promise<Entity>`
|
||||
* now typeorm is compiled into `ES5` instead of `ES6` - this allows to run it on older versions of node.js
|
||||
* fixed multiple issues with dates and utc-related stuff
|
||||
* multiple bugfixes
|
||||
|
||||
# 0.0.2
|
||||
|
||||
* lot of API refactorings
|
||||
* complete support TypeScript 2
|
||||
@ -9,4 +34,4 @@
|
||||
|
||||
# 0.0.1
|
||||
|
||||
* first stable version, works with TypeScript 1.x
|
||||
* first stable version, works with TypeScript 1.x
|
||||
|
||||
88
README.md
88
README.md
@ -9,9 +9,11 @@ Share this library with friends on twitter and everywhere else you can.
|
||||
If you notice bug or have something not working please report an issue, we'll try to fix it as soon as possible.
|
||||
More documentation and features expected to be soon. Feel free to contribute.
|
||||
|
||||
> 0.0.3 is released! Most notable changes are in the [changelog](./CHANGELOG.md).
|
||||
|
||||
TypeORM is an [Object Relational Mapper](1) (ORM) for node.js written in
|
||||
Typescript that can be used with Typescript or Javascript (ES5, ES6, ES7).
|
||||
Its goal to always support latest Javascript features and provide features
|
||||
TypeScript that can be used with TypeScript or JavaScript (ES5, ES6, ES7).
|
||||
Its goal to always support latest JavaScript features and provide features
|
||||
that help you to develop any kind of applications that use database - from
|
||||
small applications with a few tables to large scale enterprise applications.
|
||||
TypeORM helps you to:
|
||||
@ -31,7 +33,8 @@ maintainable applications with less problems.
|
||||
The benefit of using TypeORM for the programmer is the ability to focus on
|
||||
the business logic and worry about persistence only as a secondary problem.
|
||||
|
||||
TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate.org/orm/) and [Doctrine](http://www.doctrine-project.org/).
|
||||
TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate.org/orm/),
|
||||
[Doctrine](http://www.doctrine-project.org/) and [Entity Framework](https://www.asp.net/entity-framework).
|
||||
|
||||
## Installation
|
||||
|
||||
@ -65,12 +68,35 @@ TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate
|
||||
|
||||
`npm install mssql --save`
|
||||
|
||||
* for **Oracle**
|
||||
* for **Oracle** (experimental)
|
||||
|
||||
`npm install oracledb --save`
|
||||
|
||||
Install only one of them, depend on which database you use.
|
||||
|
||||
To make oracle driver to work you need to follow installation instructions from
|
||||
[their](https://github.com/oracle/node-oracledb) site.
|
||||
|
||||
#### TypeScript configuration
|
||||
|
||||
Also make sure you are using TypeScript compiler version > **2.1** and you have enabled following settings in `tsconfig.json`:
|
||||
|
||||
```json
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
```
|
||||
|
||||
#### Node.js version
|
||||
|
||||
TypeORM was tested with Node.JS version 6 and above.
|
||||
If you have errors during app bootstrap, try to upgrade your node.js version to the latest version.
|
||||
|
||||
## Usage in the browser with WebSQL
|
||||
|
||||
TypeORM works in the browser and has experimental support of WebSQL.
|
||||
If you want to use TypeORM in the browser then you need to `npm i typeorm-browser` instead of `typeorm`.
|
||||
More information about it in [this page](https://typeorm.github.io/usage-in-browser.html).
|
||||
Also take a look on [this sample](https://github.com/typeorm/browser-example).
|
||||
|
||||
## Quick Start
|
||||
|
||||
@ -297,7 +323,7 @@ createConnection({
|
||||
autoSchemaSync: true,
|
||||
}).then(connection => {
|
||||
// here you can start to work with your entities
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
We are using mysql in this example, but you can use any other database.
|
||||
@ -334,7 +360,7 @@ createConnection({
|
||||
autoSchemaSync: true,
|
||||
}).then(connection => {
|
||||
// here you can start to work with your entities
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### Run the application
|
||||
@ -379,7 +405,7 @@ createConnection(/*...*/).then(connection => {
|
||||
console.log("Photo has been saved");
|
||||
});
|
||||
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### Using async/await syntax
|
||||
@ -402,7 +428,7 @@ createConnection(/*...*/).then(async connection => {
|
||||
await connection.entityManager.persist(photo);
|
||||
console.log("Photo has been saved");
|
||||
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### Using Entity Manager
|
||||
@ -422,7 +448,7 @@ createConnection(/*...*/).then(async connection => {
|
||||
let savedPhotos = await connection.entityManager.find(Photo);
|
||||
console.log("All photos from the db: ", savedPhotos);
|
||||
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
savedPhotos will be an array of Photo objects with the data loaded from the database.
|
||||
@ -455,7 +481,7 @@ createConnection(/*...*/).then(async connection => {
|
||||
let savedPhotos = await photoRepository.find();
|
||||
console.log("All photos from the db: ", savedPhotos);
|
||||
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### Loading photos from the database
|
||||
@ -488,7 +514,7 @@ createConnection(/*...*/).then(async connection => {
|
||||
console.log("All photos: ", allPublishedPhotos);
|
||||
console.log("Photos count: ", allPublishedPhotos);
|
||||
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### Updating photo in the database
|
||||
@ -506,7 +532,7 @@ createConnection(/*...*/).then(async connection => {
|
||||
photoToUpdate.name = "Me, my friends and polar bears";
|
||||
await photoRepository.persist(photoToUpdate);
|
||||
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
Now photo with `id = 1` will be updated in the database.
|
||||
@ -526,7 +552,7 @@ createConnection(/*...*/).then(async connection => {
|
||||
let photoToRemove = await photoRepository.findOneById(1);
|
||||
await photoRepository.remove(photoToRemove);
|
||||
|
||||
});
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
Now photo with `id = 1` will be removed from the database.
|
||||
@ -633,7 +659,8 @@ createConnection(/*...*/).then(async connection => {
|
||||
|
||||
// done
|
||||
console.log("metadata is saved, and relation between metadata and photo is created in the database too");
|
||||
});
|
||||
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### Adding inverse side of a relation
|
||||
@ -707,7 +734,8 @@ createConnection(/*...*/).then(async connection => {
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
Here photos will contain array of photos from the database, and each photo will contain its photo metadata.
|
||||
@ -731,10 +759,11 @@ createConnection(/*...*/).then(async connection => {
|
||||
/*...*/
|
||||
let photoRepository = connection.getRepository(Photo);
|
||||
let photos = await photoRepository.createQueryBuilder("photo")
|
||||
.innerJoinAndSelect("photo.metadata")
|
||||
.getResults();
|
||||
.innerJoinAndSelect("photo.metadata", "metadata")
|
||||
.getMany();
|
||||
|
||||
});
|
||||
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### using cascade options to automatically save related objects
|
||||
@ -780,7 +809,8 @@ createConnection(options).then(async connection => {
|
||||
metadata.compressed = true;
|
||||
metadata.comment = "cybershoot";
|
||||
metadata.orientation = "portait";
|
||||
metadata.photo = photo; // this way we connect them
|
||||
|
||||
photo.metadata = metadata; // this way we connect them
|
||||
|
||||
// get repository
|
||||
let photoRepository = connection.getRepository(Photo);
|
||||
@ -789,7 +819,8 @@ createConnection(options).then(async connection => {
|
||||
await photoRepository.persist(photo);
|
||||
|
||||
console.log("Photo is saved, photo metadata is saved too.")
|
||||
});
|
||||
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
### creating a many-to-one / one-to-many relation
|
||||
@ -931,7 +962,7 @@ const options: CreateConnectionOptions = {
|
||||
};
|
||||
```
|
||||
|
||||
Now lets insert author and photo to our database:
|
||||
Now lets insert albums and photos to our database:
|
||||
|
||||
```typescript
|
||||
let connection = await createConnection(options);
|
||||
@ -947,18 +978,20 @@ album2.name = "Me";
|
||||
let photo1 = new Photo();
|
||||
photo1.name = "Me and Bears";
|
||||
photo1.description = "I am near polar bears";
|
||||
photo1.filename = "photo-with-bears.jpg"
|
||||
photo1.filename = "photo-with-bears.jpg";
|
||||
photo1.albums.push(album1);
|
||||
|
||||
let photo2 = new Photo();
|
||||
photo2.name = "Me and Bears";
|
||||
photo2.description = "I am near polar bears";
|
||||
photo2.filename = "photo-with-bears.jpg"
|
||||
photo2.filename = "photo-with-bears.jpg";
|
||||
photo2.albums.push(album2);
|
||||
|
||||
// get entity repository
|
||||
let photoRepository = connection.getRepository(Photo);
|
||||
|
||||
// first save a first photo
|
||||
// we only save a photos, albums are persisted
|
||||
// we only save the photos, albums are persisted
|
||||
// automatically because of cascade options
|
||||
await photoRepository.persist(photo1);
|
||||
|
||||
@ -976,15 +1009,15 @@ You can use QueryBuilder to build even more complex queries. For example you can
|
||||
let photoRepository = connection.getRepository(Photo);
|
||||
let photos = await photoRepository
|
||||
.createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
|
||||
.innerJoinAndSelect("photo.metadata")
|
||||
.leftJoinAndSelect("photo.albums")
|
||||
.innerJoinAndSelect("photo.metadata", "metadata")
|
||||
.leftJoinAndSelect("photo.albums", "albums")
|
||||
.where("photo.isPublished=true")
|
||||
.andWhere("(photo.name=:photoName OR photo.name=:bearName)")
|
||||
.orderBy("photo.id", "DESC")
|
||||
.setFirstResult(5)
|
||||
.setMaxResults(10)
|
||||
.setParameters({ photoName: "My", bearName: "Mishka" })
|
||||
.getResults();
|
||||
.getMany();
|
||||
```
|
||||
|
||||
This query builder will select you all photos that are published and whose name is "My" or "Mishka",
|
||||
@ -1010,6 +1043,7 @@ You'll use query builder in your application a lot. Learn more about QueryBuilde
|
||||
* [Subscribers and entity listeners](https://typeorm.github.io/subscribers-and-entity-listeners.html)
|
||||
* [Using service container](https://typeorm.github.io/using-service-container.html)
|
||||
* [Decorators Reference](https://typeorm.github.io/decorators-reference.html)
|
||||
* [Usage in the browser](https://typeorm.github.io/usage-in-browser.html)
|
||||
|
||||
## Samples
|
||||
|
||||
|
||||
1015
README_BROWSER.md
Normal file
1015
README_BROWSER.md
Normal file
File diff suppressed because it is too large
Load Diff
173
gulpfile.ts
173
gulpfile.ts
@ -4,14 +4,17 @@ const gulp = require("gulp");
|
||||
const del = require("del");
|
||||
const shell = require("gulp-shell");
|
||||
const replace = require("gulp-replace");
|
||||
const rename = require("gulp-rename");
|
||||
const file = require("gulp-file");
|
||||
const uglify = require("gulp-uglify");
|
||||
const mocha = require("gulp-mocha");
|
||||
const chai = require("chai");
|
||||
const tslint = require("gulp-tslint");
|
||||
const stylish = require("tslint-stylish");
|
||||
const ts = require("gulp-typescript");
|
||||
const sourcemaps = require("gulp-sourcemaps");
|
||||
const istanbul = require("gulp-istanbul");
|
||||
const remapIstanbul = require("remap-istanbul/lib/gulpRemapIstanbul");
|
||||
const ts = require("gulp-typescript");
|
||||
|
||||
@Gulpclass()
|
||||
export class Gulpfile {
|
||||
@ -38,14 +41,132 @@ export class Gulpfile {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Packaging and Publishing tasks
|
||||
// Build and packaging for browser
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Copies all source files into destination folder in a correct structure.
|
||||
*/
|
||||
@Task()
|
||||
browserCopySources() {
|
||||
return gulp.src([
|
||||
"./src/**/*.ts",
|
||||
"!./src/commands/*.ts",
|
||||
"!./src/cli.ts",
|
||||
"!./src/typeorm.ts",
|
||||
"!./src/decorators-shim.ts",
|
||||
"!./src/platform/PlatformTools.ts",
|
||||
"!./src/platform/BrowserPlatformTools.ts"
|
||||
])
|
||||
.pipe(gulp.dest("./build/browser/typeorm"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates special main file for browser build.
|
||||
*/
|
||||
@Task()
|
||||
browserCopyMainBrowserFile() {
|
||||
return gulp.src("./package.json", { read: false })
|
||||
.pipe(file("typeorm.ts", `export * from "./typeorm/index";`))
|
||||
.pipe(gulp.dest("./build/browser"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces PlatformTools with browser-specific implementation called BrowserPlatformTools.
|
||||
*/
|
||||
@Task()
|
||||
browserCopyPlatformTools() {
|
||||
return gulp.src("./src/platform/BrowserPlatformTools.ts")
|
||||
.pipe(rename("PlatformTools.ts"))
|
||||
.pipe(gulp.dest("./build/browser/typeorm/platform"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs files compilation of browser-specific source code.
|
||||
*/
|
||||
@MergedTask()
|
||||
browserCompile() {
|
||||
const tsProject = ts.createProject("tsconfig.json", {
|
||||
outFile: "typeorm-browser.js",
|
||||
module: "system",
|
||||
typescript: require("typescript")
|
||||
});
|
||||
const tsResult = gulp.src(["./build/browser/**/*.ts", "./node_modules/@types/**/*.ts"])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsProject());
|
||||
|
||||
return [
|
||||
tsResult.dts.pipe(gulp.dest("./build/browser-package")),
|
||||
tsResult.js
|
||||
.pipe(sourcemaps.write(".", { sourceRoot: "", includeContent: true }))
|
||||
.pipe(gulp.dest("./build/browser-package"))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Uglifys all code.
|
||||
*/
|
||||
@Task()
|
||||
browserUglify() {
|
||||
return gulp.src("./build/browser-package/*.js")
|
||||
.pipe(uglify())
|
||||
.pipe(rename("typeorm-browser.min.js"))
|
||||
.pipe(gulp.dest("./build/browser-package"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies README_BROWSER.md into README.md for the typeorm-browser package.
|
||||
*/
|
||||
@Task()
|
||||
browserCopyReadmeFile() {
|
||||
return gulp.src("./README_BROWSER.md")
|
||||
.pipe(rename("README.md"))
|
||||
.pipe(gulp.dest("./build/browser-package"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies package_browser.json into package.json for the typeorm-browser package.
|
||||
*/
|
||||
@Task()
|
||||
browserCopyPackageJsonFile() {
|
||||
return gulp.src("./package_browser.json")
|
||||
.pipe(rename("package.json"))
|
||||
.pipe(replace("\"private\": true,", "\"private\": false,"))
|
||||
.pipe(gulp.dest("./build/browser-package"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all tasks for the browser build and package.
|
||||
*/
|
||||
@SequenceTask()
|
||||
browserPackage() {
|
||||
return [
|
||||
["browserCopySources", "browserCopyMainBrowserFile", "browserCopyPlatformTools"],
|
||||
"browserCompile",
|
||||
["browserCopyReadmeFile", "browserUglify", "browserCopyPackageJsonFile"]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Publishes a browser package.
|
||||
*/
|
||||
@Task()
|
||||
browserPublish() {
|
||||
return gulp.src("*.js", { read: false })
|
||||
.pipe(shell([
|
||||
"cd ./build/browser-package && npm publish"
|
||||
]));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Main Packaging and Publishing tasks
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Publishes a package to npm from ./build/package directory.
|
||||
*/
|
||||
@Task()
|
||||
npmPublish() {
|
||||
nodePublish() {
|
||||
return gulp.src("*.js", { read: false })
|
||||
.pipe(shell([
|
||||
"cd ./build/package && npm publish"
|
||||
@ -60,7 +181,7 @@ export class Gulpfile {
|
||||
const tsProject = ts.createProject("tsconfig.json", { typescript: require("typescript") });
|
||||
const tsResult = gulp.src(["./src/**/*.ts", "./node_modules/@types/**/*.ts"])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(ts(tsProject));
|
||||
.pipe(tsProject());
|
||||
|
||||
return [
|
||||
tsResult.dts.pipe(gulp.dest("./build/package")),
|
||||
@ -79,6 +200,17 @@ export class Gulpfile {
|
||||
.pipe(gulp.dest("./build/package"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes /// <reference from compiled sources.
|
||||
*/
|
||||
@Task()
|
||||
packageReplaceReferences() {
|
||||
return gulp.src("./build/package/**/*.d.ts")
|
||||
.pipe(replace(`/// <reference types="node" />`, ""))
|
||||
.pipe(replace(`/// <reference types="chai" />`, ""))
|
||||
.pipe(gulp.dest("./build/package"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all compiled files to the final package directory.
|
||||
*/
|
||||
@ -100,23 +232,15 @@ export class Gulpfile {
|
||||
}
|
||||
|
||||
/**
|
||||
* This task will replace all typescript code blocks in the README (since npm does not support typescript syntax
|
||||
* highlighting) and copy this README file into the package folder.
|
||||
* Creates a package that can be published to npm.
|
||||
*/
|
||||
@Task()
|
||||
packageReadmeFile() {
|
||||
return gulp.src("./README.md")
|
||||
.pipe(replace(/```typescript([\s\S]*?)```/g, "```javascript$1```"))
|
||||
.pipe(gulp.dest("./build/package"));
|
||||
}
|
||||
|
||||
/**
|
||||
* This task will copy typings.json file to the build package.
|
||||
*/
|
||||
@Task()
|
||||
copyTypingsFile() {
|
||||
return gulp.src("./typings.json")
|
||||
.pipe(gulp.dest("./build/package"));
|
||||
@SequenceTask()
|
||||
nodePackage() {
|
||||
return [
|
||||
"packageCompile",
|
||||
"packageMoveCompiledFiles",
|
||||
["packageClearCompileDirectory", "packageReplaceReferences", "packagePreparePackageFile"],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,10 +250,7 @@ export class Gulpfile {
|
||||
package() {
|
||||
return [
|
||||
"clean",
|
||||
"packageCompile",
|
||||
"packageMoveCompiledFiles",
|
||||
"packageClearCompileDirectory",
|
||||
["packagePreparePackageFile", "packageReadmeFile", "copyTypingsFile"]
|
||||
["nodePackage", "browserPackage"]
|
||||
];
|
||||
}
|
||||
|
||||
@ -138,7 +259,7 @@ export class Gulpfile {
|
||||
*/
|
||||
@SequenceTask()
|
||||
publish() {
|
||||
return ["package", "npmPublish"];
|
||||
return ["package", "nodePublish", "browserPublish"];
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -195,7 +316,7 @@ export class Gulpfile {
|
||||
*/
|
||||
@SequenceTask()
|
||||
tests() {
|
||||
return [/*"compile", */"tslint", "coveragePost", "coverageRemap"];
|
||||
return ["compile", "tslint", "coveragePost", "coverageRemap"];
|
||||
}
|
||||
|
||||
}
|
||||
62
package.json
62
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "typeorm",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"description": "Data-mapper ORM for Typescript",
|
||||
"version": "0.0.3",
|
||||
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases.",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"author": {
|
||||
@ -21,56 +21,64 @@
|
||||
"typescript",
|
||||
"typescript-orm",
|
||||
"mysql",
|
||||
"mysql-orm"
|
||||
"mysql-orm",
|
||||
"postgresql",
|
||||
"postgresql-orm",
|
||||
"mariadb",
|
||||
"mariadb-orm",
|
||||
"sqlite",
|
||||
"sqlite-orm",
|
||||
"sql-server",
|
||||
"sql-server-orm",
|
||||
"oracle",
|
||||
"oracle-orm",
|
||||
"websql",
|
||||
"websql-orm"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.4.30",
|
||||
"@types/chai-as-promised": "0.0.29",
|
||||
"@types/mocha": "^2.2.32",
|
||||
"@types/mocha": "^2.2.33",
|
||||
"@types/promises-a-plus": "0.0.27",
|
||||
"@types/sinon": "^1.16.30",
|
||||
"@types/sinon": "^1.16.32",
|
||||
"chai": "^3.4.1",
|
||||
"chai-as-promised": "^5.3.0",
|
||||
"chai-as-promised": "^6.0.0",
|
||||
"del": "^2.2.2",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-file": "^0.3.0",
|
||||
"gulp-istanbul": "^1.1.1",
|
||||
"gulp-mocha": "^3.0.1",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-replace": "^0.5.4",
|
||||
"gulp-shell": "^0.5.1",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-tslint": "^6.1.1",
|
||||
"gulp-typescript": "^2.14.0",
|
||||
"gulp-sourcemaps": "^1.9.1",
|
||||
"gulp-tslint": "^7.0.1",
|
||||
"gulp-typescript": "^3.1.3",
|
||||
"gulp-uglify": "^2.0.0",
|
||||
"gulpclass": "0.1.1",
|
||||
"mariasql": "^0.2.6",
|
||||
"mocha": "^3.0.2",
|
||||
"mocha": "^3.2.0",
|
||||
"mssql": "^3.3.0",
|
||||
"mysql": "^2.11.1",
|
||||
"mysql2": "^1.1.0",
|
||||
"mysql": "^2.12.0",
|
||||
"mysql2": "^1.1.2",
|
||||
"oracledb": "^1.11.0",
|
||||
"pg": "^6.1.0",
|
||||
"remap-istanbul": "^0.6.4",
|
||||
"remap-istanbul": "^0.7.0",
|
||||
"sinon": "^1.17.6",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"sqlite3": "^3.1.4",
|
||||
"ts-node": "^1.3.0",
|
||||
"tslint": "^3.15.0-dev.0",
|
||||
"sqlite3": "^3.1.8",
|
||||
"ts-node": "^1.7.0",
|
||||
"tslint": "^4.0.2",
|
||||
"tslint-stylish": "^2.1.0-beta",
|
||||
"typescript": "^2.1.0-dev.20160919"
|
||||
"typescript": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "4.14.35",
|
||||
"@types/node": "^6.0.39",
|
||||
"@types/node": "^6.0.51",
|
||||
"app-root-path": "^2.0.1",
|
||||
"glob": "^7.1.0",
|
||||
"lodash": "^4.16.1",
|
||||
"moment": "^2.15.0",
|
||||
"path": "^0.12.7",
|
||||
"glob": "^7.1.1",
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"require-all": "^2.0.0",
|
||||
"sha1": "^1.1.1",
|
||||
"url": "^0.11.0",
|
||||
"yargonaut": "^1.1.2",
|
||||
"yargs": "^5.0.0"
|
||||
"yargs": "^6.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/gulp tests"
|
||||
|
||||
45
package_browser.json
Normal file
45
package_browser.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "typeorm-browser",
|
||||
"private": true,
|
||||
"version": "0.0.3",
|
||||
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases.",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"author": {
|
||||
"name": "Umed Khudoiberdiev",
|
||||
"email": "pleerock.me@gmail.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/typeorm/typeorm.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/typeorm/typeorm/issues"
|
||||
},
|
||||
"tags": [
|
||||
"orm",
|
||||
"typescript",
|
||||
"typescript-orm",
|
||||
"mysql",
|
||||
"mysql-orm",
|
||||
"postgresql",
|
||||
"postgresql-orm",
|
||||
"mariadb",
|
||||
"mariadb-orm",
|
||||
"sqlite",
|
||||
"sqlite-orm",
|
||||
"sql-server",
|
||||
"sql-server-orm",
|
||||
"oracle",
|
||||
"oracle-orm",
|
||||
"websql",
|
||||
"websql-orm"
|
||||
],
|
||||
"devDependencies": {
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/gulp tests"
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
import * as _ from "lodash";
|
||||
import {NamingStrategyInterface} from "../../../src/naming-strategy/NamingStrategyInterface";
|
||||
import {NamingStrategy} from "../../../src/decorator/NamingStrategy";
|
||||
import {DefaultNamingStrategy} from "../../../src/naming-strategy/DefaultNamingStrategy";
|
||||
import {snakeCase} from "../../../src/util/StringUtils";
|
||||
|
||||
@NamingStrategy("custom_strategy")
|
||||
export class CustomNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
|
||||
|
||||
tableName(className: string, customName: string): string {
|
||||
return customName ? customName : _.snakeCase(className);
|
||||
return customName ? customName : snakeCase(className);
|
||||
}
|
||||
|
||||
columnName(propertyName: string, customName: string): string {
|
||||
return customName ? customName : _.snakeCase(propertyName);
|
||||
return customName ? customName : snakeCase(propertyName);
|
||||
}
|
||||
|
||||
columnNameCustomized(customName: string): string {
|
||||
@ -19,7 +19,7 @@ export class CustomNamingStrategy extends DefaultNamingStrategy implements Namin
|
||||
}
|
||||
|
||||
relationName(propertyName: string): string {
|
||||
return _.snakeCase(propertyName);
|
||||
return snakeCase(propertyName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ createConnection(options).then(connection => {
|
||||
.createQueryBuilder("post")
|
||||
.where("post.title=:keyword")
|
||||
.setParameter("keyword", "hello")
|
||||
.getResults();
|
||||
.getMany();
|
||||
})
|
||||
.then(post => {
|
||||
console.log("Loaded post: ", post);
|
||||
|
||||
@ -50,7 +50,7 @@ export class Post {
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinColumn()
|
||||
metadata: PostMetadata|undefined;
|
||||
metadata: PostMetadata|null;
|
||||
|
||||
// post has relation with details. full cascades here
|
||||
@OneToOne(type => PostInformation, information => information.post, {
|
||||
|
||||
@ -60,15 +60,15 @@ createConnection(options).then(connection => {
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoinAndMapMany("post.superCategories", "post.categories", "categories")
|
||||
.leftJoinAndMapOne("post.author", Author, "author", "ON", "author.id=post.authorId")
|
||||
.getResults();
|
||||
.leftJoinAndMapOne("post.author", Author, "author", "author.id=post.authorId")
|
||||
.getMany();
|
||||
|
||||
}).then(posts => {
|
||||
console.log("Loaded posts: ", posts);
|
||||
|
||||
return entityManager
|
||||
.createQueryBuilder(Author, "author")
|
||||
.getResults();
|
||||
.getMany();
|
||||
|
||||
}).then(authors => {
|
||||
console.log("Loaded authors: ", authors);
|
||||
|
||||
@ -51,7 +51,7 @@ createConnection(options).then(connection => {
|
||||
.leftJoin("post.categories", "categories")
|
||||
.leftJoin("categories.author", "author")
|
||||
.where("post.id=1")
|
||||
.getSingleResult();
|
||||
.getOne();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log("loadedPosts: ", loadedPost);
|
||||
@ -71,7 +71,7 @@ createConnection(options).then(connection => {
|
||||
.leftJoinAndSelect("post.author", "author")
|
||||
.leftJoinAndSelect("post.categories", "categories")
|
||||
.where("post.id=:id", { id: post.id })
|
||||
.getSingleResult();
|
||||
.getOne();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log(loadedPost);
|
||||
@ -88,7 +88,7 @@ createConnection(options).then(connection => {
|
||||
.leftJoinAndSelect("post.author", "author")
|
||||
.leftJoinAndSelect("post.categories", "categories")
|
||||
.where("post.id=:id", { id: post.id })
|
||||
.getSingleResult();
|
||||
.getOne();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log(loadedPost);
|
||||
@ -102,7 +102,7 @@ createConnection(options).then(connection => {
|
||||
.leftJoinAndSelect("post.author", "author")
|
||||
.leftJoinAndSelect("post.categories", "categories")
|
||||
.where("post.id=:id", { id: post.id })
|
||||
.getSingleResult();
|
||||
.getOne();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log(loadedPost);
|
||||
@ -116,7 +116,7 @@ createConnection(options).then(connection => {
|
||||
.leftJoinAndSelect("post.author", "author")
|
||||
.leftJoinAndSelect("post.categories", "categories")
|
||||
.where("post.id=:id", { id: post.id })
|
||||
.getSingleResult();
|
||||
.getOne();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log(loadedPost);
|
||||
|
||||
@ -48,7 +48,7 @@ createConnection(options).then(async connection => {
|
||||
console.log("employee has been updated: ", employee);
|
||||
|
||||
console.log("now loading the employee: ");
|
||||
const loadedEmployee = await employeeRepository.findOneById(1);
|
||||
const loadedEmployee = (await employeeRepository.findOneById(1))!;
|
||||
console.log("loaded employee: ", loadedEmployee);
|
||||
|
||||
loadedEmployee.firstName = "dima";
|
||||
|
||||
@ -45,7 +45,7 @@ export class Post {
|
||||
@ManyToOne(type => PostMetadata, metadata => metadata.posts, {
|
||||
cascadeRemove: true
|
||||
})
|
||||
metadata: PostMetadata|undefined;
|
||||
metadata: PostMetadata|null;
|
||||
|
||||
// post has relation with details. full cascades here
|
||||
@ManyToOne(type => PostInformation, information => information.posts, {
|
||||
|
||||
@ -27,6 +27,6 @@ export class PostDetails {
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
posts: Post[] = [];
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
@ -26,7 +26,7 @@ export class Post {
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
categories: PostCategory[] = [];
|
||||
categories: PostCategory[];
|
||||
|
||||
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
|
||||
// relation it will be inserted automatically to the db when you save this Post entity
|
||||
@ -34,7 +34,7 @@ export class Post {
|
||||
cascadeInsert: true
|
||||
})
|
||||
@JoinTable()
|
||||
details: PostDetails[] = [];
|
||||
details: PostDetails[];
|
||||
|
||||
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
|
||||
// it will be inserted automatically to the db when you save this Post entity
|
||||
@ -42,7 +42,7 @@ export class Post {
|
||||
cascadeUpdate: true
|
||||
})
|
||||
@JoinTable()
|
||||
images: PostImage[] = [];
|
||||
images: PostImage[];
|
||||
|
||||
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
|
||||
// it will be inserted automatically to the db when you save this Post entity
|
||||
@ -50,7 +50,7 @@ export class Post {
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
metadatas: PostMetadata[] = [];
|
||||
metadatas: PostMetadata[];
|
||||
|
||||
// post has relation with details. full cascades here
|
||||
@ManyToMany(type => PostInformation, information => information.posts, {
|
||||
@ -59,11 +59,11 @@ export class Post {
|
||||
cascadeRemove: true
|
||||
})
|
||||
@JoinTable()
|
||||
informations: PostInformation[] = [];
|
||||
informations: PostInformation[];
|
||||
|
||||
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
|
||||
@ManyToMany(type => PostAuthor, author => author.posts)
|
||||
@JoinTable()
|
||||
authors: PostAuthor[] = [];
|
||||
authors: PostAuthor[];
|
||||
|
||||
}
|
||||
@ -27,6 +27,6 @@ export class PostDetails {
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
posts: Post[] = [];
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
@ -53,7 +53,7 @@ createConnection(options).then(connection => {
|
||||
.leftJoinAndSelect("p.author", "author")
|
||||
.leftJoinAndSelect("p.categories", "categories")
|
||||
.where("p.id = :id", { id: loadedPost.id })
|
||||
.getSingleResult();
|
||||
.getOne();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log("---------------------------");
|
||||
|
||||
@ -52,7 +52,7 @@ createConnection(options).then(connection => {
|
||||
Promise.all(posts.map(post => postRepository.persist(post)))
|
||||
.then(savedPosts => {
|
||||
console.log("Posts has been saved. Lets try to load some posts");
|
||||
return qb.getResults();
|
||||
return qb.getMany();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log("post loaded: ", loadedPost);
|
||||
|
||||
@ -52,7 +52,7 @@ createConnection(options).then(connection => {
|
||||
.leftJoinAndSelect("p.author", "author")
|
||||
.leftJoinAndSelect("p.categories", "categories")
|
||||
.where("p.id = :id", { id: loadedPost.id })
|
||||
.getSingleResult();
|
||||
.getOne();
|
||||
})
|
||||
.then(loadedPost => {
|
||||
console.log("load finished. Now lets update entity");
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import {createConnection} from "../index";
|
||||
import {QueryRunner} from "../query-runner/QueryRunner";
|
||||
import {Connection} from "../connection/Connection";
|
||||
|
||||
/**
|
||||
* Executes an sql query on the given connection.
|
||||
@ -17,18 +19,24 @@ export class QueryCommand {
|
||||
}
|
||||
|
||||
async handler(argv: any) {
|
||||
let connection: Connection|undefined = undefined,
|
||||
queryRunner: QueryRunner|undefined = undefined;
|
||||
try {
|
||||
process.env.SKIP_SCHEMA_CREATION = true;
|
||||
const connectionName = "default" || argv.connection;
|
||||
const connection = await createConnection(connectionName);
|
||||
const queryRunner = await connection.driver.createQueryRunner();
|
||||
connection = await createConnection("default" || argv.connection);
|
||||
queryRunner = await connection.driver.createQueryRunner();
|
||||
const queryResult = await queryRunner.query(argv._[1]);
|
||||
console.log("Query executed. Result: ", queryResult);
|
||||
await queryRunner.release();
|
||||
await connection.close();
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.error(err);
|
||||
throw err;
|
||||
|
||||
} finally {
|
||||
if (queryRunner)
|
||||
await queryRunner.release();
|
||||
if (connection)
|
||||
await connection.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import {createConnections, createConnection} from "../index";
|
||||
import {Connection} from "../connection/Connection";
|
||||
|
||||
/**
|
||||
* Drops all tables of the database from the given connection.
|
||||
@ -17,21 +18,29 @@ export class SchemaDropCommand {
|
||||
}
|
||||
|
||||
async handler(argv: any) {
|
||||
|
||||
let connection: Connection|undefined = undefined, connections: Connection[] = [];
|
||||
try {
|
||||
process.env.LOGGER_CLI_SCHEMA_SYNC = true;
|
||||
process.env.SKIP_SCHEMA_CREATION = true;
|
||||
if (argv.connection) {
|
||||
const connection = await createConnection(argv.connection);
|
||||
connection = await createConnection(argv.connection);
|
||||
await connection.dropDatabase();
|
||||
await connection.close();
|
||||
} else {
|
||||
const connections = await createConnections();
|
||||
connections = await createConnections();
|
||||
await Promise.all(connections.map(connection => connection.dropDatabase()));
|
||||
await Promise.all(connections.map(connection => connection.close()));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.error(err);
|
||||
throw err;
|
||||
|
||||
} finally {
|
||||
if (connection)
|
||||
await connection.close();
|
||||
|
||||
await Promise.all(connections.map(connection => connection.close()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import {createConnections, createConnection} from "../index";
|
||||
import {Connection} from "../connection/Connection";
|
||||
|
||||
/**
|
||||
* Synchronizes database schema with entities.
|
||||
@ -17,21 +18,29 @@ export class SchemaSyncCommand {
|
||||
}
|
||||
|
||||
async handler(argv: any) {
|
||||
|
||||
let connection: Connection|undefined = undefined, connections: Connection[] = [];
|
||||
try {
|
||||
process.env.LOGGER_CLI_SCHEMA_SYNC = true;
|
||||
process.env.SKIP_SCHEMA_CREATION = true;
|
||||
if (argv.connection) {
|
||||
const connection = await createConnection(argv.connection);
|
||||
connection = await createConnection(argv.connection);
|
||||
await connection.syncSchema(false);
|
||||
await connection.close();
|
||||
} else {
|
||||
const connections = await createConnections();
|
||||
connections = await createConnections();
|
||||
await Promise.all(connections.map(connection => connection.syncSchema(false)));
|
||||
await Promise.all(connections.map(connection => connection.close()));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.error(err);
|
||||
throw err;
|
||||
|
||||
} finally {
|
||||
if (connection)
|
||||
await connection.close();
|
||||
|
||||
await Promise.all(connections.map(connection => connection.close()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,8 +9,6 @@ import {importClassesFromDirectories, importJsonsFromDirectories} from "../util/
|
||||
import {getMetadataArgsStorage, getFromContainer} from "../index";
|
||||
import {EntityMetadataBuilder} from "../metadata-builder/EntityMetadataBuilder";
|
||||
import {DefaultNamingStrategy} from "../naming-strategy/DefaultNamingStrategy";
|
||||
import {EntityMetadataCollection} from "../metadata-args/collection/EntityMetadataCollection";
|
||||
import {NoConnectionForRepositoryError} from "./error/NoConnectionForRepositoryError";
|
||||
import {CannotImportAlreadyConnectedError} from "./error/CannotImportAlreadyConnectedError";
|
||||
import {CannotCloseNotConnectedError} from "./error/CannotCloseNotConnectedError";
|
||||
import {CannotConnectAlreadyConnectedError} from "./error/CannotConnectAlreadyConnectedError";
|
||||
@ -22,7 +20,6 @@ import {EntitySchema} from "../entity-schema/EntitySchema";
|
||||
import {CannotSyncNotConnectedError} from "./error/CannotSyncNotConnectedError";
|
||||
import {CannotUseNamingStrategyNotConnectedError} from "./error/CannotUseNamingStrategyNotConnectedError";
|
||||
import {Broadcaster} from "../subscriber/Broadcaster";
|
||||
import {CannotGetEntityManagerNotConnectedError} from "./error/CannotGetEntityManagerNotConnectedError";
|
||||
import {LazyRelationsWrapper} from "../lazy-loading/LazyRelationsWrapper";
|
||||
import {SpecificRepository} from "../repository/SpecificRepository";
|
||||
import {RepositoryAggregator} from "../repository/RepositoryAggregator";
|
||||
@ -30,6 +27,7 @@ import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {SchemaBuilder} from "../schema-builder/SchemaBuilder";
|
||||
import {Logger} from "../logger/Logger";
|
||||
import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider";
|
||||
import {EntityMetadataNotFound} from "../metadata-args/error/EntityMetadataNotFound";
|
||||
|
||||
/**
|
||||
* Connection is a single database connection to a specific database of a database management system.
|
||||
@ -59,7 +57,7 @@ export class Connection {
|
||||
/**
|
||||
* All entity metadatas that are registered for this connection.
|
||||
*/
|
||||
public readonly entityMetadatas = new EntityMetadataCollection();
|
||||
public readonly entityMetadatas: EntityMetadata[] = [];
|
||||
|
||||
/**
|
||||
* Used to broadcast connection events.
|
||||
@ -147,9 +145,9 @@ export class Connection {
|
||||
* Gets entity manager that allows to perform repository operations with any entity in this connection.
|
||||
*/
|
||||
get entityManager() {
|
||||
if (!this.isConnected)
|
||||
throw new CannotGetEntityManagerNotConnectedError(this.name);
|
||||
|
||||
// if (!this.isConnected)
|
||||
// throw new CannotGetEntityManagerNotConnectedError(this.name);
|
||||
|
||||
return this._entityManager;
|
||||
}
|
||||
|
||||
@ -167,12 +165,21 @@ export class Connection {
|
||||
// connect to the database via its driver
|
||||
await this.driver.connect();
|
||||
|
||||
// build all metadatas registered in the current connection
|
||||
this.buildMetadatas();
|
||||
|
||||
// set connected status for the current connection
|
||||
this._isConnected = true;
|
||||
|
||||
|
||||
// build all metadatas registered in the current connection
|
||||
try {
|
||||
this.buildMetadatas();
|
||||
|
||||
} catch (error) {
|
||||
|
||||
// if for some reason build metadata fail (for example validation error during entity metadata check)
|
||||
// connection needs to be closed
|
||||
await this.close();
|
||||
throw error;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -194,17 +201,7 @@ export class Connection {
|
||||
*/
|
||||
async dropDatabase(): Promise<void> {
|
||||
const queryRunner = await this.driver.createQueryRunner();
|
||||
await queryRunner.beginTransaction();
|
||||
try {
|
||||
await queryRunner.clearDatabase();
|
||||
await queryRunner.commitTransaction();
|
||||
await queryRunner.release();
|
||||
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
await queryRunner.release();
|
||||
throw error;
|
||||
}
|
||||
await queryRunner.clearDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -326,18 +323,27 @@ export class Connection {
|
||||
/**
|
||||
* Gets the entity metadata of the given entity class.
|
||||
*/
|
||||
getMetadata(entity: Function): EntityMetadata;
|
||||
getMetadata(target: Function): EntityMetadata;
|
||||
|
||||
/**
|
||||
* Gets the entity metadata of the given entity name.
|
||||
*/
|
||||
getMetadata(entity: string): EntityMetadata;
|
||||
getMetadata(target: string): EntityMetadata;
|
||||
|
||||
/**
|
||||
* Gets the entity metadata of the given entity class or schema name.
|
||||
*/
|
||||
getMetadata(target: Function|string): EntityMetadata;
|
||||
|
||||
/**
|
||||
Gets entity metadata for the given entity class or schema name.
|
||||
*/
|
||||
getMetadata(entity: Function|string): EntityMetadata {
|
||||
return this.entityMetadatas.findByTarget(entity);
|
||||
getMetadata(target: Function|string): EntityMetadata {
|
||||
const metadata = this.entityMetadatas.find(metadata => metadata.target === target || (typeof target === "string" && metadata.targetName === target));
|
||||
if (!metadata)
|
||||
throw new EntityMetadataNotFound(target);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -350,6 +356,11 @@ export class Connection {
|
||||
*/
|
||||
getRepository<Entity>(entityName: string): Repository<Entity>;
|
||||
|
||||
/**
|
||||
* Gets repository for the given entity name.
|
||||
*/
|
||||
getRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): Repository<Entity>;
|
||||
|
||||
/**
|
||||
* Gets repository for the given entity class or name.
|
||||
*/
|
||||
@ -395,6 +406,12 @@ export class Connection {
|
||||
*/
|
||||
getSpecificRepository<Entity>(entityName: string): SpecificRepository<Entity>;
|
||||
|
||||
/**
|
||||
* Gets specific repository for the given entity class or name.
|
||||
* SpecificRepository is a special repository that contains specific and non standard repository methods.
|
||||
*/
|
||||
getSpecificRepository<Entity>(entityClassOrName: ObjectType<Entity>|string): SpecificRepository<Entity>;
|
||||
|
||||
/**
|
||||
* Gets specific repository for the given entity class or name.
|
||||
* SpecificRepository is a special repository that contains specific and non standard repository methods.
|
||||
@ -421,13 +438,13 @@ export class Connection {
|
||||
* Finds repository aggregator of the given entity class or name.
|
||||
*/
|
||||
protected findRepositoryAggregator(entityClassOrName: ObjectType<any>|string): RepositoryAggregator {
|
||||
if (!this.isConnected)
|
||||
throw new NoConnectionForRepositoryError(this.name);
|
||||
// if (!this.isConnected)
|
||||
// throw new NoConnectionForRepositoryError(this.name);
|
||||
|
||||
if (!this.entityMetadatas.hasTarget(entityClassOrName))
|
||||
if (!this.entityMetadatas.find(metadata => metadata.target === entityClassOrName || (typeof entityClassOrName === "string" && metadata.targetName === entityClassOrName)))
|
||||
throw new RepositoryNotFoundError(this.name, entityClassOrName);
|
||||
|
||||
const metadata = this.entityMetadatas.findByTarget(entityClassOrName);
|
||||
const metadata = this.getMetadata(entityClassOrName);
|
||||
const repositoryAggregator = this.repositoryAggregators.find(repositoryAggregate => repositoryAggregate.metadata === metadata);
|
||||
if (!repositoryAggregator)
|
||||
throw new RepositoryNotFoundError(this.name, entityClassOrName);
|
||||
@ -453,6 +470,7 @@ export class Connection {
|
||||
getMetadataArgsStorage()
|
||||
.entitySubscribers
|
||||
.filterByTargets(this.subscriberClasses)
|
||||
.toArray()
|
||||
.map(metadata => getFromContainer(metadata.target))
|
||||
.forEach(subscriber => this.entitySubscribers.push(subscriber));
|
||||
}
|
||||
@ -462,9 +480,10 @@ export class Connection {
|
||||
getMetadataArgsStorage()
|
||||
.entityListeners
|
||||
.filterByTargets(this.entityClasses)
|
||||
.toArray()
|
||||
.forEach(metadata => this.entityListeners.push(new EntityListenerMetadata(metadata)));
|
||||
}
|
||||
|
||||
|
||||
// build entity metadatas from metadata args storage (collected from decorators)
|
||||
if (this.entityClasses && this.entityClasses.length) {
|
||||
getFromContainer(EntityMetadataBuilder)
|
||||
@ -490,15 +509,16 @@ export class Connection {
|
||||
* Creates a naming strategy to be used for this connection.
|
||||
*/
|
||||
protected createNamingStrategy(): NamingStrategyInterface {
|
||||
|
||||
|
||||
// if naming strategies are not loaded, or used naming strategy is not set then use default naming strategy
|
||||
if (!this.namingStrategyClasses || !this.namingStrategyClasses.length || !this.usedNamingStrategy)
|
||||
return getFromContainer(DefaultNamingStrategy);
|
||||
|
||||
|
||||
// try to find used naming strategy in the list of loaded naming strategies
|
||||
const namingMetadata = getMetadataArgsStorage()
|
||||
.namingStrategies
|
||||
.filterByTargets(this.namingStrategyClasses)
|
||||
.toArray()
|
||||
.find(strategy => {
|
||||
if (typeof this.usedNamingStrategy === "string") {
|
||||
return strategy.name === this.usedNamingStrategy;
|
||||
@ -506,7 +526,7 @@ export class Connection {
|
||||
return strategy.target === this.usedNamingStrategy;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// throw an error if not found
|
||||
if (!namingMetadata)
|
||||
throw new NamingStrategyNotFoundError(this.usedNamingStrategy, this.name);
|
||||
@ -526,7 +546,7 @@ export class Connection {
|
||||
* Creates a new entity broadcaster using in this connection.
|
||||
*/
|
||||
protected createBroadcaster() {
|
||||
return new Broadcaster(this.entityMetadatas, this.entitySubscribers, this.entityListeners);
|
||||
return new Broadcaster(this, this.entitySubscribers, this.entityListeners);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import * as fs from "fs";
|
||||
import {Connection} from "./Connection";
|
||||
import {ConnectionNotFoundError} from "./error/ConnectionNotFoundError";
|
||||
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
|
||||
@ -14,6 +13,8 @@ import {OracleDriver} from "../driver/oracle/OracleDriver";
|
||||
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
|
||||
import {OrmUtils} from "../util/OrmUtils";
|
||||
import {CannotDetermineConnectionOptionsError} from "./error/CannotDetermineConnectionOptionsError";
|
||||
import {PlatformTools} from "../platform/PlatformTools";
|
||||
import {WebsqlDriver} from "../driver/websql/WebsqlDriver";
|
||||
|
||||
/**
|
||||
* ConnectionManager is used to store and manage all these different connections.
|
||||
@ -29,7 +30,7 @@ export class ConnectionManager {
|
||||
* List of connections registered in this connection manager.
|
||||
*/
|
||||
protected connections: Connection[] = [];
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
@ -118,7 +119,7 @@ export class ConnectionManager {
|
||||
* it will try to create connection from environment variables.
|
||||
* There are several environment variables you can set:
|
||||
*
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_URL - database connection url. Should be a string.
|
||||
* - TYPEORM_HOST - database host. Should be a string.
|
||||
* - TYPEORM_PORT - database access port. Should be a number.
|
||||
@ -186,7 +187,7 @@ export class ConnectionManager {
|
||||
* it will try to create connection from environment variables.
|
||||
* There are several environment variables you can set:
|
||||
*
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_URL - database connection url. Should be a string.
|
||||
* - TYPEORM_HOST - database host. Should be a string.
|
||||
* - TYPEORM_PORT - database access port. Should be a number.
|
||||
@ -254,18 +255,18 @@ export class ConnectionManager {
|
||||
* Checks if ormconfig.json exists.
|
||||
*/
|
||||
protected hasOrmConfigurationFile(): boolean {
|
||||
const path = require("app-root-path").path + "/ormconfig.json";
|
||||
if (!fs.existsSync(path))
|
||||
const path = PlatformTools.load("app-root-path").path + "/ormconfig.json";
|
||||
if (!PlatformTools.fileExist(path))
|
||||
return false;
|
||||
|
||||
const configuration: ConnectionOptions[]|ConnectionOptions = require(path);
|
||||
const configuration: ConnectionOptions[]|ConnectionOptions = PlatformTools.load(path);
|
||||
if (configuration instanceof Array) {
|
||||
return configuration
|
||||
.filter(options => !options.environment || options.environment === process.env.NODE_ENV)
|
||||
.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV"))
|
||||
.length > 0;
|
||||
|
||||
} else if (configuration instanceof Object) {
|
||||
if (configuration.environment && configuration.environment !== process.env.NODE_ENV)
|
||||
if (configuration.environment && configuration.environment !== PlatformTools.getEnvVariable("NODE_ENV"))
|
||||
return false;
|
||||
|
||||
return Object.keys(configuration).length > 0;
|
||||
@ -278,14 +279,14 @@ export class ConnectionManager {
|
||||
* Checks if there is a default connection in the ormconfig.json file.
|
||||
*/
|
||||
protected hasDefaultConfigurationInConfigurationFile(): boolean {
|
||||
const path = require("app-root-path").path + "/ormconfig.json";
|
||||
if (!fs.existsSync(path))
|
||||
const path = PlatformTools.load("app-root-path").path + "/ormconfig.json";
|
||||
if (!PlatformTools.fileExist(path))
|
||||
return false;
|
||||
|
||||
const configuration: ConnectionOptions[]|ConnectionOptions = require(path);
|
||||
const configuration: ConnectionOptions[]|ConnectionOptions = PlatformTools.load(path);
|
||||
if (configuration instanceof Array) {
|
||||
return !!configuration
|
||||
.filter(options => !options.environment || options.environment === process.env.NODE_ENV)
|
||||
.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV"))
|
||||
.find(config => !!config.name || config.name === "default");
|
||||
|
||||
} else if (configuration instanceof Object) {
|
||||
@ -293,7 +294,7 @@ export class ConnectionManager {
|
||||
configuration.name !== "default")
|
||||
return false;
|
||||
|
||||
if (configuration.environment && configuration.environment !== process.env.NODE_ENV)
|
||||
if (configuration.environment && configuration.environment !== PlatformTools.getEnvVariable("NODE_ENV"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@ -306,7 +307,7 @@ export class ConnectionManager {
|
||||
* Checks if environment variables contains connection options.
|
||||
*/
|
||||
protected hasDefaultConfigurationInEnvironmentVariables(): boolean {
|
||||
return !!process.env.TYPEORM_DRIVER_TYPE;
|
||||
return !!PlatformTools.getEnvVariable("TYPEORM_DRIVER_TYPE");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,28 +316,28 @@ export class ConnectionManager {
|
||||
protected async createFromEnvAndConnect(): Promise<Connection> {
|
||||
return this.createAndConnectByConnectionOptions({
|
||||
driver: {
|
||||
type: process.env.TYPEORM_DRIVER_TYPE,
|
||||
url: process.env.TYPEORM_URL,
|
||||
host: process.env.TYPEORM_HOST,
|
||||
port: process.env.TYPEORM_PORT,
|
||||
username: process.env.TYPEORM_USERNAME,
|
||||
password: process.env.TYPEORM_PASSWORD,
|
||||
database: process.env.TYPEORM_DATABASE,
|
||||
sid: process.env.TYPEORM_SID,
|
||||
storage: process.env.TYPEORM_STORAGE,
|
||||
usePool: process.env.TYPEORM_USE_POOL !== undefined ? OrmUtils.toBoolean(process.env.TYPEORM_USE_POOL) : undefined, // special check for defined is required here
|
||||
extra: process.env.TYPEORM_DRIVER_EXTRA ? JSON.parse(process.env.TYPEORM_DRIVER_EXTRA) : undefined
|
||||
type: PlatformTools.getEnvVariable("TYPEORM_DRIVER_TYPE"),
|
||||
url: PlatformTools.getEnvVariable("TYPEORM_URL"),
|
||||
host: PlatformTools.getEnvVariable("TYPEORM_HOST"),
|
||||
port: PlatformTools.getEnvVariable("TYPEORM_PORT"),
|
||||
username: PlatformTools.getEnvVariable("TYPEORM_USERNAME"),
|
||||
password: PlatformTools.getEnvVariable("TYPEORM_PASSWORD"),
|
||||
database: PlatformTools.getEnvVariable("TYPEORM_DATABASE"),
|
||||
sid: PlatformTools.getEnvVariable("TYPEORM_SID"),
|
||||
storage: PlatformTools.getEnvVariable("TYPEORM_STORAGE"),
|
||||
usePool: PlatformTools.getEnvVariable("TYPEORM_USE_POOL") !== undefined ? OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_USE_POOL")) : undefined, // special check for defined is required here
|
||||
extra: PlatformTools.getEnvVariable("TYPEORM_DRIVER_EXTRA") ? JSON.parse(PlatformTools.getEnvVariable("TYPEORM_DRIVER_EXTRA")) : undefined
|
||||
},
|
||||
autoSchemaSync: OrmUtils.toBoolean(process.env.TYPEORM_AUTO_SCHEMA_SYNC),
|
||||
entities: process.env.TYPEORM_ENTITIES ? process.env.TYPEORM_ENTITIES.split(",") : [],
|
||||
subscribers: process.env.TYPEORM_SUBSCRIBERS ? process.env.TYPEORM_SUBSCRIBERS.split(",") : [],
|
||||
entitySchemas: process.env.TYPEORM_ENTITY_SCHEMAS ? process.env.TYPEORM_ENTITY_SCHEMAS.split(",") : [],
|
||||
namingStrategies: process.env.TYPEORM_NAMING_STRATEGIES ? process.env.TYPEORM_NAMING_STRATEGIES.split(",") : [],
|
||||
usedNamingStrategy: process.env.TYPEORM_USED_NAMING_STRATEGY,
|
||||
autoSchemaSync: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_AUTO_SCHEMA_SYNC")),
|
||||
entities: PlatformTools.getEnvVariable("TYPEORM_ENTITIES") ? PlatformTools.getEnvVariable("TYPEORM_ENTITIES").split(",") : [],
|
||||
subscribers: PlatformTools.getEnvVariable("TYPEORM_SUBSCRIBERS") ? PlatformTools.getEnvVariable("TYPEORM_SUBSCRIBERS").split(",") : [],
|
||||
entitySchemas: PlatformTools.getEnvVariable("TYPEORM_ENTITY_SCHEMAS") ? PlatformTools.getEnvVariable("TYPEORM_ENTITY_SCHEMAS").split(",") : [],
|
||||
namingStrategies: PlatformTools.getEnvVariable("TYPEORM_NAMING_STRATEGIES") ? PlatformTools.getEnvVariable("TYPEORM_NAMING_STRATEGIES").split(",") : [],
|
||||
usedNamingStrategy: PlatformTools.getEnvVariable("TYPEORM_USED_NAMING_STRATEGY"),
|
||||
logging: {
|
||||
logQueries: OrmUtils.toBoolean(process.env.TYPEORM_LOGGING_QUERIES),
|
||||
logFailedQueryError: OrmUtils.toBoolean(process.env.TYPEORM_LOGGING_FAILED_QUERIES),
|
||||
logOnlyFailedQueries: OrmUtils.toBoolean(process.env.TYPEORM_LOGGING_ONLY_FAILED_QUERIES),
|
||||
logQueries: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_QUERIES")),
|
||||
logFailedQueryError: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_FAILED_QUERIES")),
|
||||
logOnlyFailedQueries: OrmUtils.toBoolean(PlatformTools.getEnvVariable("TYPEORM_LOGGING_ONLY_FAILED_QUERIES")),
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -349,12 +350,12 @@ export class ConnectionManager {
|
||||
* If path is not given, then ormconfig.json file will be searched near node_modules directory.
|
||||
*/
|
||||
protected async createFromConfigAndConnectToAll(path?: string): Promise<Connection[]> {
|
||||
const optionsArray: ConnectionOptions[] = require(path || (require("app-root-path").path + "/ormconfig.json"));
|
||||
const optionsArray: ConnectionOptions[] = PlatformTools.load(path || (PlatformTools.load("app-root-path").path + "/ormconfig.json"));
|
||||
if (!optionsArray)
|
||||
throw new Error(`Configuration ${path || "ormconfig.json"} was not found. Add connection configuration inside ormconfig.json file.`);
|
||||
|
||||
const promises = optionsArray
|
||||
.filter(options => !options.environment || options.environment === process.env.NODE_ENV) // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
|
||||
.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV")) // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
|
||||
.map(options => this.createAndConnectByConnectionOptions(options));
|
||||
|
||||
return Promise.all(promises);
|
||||
@ -367,15 +368,15 @@ export class ConnectionManager {
|
||||
* If path is not given, then ormconfig.json file will be searched near node_modules directory.
|
||||
*/
|
||||
protected async createFromConfigAndConnect(connectionName: string, path?: string): Promise<Connection> {
|
||||
const optionsArray: ConnectionOptions[] = require(path || (require("app-root-path").path + "/ormconfig.json"));
|
||||
const optionsArray: ConnectionOptions[] = PlatformTools.load(path || (PlatformTools.load("app-root-path").path + "/ormconfig.json"));
|
||||
if (!optionsArray)
|
||||
throw new Error(`Configuration ${path || "ormconfig.json"} was not found. Add connection configuration inside ormconfig.json file.`);
|
||||
|
||||
const environmentLessOptions = optionsArray.filter(options => (options.name || "default") === connectionName);
|
||||
const options = environmentLessOptions.filter(options => !options.environment || options.environment === process.env.NODE_ENV); // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
|
||||
const options = environmentLessOptions.filter(options => !options.environment || options.environment === PlatformTools.getEnvVariable("NODE_ENV")); // skip connection creation if environment is set in the options, and its not equal to the value in the NODE_ENV variable
|
||||
|
||||
if (!options.length)
|
||||
throw new Error(`Connection "${connectionName}" ${process.env.NODE_ENV ? "for the environment " + process.env.NODE_ENV + " " : ""}was not found in the json configuration file.` +
|
||||
throw new Error(`Connection "${connectionName}" ${PlatformTools.getEnvVariable("NODE_ENV") ? "for the environment " + PlatformTools.getEnvVariable("NODE_ENV") + " " : ""}was not found in the json configuration file.` +
|
||||
(environmentLessOptions.length ? ` However there are such configurations for other environments: ${environmentLessOptions.map(options => options.environment).join(", ")}.` : ""));
|
||||
|
||||
return this.createAndConnectByConnectionOptions(options[0]);
|
||||
@ -391,11 +392,11 @@ export class ConnectionManager {
|
||||
await connection.connect();
|
||||
|
||||
// if option is set - drop schema once connection is done
|
||||
if (options.dropSchemaOnConnection && !process.env.SKIP_SCHEMA_CREATION)
|
||||
if (options.dropSchemaOnConnection && !PlatformTools.getEnvVariable("SKIP_SCHEMA_CREATION"))
|
||||
await connection.dropDatabase();
|
||||
|
||||
// if option is set - automatically synchronize a schema
|
||||
if (options.autoSchemaSync && !process.env.SKIP_SCHEMA_CREATION)
|
||||
if (options.autoSchemaSync && !PlatformTools.getEnvVariable("SKIP_SCHEMA_CREATION"))
|
||||
await connection.syncSchema();
|
||||
|
||||
return connection;
|
||||
@ -417,9 +418,7 @@ export class ConnectionManager {
|
||||
protected createDriver(options: DriverOptions, logger: Logger): Driver {
|
||||
switch (options.type) {
|
||||
case "mysql":
|
||||
return new MysqlDriver(options, logger, undefined, "mysql");
|
||||
case "mysql2":
|
||||
return new MysqlDriver(options, logger, undefined, "mysql2");
|
||||
return new MysqlDriver(options, logger, undefined);
|
||||
case "postgres":
|
||||
return new PostgresDriver(options, logger);
|
||||
case "mariadb":
|
||||
@ -430,6 +429,8 @@ export class ConnectionManager {
|
||||
return new OracleDriver(options, logger);
|
||||
case "mssql":
|
||||
return new SqlServerDriver(options, logger);
|
||||
case "websql":
|
||||
return new WebsqlDriver(options, logger);
|
||||
default:
|
||||
throw new MissingDriverError(options.type);
|
||||
}
|
||||
|
||||
@ -8,9 +8,9 @@ export class CannotDetermineConnectionOptionsError extends Error {
|
||||
constructor() {
|
||||
super();
|
||||
this.message = `Cannot create connection, because connection options are missing. ` +
|
||||
`You either need to explicitly pass connection options, either create a ormconfig.json with with connection options ` +
|
||||
`and "default" connection name, either to set proper environment variables. Also, if you are using environment-specific ` +
|
||||
`configurations in your ormconfig.json make sure your are running under correct NODE_ENV.`;
|
||||
`You either need to explicitly pass connection options, either create a ormconfig.json with with connection options ` +
|
||||
`and "default" connection name, either to set proper environment variables. Also, if you are using environment-specific ` +
|
||||
`configurations in your ormconfig.json make sure your are running under correct NODE_ENV.`;
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ export class MissingDriverError extends Error {
|
||||
|
||||
constructor(driverType: string) {
|
||||
super();
|
||||
this.message = `Wrong driver ${driverType} given. Supported drivers are: "mysql", "mysql2", "postgres", "mssql", "oracle", "mariadb", "sqlite".`;
|
||||
this.message = `Wrong driver ${driverType} given. Supported drivers are: "mysql", "postgres", "mssql", "oracle", "mariadb", "sqlite".`;
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ export class NoConnectionForRepositoryError extends Error {
|
||||
constructor(connectionName: string) {
|
||||
super();
|
||||
this.message = `Cannot get a Repository for "${connectionName} connection, because connection with the database ` +
|
||||
`is not established yet. Call connection#connect method to establish connection.`;
|
||||
`is not established yet. Call connection#connect method to establish connection.`;
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ export class RepositoryNotFoundError extends Error {
|
||||
constructor(connectionName: string, entityClass: Function|string) {
|
||||
super();
|
||||
const targetName = typeof entityClass === "function" && (<any> entityClass).name ? (<any> entityClass).name : entityClass;
|
||||
this.message = `No repository for "${targetName}" was found. Looks like this entity is not registered in ` +
|
||||
this.message = `No repository for "${targetName}" was found. Looks like this entity is not registered in ` +
|
||||
`current "${connectionName}" connection?`;
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ export interface UseContainerOptions {
|
||||
*/
|
||||
export const defaultContainer: { get<T>(someClass: { new (...args: any[]): T }|Function): T } = new (class {
|
||||
private instances: { type: Function, object: any }[] = [];
|
||||
|
||||
get<T>(someClass: { new (...args: any[]): T }): T {
|
||||
let instance = this.instances.find(instance => instance.type === someClass);
|
||||
if (!instance) {
|
||||
|
||||
@ -6,7 +6,7 @@ import {ColumnType, ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
|
||||
|
||||
/**
|
||||
* Column decorator is used to mark a specific class property as a table column. Only properties decorated with this
|
||||
* Column decorator is used to mark a specific class property as a table column. Only properties decorated with this
|
||||
* decorator will be persisted to the database when entity be saved.
|
||||
*/
|
||||
export function Column(): Function;
|
||||
@ -41,7 +41,7 @@ export function Column(typeOrOptions?: ColumnType|ColumnOptions, options?: Colum
|
||||
options = <ColumnOptions> typeOrOptions;
|
||||
}
|
||||
return function (object: Object, propertyName: string) {
|
||||
|
||||
|
||||
// todo: need to store not string type, but original type instead? (like in relation metadata)
|
||||
const reflectedType = ColumnTypes.typeToString((Reflect as any).getMetadata("design:type", object, propertyName));
|
||||
|
||||
@ -51,7 +51,7 @@ export function Column(typeOrOptions?: ColumnType|ColumnOptions, options?: Colum
|
||||
|
||||
// if column options are not given then create a new empty options
|
||||
if (!options) options = {} as ColumnOptions;
|
||||
|
||||
|
||||
// check if there is no type in column options then set type from first function argument, or guessed one
|
||||
if (!options.type)
|
||||
options = Object.assign({ type: type } as ColumnOptions, options);
|
||||
|
||||
@ -44,7 +44,7 @@ export function PrimaryColumn(typeOrOptions?: ColumnType|ColumnOptions, options?
|
||||
|
||||
// check if there is no type in column options then set type from first function argument, or guessed one
|
||||
if (!options.type)
|
||||
options = Object.assign({ type: type } as ColumnOptions, options);
|
||||
options = Object.assign({type: type} as ColumnOptions, options);
|
||||
|
||||
// if we still don't have a type then we need to give error to user that type is required
|
||||
if (!options.type)
|
||||
|
||||
@ -20,7 +20,7 @@ export function PrimaryGeneratedColumn(options?: ColumnOptions): Function {
|
||||
|
||||
// check if there is no type in column options then set the int type - by default for auto generated column
|
||||
if (!options.type)
|
||||
options = Object.assign({ type: "int" } as ColumnOptions, options);
|
||||
options = Object.assign({type: "int"} as ColumnOptions, options);
|
||||
|
||||
// check if column is not nullable, because we cannot allow a primary key to be nullable
|
||||
if (options.nullable)
|
||||
|
||||
@ -19,7 +19,7 @@ export interface ColumnOptions {
|
||||
* Column type's length. Used only on some column types.
|
||||
* For example type = "string" and length = "100" means that ORM will create a column with type varchar(100).
|
||||
*/
|
||||
readonly length?: string;
|
||||
readonly length?: string|number;
|
||||
|
||||
/**
|
||||
* Indicates if this column is PRIMARY.
|
||||
@ -72,5 +72,19 @@ export interface ColumnOptions {
|
||||
* Note that timezone option is not supported by all databases (only postgres for now).
|
||||
*/
|
||||
readonly timezone?: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if date object must be stored in given date's timezone.
|
||||
* By default date is saved in UTC timezone.
|
||||
* Works only with "datetime" columns.
|
||||
*/
|
||||
readonly storeInLocalTimezone?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if date object must be loaded and set to the Date object in local timezone.
|
||||
* By default date is loaded in UTC timezone.
|
||||
* Works only with "datetime" columns.
|
||||
*/
|
||||
readonly loadInLocalTimezone?: boolean;
|
||||
|
||||
}
|
||||
|
||||
@ -21,5 +21,5 @@ export interface TableOptions {
|
||||
* Specifies if this table will be skipped during schema synchronization.
|
||||
*/
|
||||
readonly skipSchemaSync?: boolean;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ export function ManyToMany<T>(typeFunction: (type?: any) => ObjectType<T>,
|
||||
|
||||
return function (object: Object, propertyName: string) {
|
||||
if (!options) options = {} as RelationOptions;
|
||||
|
||||
|
||||
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
|
||||
const isLazy = reflectedType && typeof reflectedType.name === "string" && reflectedType.name.toLowerCase() === "promise";
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
|
||||
|
||||
/**
|
||||
* Many-to-one relation allows to create type of relation when Entity1 can have single instance of Entity2, but
|
||||
* Entity2 can have a multiple instances of Entity1. Entity1 is an owner of the relationship, and storages Entity2 id
|
||||
* Entity2 can have a multiple instances of Entity1. Entity1 is an owner of the relationship, and storages Entity2 id
|
||||
* on its own side.
|
||||
*/
|
||||
export function ManyToOne<T>(typeFunction: (type?: any) => ObjectType<T>, options?: RelationOptions): Function;
|
||||
@ -26,8 +26,8 @@ export function ManyToOne<T>(typeFunction: (type?: any) => ObjectType<T>,
|
||||
* on its own side.
|
||||
*/
|
||||
export function ManyToOne<T>(typeFunction: (type?: any) => ObjectType<T>,
|
||||
inverseSideOrOptions?: string|((object: T) => any)|RelationOptions,
|
||||
options?: RelationOptions): Function {
|
||||
inverseSideOrOptions?: string|((object: T) => any)|RelationOptions,
|
||||
options?: RelationOptions): Function {
|
||||
let inverseSideProperty: string|((object: T) => any);
|
||||
if (typeof inverseSideOrOptions === "object") {
|
||||
options = <RelationOptions> inverseSideOrOptions;
|
||||
|
||||
@ -7,7 +7,7 @@ import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
|
||||
// todo: make decorators which use inverse side string separate
|
||||
|
||||
/**
|
||||
* One-to-many relation allows to create type of relation when Entity2 can have multiple instances of Entity1.
|
||||
* One-to-many relation allows to create type of relation when Entity2 can have multiple instances of Entity1.
|
||||
* Entity1 have only one Entity2. Entity1 is an owner of the relationship, and storages Entity2 id on its own side.
|
||||
*/
|
||||
// export function OneToMany<T>(typeFunction: (type?: any) => ConstructorFunction<T>, options?: RelationOptions): Function;
|
||||
@ -33,7 +33,7 @@ export function OneToMany<T>(typeFunction: (type?: any) => ObjectType<T>,
|
||||
} else {
|
||||
inverseSideProperty = <string|((object: T) => any)> inverseSideOrOptions;
|
||||
}
|
||||
|
||||
|
||||
// todo: for OneToMany having inverse side is required because otherwise its not possible to do anything (selections/persisment)
|
||||
// todo: validate it somehow?
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ export function TreeChildren(options?: RelationOptions): Function {
|
||||
|
||||
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
|
||||
const isLazy = reflectedType && typeof reflectedType.name === "string" && reflectedType.name.toLowerCase() === "promise";
|
||||
|
||||
|
||||
// add one-to-many relation for this
|
||||
const args: RelationMetadataArgs = {
|
||||
isTreeChildren: true,
|
||||
|
||||
@ -12,7 +12,7 @@ export function TreeParent(options?: RelationOptions): Function {
|
||||
|
||||
const reflectedType = (Reflect as any).getMetadata("design:type", object, propertyName);
|
||||
const isLazy = reflectedType && typeof reflectedType.name === "string" && reflectedType.name.toLowerCase() === "promise";
|
||||
|
||||
|
||||
const args: RelationMetadataArgs = {
|
||||
isTreeParent: true,
|
||||
target: object.constructor,
|
||||
|
||||
207
src/decorators-shim.ts
Normal file
207
src/decorators-shim.ts
Normal file
@ -0,0 +1,207 @@
|
||||
// columns
|
||||
|
||||
/* export */
|
||||
function Column(typeOrOptions?: any, options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function CreateDateColumn(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function DiscriminatorColumn(discriminatorOptions: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function PrimaryColumn(typeOrOptions?: any, options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function PrimaryGeneratedColumn(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function UpdateDateColumn(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function VersionColumn(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
// listeners
|
||||
|
||||
/* export */
|
||||
function AfterInsert(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function AfterLoad(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function AfterRemove(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function AfterUpdate(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function BeforeInsert(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function BeforeRemove(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function BeforeUpdate(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function EventSubscriber(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
// relations
|
||||
|
||||
/* export */
|
||||
function JoinColumn(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function JoinTable(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function ManyToMany<T>(typeFunction: any, inverseSideOrOptions?: any, options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function ManyToOne<T>(typeFunction: any, inverseSideOrOptions?: any, options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function OneToMany<T>(typeFunction: any, inverseSideOrOptions?: any, options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function OneToOne<T>(typeFunction: any, inverseSideOrOptions?: any, options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function RelationCount<T>(relation: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function RelationId<T>(relation: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
// tables
|
||||
|
||||
/* export */
|
||||
function AbstractTable(): Function {
|
||||
return function (object: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function ClassTableChild(tableName?: any, options?: any): Function {
|
||||
return function (object: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function ClosureTable(name?: any, options?: any): Function {
|
||||
return function (object: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function EmbeddableTable(): Function {
|
||||
return function (object: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function SingleTableChild(): Function {
|
||||
return function (object: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function Table(name?: any, options?: any): Function {
|
||||
return function (object: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function TableInheritance(type?: any): Function {
|
||||
return function (object: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
// tree
|
||||
|
||||
/* export */
|
||||
function TreeChildren(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function TreeLevelColumn(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/* export */
|
||||
function TreeParent(options?: any): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
};
|
||||
}
|
||||
@ -2,7 +2,6 @@ import {DriverOptions} from "./DriverOptions";
|
||||
import {QueryRunner} from "../query-runner/QueryRunner";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
import {ColumnType} from "../metadata/types/ColumnTypes";
|
||||
|
||||
/**
|
||||
* Driver organizes TypeORM communication with specific database management system.
|
||||
@ -65,7 +64,7 @@ export interface Driver {
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, type: ColumnType): any;
|
||||
prepareHydratedValue(value: any, type: ColumnMetadata): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type.
|
||||
|
||||
@ -6,7 +6,7 @@ export interface DriverOptions {
|
||||
/**
|
||||
* Database type. This value is required.
|
||||
*/
|
||||
readonly type: "mysql"|"mysql2"|"postgres"|"mariadb"|"sqlite"|"oracle"|"mssql";
|
||||
readonly type: "mysql"|"postgres"|"mariadb"|"sqlite"|"oracle"|"mssql"|"websql";
|
||||
|
||||
/**
|
||||
* Connection url to where perform connection to.
|
||||
|
||||
@ -50,16 +50,21 @@ export class DriverUtils {
|
||||
* Extracts connection data from the connection url.
|
||||
*/
|
||||
private static parseConnectionUrl(url: string) {
|
||||
const urlParser = require("url");
|
||||
const params = urlParser.parse(url);
|
||||
const auth = params.auth.split(":");
|
||||
const firstSlashes = url.indexOf("//");
|
||||
const preBase = url.substr(firstSlashes + 2);
|
||||
const secondSlash = preBase.indexOf("/");
|
||||
const base = (secondSlash !== -1) ? preBase.substr(0, secondSlash) : preBase;
|
||||
const afterBase = (secondSlash !== -1) ? preBase.substr(secondSlash) + 1 : undefined;
|
||||
const [usernameAndPassword, hostAndPort] = base.split("@");
|
||||
const [username, password] = usernameAndPassword.split(":");
|
||||
const [host, port] = hostAndPort.split(":");
|
||||
|
||||
return {
|
||||
host: params.hostname,
|
||||
username: auth[0],
|
||||
password: auth[1],
|
||||
port: parseInt(params.port),
|
||||
database: params.pathname.split("/")[1]
|
||||
host: host,
|
||||
username: username,
|
||||
password: password,
|
||||
port: port ? parseInt(port) : undefined,
|
||||
database: afterBase ? afterBase.split("/")[0] : undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,16 +3,16 @@ import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
|
||||
import {DriverOptions} from "../DriverOptions";
|
||||
import {DatabaseConnection} from "../DatabaseConnection";
|
||||
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
|
||||
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
|
||||
import {DriverUtils} from "../DriverUtils";
|
||||
import {Logger} from "../../logger/Logger";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {MysqlQueryRunner} from "./MysqlQueryRunner";
|
||||
import {ColumnTypes, ColumnType} from "../../metadata/types/ColumnTypes";
|
||||
import * as moment from "moment";
|
||||
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
|
||||
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
|
||||
import {PlatformTools} from "../../platform/PlatformTools";
|
||||
|
||||
/**
|
||||
* Organizes communication with MySQL DBMS.
|
||||
@ -57,21 +57,15 @@ export class MysqlDriver implements Driver {
|
||||
*/
|
||||
protected logger: Logger;
|
||||
|
||||
/**
|
||||
* Driver type's version. node-mysql and mysql2 are supported.
|
||||
*/
|
||||
protected version: "mysql"|"mysql2" = "mysql";
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(options: DriverOptions, logger: Logger, mysql?: any, mysqlVersion: "mysql"|"mysql2" = "mysql") {
|
||||
constructor(options: DriverOptions, logger: Logger, mysql?: any) {
|
||||
|
||||
this.options = DriverUtils.buildDriverOptions(options);
|
||||
this.logger = logger;
|
||||
this.mysql = mysql;
|
||||
this.version = mysqlVersion;
|
||||
|
||||
// validate options to make sure everything is set
|
||||
if (!this.options.host)
|
||||
@ -130,7 +124,7 @@ export class MysqlDriver implements Driver {
|
||||
*/
|
||||
disconnect(): Promise<void> {
|
||||
if (!this.databaseConnection && !this.pool)
|
||||
throw new ConnectionIsNotSetError(this.version);
|
||||
throw new ConnectionIsNotSetError("mysql");
|
||||
|
||||
return new Promise<void>((ok, fail) => {
|
||||
const handler = (err: any) => err ? fail(err) : ok();
|
||||
@ -155,7 +149,7 @@ export class MysqlDriver implements Driver {
|
||||
*/
|
||||
async createQueryRunner(): Promise<QueryRunner> {
|
||||
if (!this.databaseConnection && !this.pool)
|
||||
return Promise.reject(new ConnectionIsNotSetError(this.version));
|
||||
return Promise.reject(new ConnectionIsNotSetError("mysql"));
|
||||
|
||||
const databaseConnection = await this.retrieveDatabaseConnection();
|
||||
return new MysqlQueryRunner(databaseConnection, this, this.logger);
|
||||
@ -212,72 +206,60 @@ export class MysqlDriver implements Driver {
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type and metadata.
|
||||
*/
|
||||
preparePersistentValue(value: any, column: ColumnMetadata): any {
|
||||
switch (column.type) {
|
||||
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (value === null || value === undefined)
|
||||
return null;
|
||||
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value === true ? 1 : 0;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
if (moment(value).isValid())
|
||||
return moment(value).format("YYYY-MM-DD");
|
||||
else return "0000-00-00";
|
||||
return DataTransformationUtils.mixedDateToDateString(value);
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
if (moment(value).isValid())
|
||||
return moment(value).format("HH:mm:ss");
|
||||
else return "00:00:00";
|
||||
return DataTransformationUtils.mixedDateToTimeString(value);
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
if (moment(value).isValid())
|
||||
return moment(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
else return "0000-00-00 00:00:00";
|
||||
if (columnMetadata.storeInLocalTimezone) {
|
||||
return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
} else {
|
||||
return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
}
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.stringify(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as any[])
|
||||
.map(i => String(i))
|
||||
.join(",");
|
||||
return DataTransformationUtils.simpleArrayToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, type: ColumnType): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type.
|
||||
*/
|
||||
prepareHydratedValue(value: any, column: ColumnMetadata): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnOrColumnType: ColumnMetadata|ColumnType): any {
|
||||
const type = columnOrColumnType instanceof ColumnMetadata ? columnOrColumnType.type : columnOrColumnType;
|
||||
switch (type) {
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value ? true : false;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD").toDate();
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value, "HH:mm:ss").toDate();
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
|
||||
// case ColumnTypes.DATETIME:
|
||||
// if (value instanceof Date)
|
||||
// return value;
|
||||
//
|
||||
// if (columnMetadata.loadInLocalTimezone) {
|
||||
// return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
// } else {
|
||||
// return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
// }
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.parse(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as string).split(",");
|
||||
return DataTransformationUtils.stringToSimpleArray(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -324,20 +306,23 @@ export class MysqlDriver implements Driver {
|
||||
if (this.databaseConnection)
|
||||
return Promise.resolve(this.databaseConnection);
|
||||
|
||||
throw new ConnectionIsNotSetError(this.version);
|
||||
throw new ConnectionIsNotSetError("mysql");
|
||||
}
|
||||
|
||||
/**
|
||||
* If driver dependency is not given explicitly, then try to load it via "require".
|
||||
*/
|
||||
protected loadDependencies(): void {
|
||||
if (!require)
|
||||
throw new DriverPackageLoadError();
|
||||
|
||||
try {
|
||||
this.mysql = require(this.version);
|
||||
this.mysql = PlatformTools.load("mysql"); // try to load first supported package
|
||||
|
||||
} catch (e) {
|
||||
throw new DriverPackageNotInstalledError("Mysql", this.version);
|
||||
try {
|
||||
this.mysql = PlatformTools.load("mysql2"); // try to load second supported package
|
||||
|
||||
} catch (e) {
|
||||
throw new DriverPackageNotInstalledError("Mysql", "mysql");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -64,14 +64,26 @@ export class MysqlQueryRunner implements QueryRunner {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`;
|
||||
const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') AS query FROM information_schema.tables WHERE table_schema = '${this.dbName}'`;
|
||||
const enableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 1;`;
|
||||
await this.beginTransaction();
|
||||
try {
|
||||
const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`;
|
||||
const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') AS query FROM information_schema.tables WHERE table_schema = '${this.dbName}'`;
|
||||
const enableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 1;`;
|
||||
|
||||
await this.query(disableForeignKeysCheckQuery);
|
||||
const dropQueries: ObjectLiteral[] = await this.query(dropTablesQuery);
|
||||
await Promise.all(dropQueries.map(query => this.query(query["query"])));
|
||||
await this.query(enableForeignKeysCheckQuery);
|
||||
await this.query(disableForeignKeysCheckQuery);
|
||||
const dropQueries: ObjectLiteral[] = await this.query(dropTablesQuery);
|
||||
await Promise.all(dropQueries.map(query => this.query(query["query"])));
|
||||
await this.query(enableForeignKeysCheckQuery);
|
||||
|
||||
await this.commitTransaction();
|
||||
|
||||
} catch (error) {
|
||||
await this.rollbackTransaction();
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
await this.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,13 +191,24 @@ export class MysqlQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
@ -272,7 +295,7 @@ export class MysqlQueryRunner implements QueryRunner {
|
||||
// create index schemas from the loaded indices
|
||||
tableSchema.indices = dbIndices
|
||||
.filter(dbIndex => {
|
||||
return dbIndex["TABLE_NAME"] === tableSchema.name &&
|
||||
return dbIndex["TABLE_NAME"] === tableSchema.name &&
|
||||
(!tableSchema.foreignKeys.find(foreignKey => foreignKey.name === dbIndex["INDEX_NAME"])) &&
|
||||
(!tableSchema.primaryKeys.find(primaryKey => primaryKey.name === dbIndex["INDEX_NAME"]));
|
||||
})
|
||||
@ -388,7 +411,7 @@ export class MysqlQueryRunner implements QueryRunner {
|
||||
const promises = foreignKeys.map(foreignKey => {
|
||||
const columnNames = foreignKey.columnNames.map(column => "`" + column + "`").join(", ");
|
||||
const referencedColumnNames = foreignKey.referencedColumnNames.map(column => "`" + column + "`").join(",");
|
||||
let sql = `ALTER TABLE ${dbTable.name} ADD CONSTRAINT \`${foreignKey.name}\` ` +
|
||||
let sql = `ALTER TABLE ${dbTable.name} ADD CONSTRAINT \`${foreignKey.name}\` ` +
|
||||
`FOREIGN KEY (${columnNames}) ` +
|
||||
`REFERENCES \`${foreignKey.referencedTableName}\`(${referencedColumnNames})`;
|
||||
if (foreignKey.onDelete) sql += " ON DELETE " + foreignKey.onDelete;
|
||||
@ -522,7 +545,7 @@ export class MysqlQueryRunner implements QueryRunner {
|
||||
if (column.comment)
|
||||
c += " COMMENT '" + column.comment + "'";
|
||||
if (column.default)
|
||||
c += " DEFAULT " + column.default + "";
|
||||
c += " DEFAULT '" + column.default + "'";
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@ -3,16 +3,16 @@ import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
|
||||
import {DriverOptions} from "../DriverOptions";
|
||||
import {DatabaseConnection} from "../DatabaseConnection";
|
||||
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
|
||||
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
|
||||
import {DriverUtils} from "../DriverUtils";
|
||||
import {Logger} from "../../logger/Logger";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {OracleQueryRunner} from "./OracleQueryRunner";
|
||||
import {ColumnTypes, ColumnType} from "../../metadata/types/ColumnTypes";
|
||||
import * as moment from "moment";
|
||||
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
|
||||
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
|
||||
import {PlatformTools} from "../../platform/PlatformTools";
|
||||
|
||||
/**
|
||||
* Organizes communication with Oracle DBMS.
|
||||
@ -217,66 +217,60 @@ export class OracleDriver implements Driver {
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type and metadata.
|
||||
*/
|
||||
preparePersistentValue(value: any, column: ColumnMetadata): any {
|
||||
switch (column.type) {
|
||||
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (value === null || value === undefined)
|
||||
return null;
|
||||
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value === true ? 1 : 0;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
return moment(value).format("YYYY-MM-DD");
|
||||
return DataTransformationUtils.mixedDateToDateString(value);
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value).format("HH:mm:ss");
|
||||
return DataTransformationUtils.mixedDateToTimeString(value);
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
return moment(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
if (columnMetadata.storeInLocalTimezone) {
|
||||
return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
} else {
|
||||
return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
}
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.stringify(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as any[])
|
||||
.map(i => String(i))
|
||||
.join(",");
|
||||
return DataTransformationUtils.simpleArrayToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, type: ColumnType): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type.
|
||||
*/
|
||||
prepareHydratedValue(value: any, column: ColumnMetadata): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnOrColumnType: ColumnMetadata|ColumnType): any {
|
||||
const type = columnOrColumnType instanceof ColumnMetadata ? columnOrColumnType.type : columnOrColumnType;
|
||||
switch (type) {
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value ? true : false;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD").toDate();
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value, "HH:mm:ss").toDate();
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
|
||||
// case ColumnTypes.DATETIME:
|
||||
// if (value instanceof Date)
|
||||
// return value;
|
||||
//
|
||||
// if (columnMetadata.loadInLocalTimezone) {
|
||||
// return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
// } else {
|
||||
// return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
// }
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.parse(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as string).split(",");
|
||||
return DataTransformationUtils.stringToSimpleArray(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -336,12 +330,10 @@ export class OracleDriver implements Driver {
|
||||
* If driver dependency is not given explicitly, then try to load it via "require".
|
||||
*/
|
||||
protected loadDependencies(): void {
|
||||
if (!require)
|
||||
throw new DriverPackageLoadError();
|
||||
|
||||
try {
|
||||
this.oracle = require("oracledb");
|
||||
} catch (e) {
|
||||
this.oracle = PlatformTools.load("oracledb");
|
||||
|
||||
} catch (e) { // todo: better error for browser env
|
||||
throw new DriverPackageNotInstalledError("Oracle", "oracledb");
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,14 +64,27 @@ export class OracleQueryRunner implements QueryRunner {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`;
|
||||
const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') AS query FROM information_schema.tables WHERE table_schema = '${this.dbName}'`;
|
||||
const enableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 1;`;
|
||||
await this.beginTransaction();
|
||||
try {
|
||||
const disableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 0;`;
|
||||
const dropTablesQuery = `SELECT concat('DROP TABLE IF EXISTS ', table_name, ';') AS query FROM information_schema.tables WHERE table_schema = '${this.dbName}'`;
|
||||
const enableForeignKeysCheckQuery = `SET FOREIGN_KEY_CHECKS = 1;`;
|
||||
|
||||
await this.query(disableForeignKeysCheckQuery);
|
||||
const dropQueries: ObjectLiteral[] = await this.query(dropTablesQuery);
|
||||
await Promise.all(dropQueries.map(query => this.query(query["query"])));
|
||||
await this.query(enableForeignKeysCheckQuery);
|
||||
|
||||
await this.commitTransaction();
|
||||
|
||||
} catch (error) {
|
||||
await this.rollbackTransaction();
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
await this.release();
|
||||
}
|
||||
|
||||
await this.query(disableForeignKeysCheckQuery);
|
||||
const dropQueries: ObjectLiteral[] = await this.query(dropTablesQuery);
|
||||
await Promise.all(dropQueries.map(query => this.query(query["query"])));
|
||||
await this.query(enableForeignKeysCheckQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +172,10 @@ export class OracleQueryRunner implements QueryRunner {
|
||||
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
|
||||
const values = keys.map(key => ":" + key).join(", ");
|
||||
const parameters = keys.map(key => keyValues[key]);
|
||||
const insertSql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`;
|
||||
|
||||
const insertSql = columns.length > 0
|
||||
? `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`
|
||||
: `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT VALUES`;
|
||||
if (generatedColumn) {
|
||||
const sql2 = `declare lastId number; begin ${insertSql} returning "id" into lastId; dbms_output.enable; dbms_output.put_line(lastId); dbms_output.get_line(:ln, :st); end;`;
|
||||
const saveResult = await this.query(sql2, parameters.concat([
|
||||
@ -191,13 +207,24 @@ export class OracleQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
@ -587,6 +614,8 @@ AND cons.constraint_name = cols.constraint_name AND cons.owner = cols.owner ORDE
|
||||
c += " GENERATED BY DEFAULT ON NULL AS IDENTITY";
|
||||
// if (column.comment) // todo: less priority, fix it later
|
||||
// c += " COMMENT '" + column.comment + "'";
|
||||
if (column.default)
|
||||
c += " DEFAULT '" + column.default + "'";
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@ -4,15 +4,15 @@ import {DriverOptions} from "../DriverOptions";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {DatabaseConnection} from "../DatabaseConnection";
|
||||
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
|
||||
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
|
||||
import {DriverUtils} from "../DriverUtils";
|
||||
import {ColumnTypes, ColumnType} from "../../metadata/types/ColumnTypes";
|
||||
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {Logger} from "../../logger/Logger";
|
||||
import * as moment from "moment";
|
||||
import {PostgresQueryRunner} from "./PostgresQueryRunner";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
|
||||
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
|
||||
import {PlatformTools} from "../../platform/PlatformTools";
|
||||
|
||||
// todo(tests):
|
||||
// check connection with url
|
||||
@ -179,67 +179,65 @@ export class PostgresDriver implements Driver {
|
||||
* Prepares given value to a value to be persisted, based on its column type and metadata.
|
||||
*/
|
||||
preparePersistentValue(value: any, column: ColumnMetadata): any {
|
||||
if (value === null || value === undefined)
|
||||
return null;
|
||||
|
||||
switch (column.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value === true ? 1 : 0;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
return moment(value).format("YYYY-MM-DD");
|
||||
return DataTransformationUtils.mixedDateToDateString(value);
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value).format("HH:mm:ss");
|
||||
return DataTransformationUtils.mixedDateToTimeString(value);
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
return moment(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
if (column.storeInLocalTimezone) {
|
||||
return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
} else {
|
||||
return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
}
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
// pg(pg-types) have done JSON.parse conversion
|
||||
// https://github.com/brianc/node-pg-types/blob/ed2d0e36e33217b34530727a98d20b325389e73a/lib/textParsers.js#L170
|
||||
// pg(pg-types) have done JSON.parse conversion
|
||||
// https://github.com/brianc/node-pg-types/blob/ed2d0e36e33217b34530727a98d20b325389e73a/lib/textParsers.js#L170
|
||||
return value;
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as any[])
|
||||
.map(i => String(i))
|
||||
.join(",");
|
||||
return DataTransformationUtils.simpleArrayToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, type: ColumnType): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type.
|
||||
*/
|
||||
prepareHydratedValue(value: any, column: ColumnMetadata): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnOrColumnType: ColumnMetadata|ColumnType): any {
|
||||
const type = columnOrColumnType instanceof ColumnMetadata ? columnOrColumnType.type : columnOrColumnType;
|
||||
switch (type) {
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value ? true : false;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD").toDate();
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value, "HH:mm:ss").toDate();
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
|
||||
// case ColumnTypes.DATETIME:
|
||||
// if (value instanceof Date)
|
||||
// return value;
|
||||
//
|
||||
// if (columnMetadata.loadInLocalTimezone) {
|
||||
// return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
// } else {
|
||||
// return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
// }
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.parse(value);
|
||||
if (typeof value === "string") {
|
||||
return JSON.parse(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as string).split(",");
|
||||
return DataTransformationUtils.stringToSimpleArray(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -255,7 +253,7 @@ export class PostgresDriver implements Driver {
|
||||
|
||||
const builtParameters: any[] = [];
|
||||
const keys = Object.keys(parameters).map(parameter => "(:" + parameter + "\\b)").join("|");
|
||||
sql = sql.replace(new RegExp(keys, "g"), (key: string): string => {
|
||||
sql = sql.replace(new RegExp(keys, "g"), (key: string): string => {
|
||||
const value = parameters[key.substr(1)];
|
||||
if (value instanceof Array) {
|
||||
return value.map((v: any) => {
|
||||
@ -340,12 +338,10 @@ export class PostgresDriver implements Driver {
|
||||
* If driver dependency is not given explicitly, then try to load it via "require".
|
||||
*/
|
||||
protected loadDependencies(): void {
|
||||
if (!require)
|
||||
throw new DriverPackageLoadError();
|
||||
|
||||
try {
|
||||
this.postgres = require("pg");
|
||||
} catch (e) {
|
||||
this.postgres = PlatformTools.load("pg");
|
||||
|
||||
} catch (e) { // todo: better error for browser env
|
||||
throw new DriverPackageNotInstalledError("Postgres", "pg");
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,9 +63,21 @@ export class PostgresQueryRunner implements QueryRunner {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const selectDropsQuery = `SELECT 'DROP TABLE IF EXISTS "' || tablename || '" CASCADE;' as query FROM pg_tables WHERE schemaname = 'public'`;
|
||||
const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
|
||||
await Promise.all(dropQueries.map(q => this.query(q["query"])));
|
||||
await this.beginTransaction();
|
||||
try {
|
||||
const selectDropsQuery = `SELECT 'DROP TABLE IF EXISTS "' || tablename || '" CASCADE;' as query FROM pg_tables WHERE schemaname = 'public'`;
|
||||
const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
|
||||
await Promise.all(dropQueries.map(q => this.query(q["query"])));
|
||||
|
||||
await this.commitTransaction();
|
||||
|
||||
} catch (error) {
|
||||
await this.rollbackTransaction();
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
await this.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,7 +159,9 @@ export class PostgresQueryRunner implements QueryRunner {
|
||||
const keys = Object.keys(keyValues);
|
||||
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
|
||||
const values = keys.map((key, index) => "$" + (index + 1)).join(",");
|
||||
const sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values}) ${ generatedColumn ? " RETURNING " + this.driver.escapeColumnName(generatedColumn.name) : "" }`;
|
||||
const sql = columns.length > 0
|
||||
? `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values}) ${ generatedColumn ? " RETURNING " + this.driver.escapeColumnName(generatedColumn.name) : "" }`
|
||||
: `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT VALUES ${ generatedColumn ? " RETURNING " + this.driver.escapeColumnName(generatedColumn.name) : "" }`;
|
||||
const parameters = keys.map(key => keyValues[key]);
|
||||
const result: ObjectLiteral[] = await this.query(sql, parameters);
|
||||
if (generatedColumn)
|
||||
@ -175,14 +189,25 @@ export class PostgresQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
const query = `DELETE FROM "${tableName}" WHERE ${conditionString}`;
|
||||
await this.query(query, parameters);
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,7 +312,7 @@ where constraint_type = 'PRIMARY KEY' and tc.table_catalog = '${this.dbName}'`;
|
||||
// create index schemas from the loaded indices
|
||||
tableSchema.indices = dbIndices
|
||||
.filter(dbIndex => {
|
||||
return dbIndex["table_name"] === tableSchema.name &&
|
||||
return dbIndex["table_name"] === tableSchema.name &&
|
||||
(!tableSchema.foreignKeys.find(foreignKey => foreignKey.name === dbIndex["index_name"])) &&
|
||||
(!tableSchema.primaryKeys.find(primaryKey => primaryKey.name === dbIndex["index_name"])) &&
|
||||
(!dbUniqueKeys.find(key => key["constraint_name"] === dbIndex["index_name"]));
|
||||
@ -582,6 +607,8 @@ where constraint_type = 'PRIMARY KEY' and tc.table_catalog = '${this.dbName}'`;
|
||||
c += " NOT NULL";
|
||||
if (column.isGenerated)
|
||||
c += " PRIMARY KEY";
|
||||
if (column.default)
|
||||
c += " DEFAULT '" + column.default + "'";
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@ -4,14 +4,14 @@ import {DriverOptions} from "../DriverOptions";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {DatabaseConnection} from "../DatabaseConnection";
|
||||
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
|
||||
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
|
||||
import {ColumnTypes, ColumnType} from "../../metadata/types/ColumnTypes";
|
||||
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {Logger} from "../../logger/Logger";
|
||||
import * as moment from "moment";
|
||||
import {SqliteQueryRunner} from "./SqliteQueryRunner";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
|
||||
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
|
||||
import {PlatformTools} from "../../platform/PlatformTools";
|
||||
|
||||
/**
|
||||
* Organizes communication with sqlite DBMS.
|
||||
@ -83,7 +83,12 @@ export class SqliteDriver implements Driver {
|
||||
connection: connection,
|
||||
isTransactionActive: false
|
||||
};
|
||||
ok();
|
||||
|
||||
// we need to enable foreign keys in sqlite to make sure all foreign key related features
|
||||
// working properly. this also makes onDelete to work with sqlite.
|
||||
connection.run(`PRAGMA foreign_keys = ON;`, (err: any, result: any) => {
|
||||
ok();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -125,66 +130,60 @@ export class SqliteDriver implements Driver {
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type and metadata.
|
||||
*/
|
||||
preparePersistentValue(value: any, column: ColumnMetadata): any {
|
||||
switch (column.type) {
|
||||
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (value === null || value === undefined)
|
||||
return null;
|
||||
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value === true ? 1 : 0;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
return moment(value).format("YYYY-MM-DD");
|
||||
return DataTransformationUtils.mixedDateToDateString(value);
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value).format("HH:mm:ss");
|
||||
return DataTransformationUtils.mixedDateToTimeString(value);
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
return moment(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
if (columnMetadata.storeInLocalTimezone) {
|
||||
return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
} else {
|
||||
return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
}
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.stringify(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as any[])
|
||||
.map(i => String(i))
|
||||
.join(",");
|
||||
return DataTransformationUtils.simpleArrayToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, type: ColumnType): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type.
|
||||
*/
|
||||
prepareHydratedValue(value: any, column: ColumnMetadata): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnOrColumnType: ColumnMetadata|ColumnType): any {
|
||||
const type = columnOrColumnType instanceof ColumnMetadata ? columnOrColumnType.type : columnOrColumnType;
|
||||
switch (type) {
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value ? true : false;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD").toDate();
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value, "HH:mm:ss").toDate();
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
|
||||
// case ColumnTypes.DATETIME:
|
||||
// if (value instanceof Date)
|
||||
// return value;
|
||||
//
|
||||
// if (columnMetadata.loadInLocalTimezone) {
|
||||
// return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
// } else {
|
||||
// return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
// }
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.parse(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as string).split(",");
|
||||
return DataTransformationUtils.stringToSimpleArray(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -200,7 +199,7 @@ export class SqliteDriver implements Driver {
|
||||
|
||||
const builtParameters: any[] = [];
|
||||
const keys = Object.keys(parameters).map(parameter => "(:" + parameter + "\\b)").join("|");
|
||||
sql = sql.replace(new RegExp(keys, "g"), (key: string): string => {
|
||||
sql = sql.replace(new RegExp(keys, "g"), (key: string): string => {
|
||||
const value = parameters[key.substr(1)];
|
||||
if (value instanceof Array) {
|
||||
return value.map((v: any) => {
|
||||
@ -256,12 +255,10 @@ export class SqliteDriver implements Driver {
|
||||
* If driver dependency is not given explicitly, then try to load it via "require".
|
||||
*/
|
||||
protected loadDependencies(): void {
|
||||
if (!require)
|
||||
throw new DriverPackageLoadError();
|
||||
|
||||
try {
|
||||
this.sqlite = require("sqlite3").verbose();
|
||||
} catch (e) {
|
||||
this.sqlite = PlatformTools.load("sqlite3").verbose();
|
||||
|
||||
} catch (e) { // todo: better error for browser env
|
||||
throw new DriverPackageNotInstalledError("SQLite", "sqlite3");
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,9 +66,22 @@ export class SqliteQueryRunner implements QueryRunner {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const selectDropsQuery = `select 'drop table ' || name || ';' as query from sqlite_master where type = 'table' and name != 'sqlite_sequence'`;
|
||||
const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
|
||||
await Promise.all(dropQueries.map(q => this.query(q["query"])));
|
||||
await this.query(`PRAGMA foreign_keys = OFF;`);
|
||||
await this.beginTransaction();
|
||||
try {
|
||||
const selectDropsQuery = `select 'drop table ' || name || ';' as query from sqlite_master where type = 'table' and name != 'sqlite_sequence'`;
|
||||
const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
|
||||
await Promise.all(dropQueries.map(q => this.query(q["query"])));
|
||||
await this.commitTransaction();
|
||||
|
||||
} catch (error) {
|
||||
await this.rollbackTransaction();
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
await this.release();
|
||||
await this.query(`PRAGMA foreign_keys = ON;`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,11 +166,11 @@ export class SqliteQueryRunner implements QueryRunner {
|
||||
|
||||
this.logger.logQuery(sql, parameters);
|
||||
return new Promise<any[]>((ok, fail) => {
|
||||
const _this = this;
|
||||
const __this = this;
|
||||
this.databaseConnection.connection.run(sql, parameters, function (err: any): void {
|
||||
if (err) {
|
||||
_this.logger.logFailedQuery(sql, parameters);
|
||||
_this.logger.logQueryError(err);
|
||||
__this.logger.logFailedQuery(sql, parameters);
|
||||
__this.logger.logQueryError(err);
|
||||
fail(err);
|
||||
} else {
|
||||
if (generatedColumn)
|
||||
@ -188,14 +201,25 @@ export class SqliteQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
const query = `DELETE FROM "${tableName}" WHERE ${conditionString}`;
|
||||
await this.query(query, parameters);
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,7 +327,7 @@ export class SqliteQueryRunner implements QueryRunner {
|
||||
// create index schemas from the loaded indices
|
||||
const indicesPromises = dbIndices
|
||||
.filter(dbIndex => {
|
||||
return dbIndex["origin"] !== "pk" &&
|
||||
return dbIndex["origin"] !== "pk" &&
|
||||
(!tableSchema.foreignKeys.find(foreignKey => foreignKey.name === dbIndex["name"])) &&
|
||||
(!tableSchema.primaryKeys.find(primaryKey => primaryKey.name === dbIndex["name"]));
|
||||
})
|
||||
@ -533,6 +557,8 @@ export class SqliteQueryRunner implements QueryRunner {
|
||||
c += " UNIQUE";
|
||||
if (column.isGenerated === true) // don't use skipPrimary here since updates can update already exist primary without auto inc.
|
||||
c += " PRIMARY KEY AUTOINCREMENT";
|
||||
if (column.default)
|
||||
c += " DEFAULT '" + column.default + "'";
|
||||
|
||||
return c;
|
||||
}
|
||||
@ -549,6 +575,7 @@ export class SqliteQueryRunner implements QueryRunner {
|
||||
const columnNames = foreignKey.columnNames.map(name => `"${name}"`).join(", ");
|
||||
const referencedColumnNames = foreignKey.referencedColumnNames.map(name => `"${name}"`).join(", ");
|
||||
sql1 += `, FOREIGN KEY(${columnNames}) REFERENCES "${foreignKey.referencedTableName}"(${referencedColumnNames})`;
|
||||
if (foreignKey.onDelete) sql1 += " ON DELETE " + foreignKey.onDelete;
|
||||
});
|
||||
|
||||
const primaryKeyColumns = tableSchema.columns.filter(column => column.isPrimary && !column.isGenerated);
|
||||
|
||||
@ -3,16 +3,16 @@ import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
|
||||
import {DriverOptions} from "../DriverOptions";
|
||||
import {DatabaseConnection} from "../DatabaseConnection";
|
||||
import {DriverPackageNotInstalledError} from "../error/DriverPackageNotInstalledError";
|
||||
import {DriverPackageLoadError} from "../error/DriverPackageLoadError";
|
||||
import {DriverUtils} from "../DriverUtils";
|
||||
import {Logger} from "../../logger/Logger";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {SqlServerQueryRunner} from "./SqlServerQueryRunner";
|
||||
import {ColumnTypes, ColumnType} from "../../metadata/types/ColumnTypes";
|
||||
import * as moment from "moment";
|
||||
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
|
||||
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
|
||||
import {PlatformTools} from "../../platform/PlatformTools";
|
||||
|
||||
/**
|
||||
* Organizes communication with SQL Server DBMS.
|
||||
@ -201,66 +201,60 @@ export class SqlServerDriver implements Driver {
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type and metadata.
|
||||
*/
|
||||
preparePersistentValue(value: any, column: ColumnMetadata): any {
|
||||
switch (column.type) {
|
||||
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (value === null || value === undefined)
|
||||
return null;
|
||||
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value === true ? 1 : 0;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
return moment(value).format("YYYY-MM-DD");
|
||||
return DataTransformationUtils.mixedDateToDateString(value);
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value).format("HH:mm:ss");
|
||||
return DataTransformationUtils.mixedDateToTimeString(value);
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
return moment(value).format("YYYY-MM-DD HH:mm:ss");
|
||||
if (columnMetadata.storeInLocalTimezone) {
|
||||
return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
} else {
|
||||
return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
}
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.stringify(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as any[])
|
||||
.map(i => String(i))
|
||||
.join(",");
|
||||
return DataTransformationUtils.simpleArrayToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, type: ColumnType): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type.
|
||||
*/
|
||||
prepareHydratedValue(value: any, column: ColumnMetadata): any;
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnOrColumnType: ColumnMetadata|ColumnType): any {
|
||||
const type = columnOrColumnType instanceof ColumnMetadata ? columnOrColumnType.type : columnOrColumnType;
|
||||
switch (type) {
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value ? true : false;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD").toDate();
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return moment(value, "HH:mm:ss").toDate();
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
if (value instanceof Date)
|
||||
return value;
|
||||
|
||||
return moment(value, "YYYY-MM-DD HH:mm:ss").toDate();
|
||||
// case ColumnTypes.DATETIME:
|
||||
// if (value instanceof Date)
|
||||
// return value;
|
||||
//
|
||||
// if (columnMetadata.loadInLocalTimezone) {
|
||||
// return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
// } else {
|
||||
// return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
// }
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.parse(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return (value as string).split(",");
|
||||
return DataTransformationUtils.stringToSimpleArray(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -321,12 +315,10 @@ export class SqlServerDriver implements Driver {
|
||||
* If driver dependency is not given explicitly, then try to load it via "require".
|
||||
*/
|
||||
protected loadDependencies(): void {
|
||||
if (!require)
|
||||
throw new DriverPackageLoadError();
|
||||
|
||||
try {
|
||||
this.mssql = require("mssql");
|
||||
} catch (e) {
|
||||
this.mssql = PlatformTools.load("mssql");
|
||||
|
||||
} catch (e) { // todo: better error for browser env
|
||||
throw new DriverPackageNotInstalledError("SQL Server", "mssql");
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,20 +64,32 @@ export class SqlServerQueryRunner implements QueryRunner {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const allTablesSql = `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'`;
|
||||
const allTablesResults: ObjectLiteral[] = await this.query(allTablesSql);
|
||||
const tableNames = allTablesResults.map(result => result["TABLE_NAME"]);
|
||||
await Promise.all(tableNames.map(async tableName => {
|
||||
const dropForeignKeySql = `SELECT 'ALTER TABLE ' + OBJECT_SCHEMA_NAME(parent_object_id) + '.[' + OBJECT_NAME(parent_object_id) + '] DROP CONSTRAINT ' + name as query FROM sys.foreign_keys WHERE referenced_object_id = object_id('${tableName}')`;
|
||||
const dropFkQueries: ObjectLiteral[] = await this.query(dropForeignKeySql);
|
||||
return Promise.all(dropFkQueries.map(result => result["query"]).map(dropQuery => {
|
||||
return this.query(dropQuery);
|
||||
await this.beginTransaction();
|
||||
try {
|
||||
const allTablesSql = `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'`;
|
||||
const allTablesResults: ObjectLiteral[] = await this.query(allTablesSql);
|
||||
const tableNames = allTablesResults.map(result => result["TABLE_NAME"]);
|
||||
await Promise.all(tableNames.map(async tableName => {
|
||||
const dropForeignKeySql = `SELECT 'ALTER TABLE ' + OBJECT_SCHEMA_NAME(parent_object_id) + '.[' + OBJECT_NAME(parent_object_id) + '] DROP CONSTRAINT ' + name as query FROM sys.foreign_keys WHERE referenced_object_id = object_id('${tableName}')`;
|
||||
const dropFkQueries: ObjectLiteral[] = await this.query(dropForeignKeySql);
|
||||
return Promise.all(dropFkQueries.map(result => result["query"]).map(dropQuery => {
|
||||
return this.query(dropQuery);
|
||||
}));
|
||||
}));
|
||||
}));
|
||||
await Promise.all(tableNames.map(tableName => {
|
||||
const dropTableSql = `DROP TABLE "${tableName}"`;
|
||||
return this.query(dropTableSql);
|
||||
}));
|
||||
await Promise.all(tableNames.map(tableName => {
|
||||
const dropTableSql = `DROP TABLE "${tableName}"`;
|
||||
return this.query(dropTableSql);
|
||||
}));
|
||||
|
||||
await this.commitTransaction();
|
||||
|
||||
} catch (error) {
|
||||
await this.rollbackTransaction();
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
await this.release();
|
||||
}
|
||||
|
||||
// const selectDropsQuery = `SELECT 'DROP TABLE "' + TABLE_NAME + '"' as query FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE';`;
|
||||
// const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
|
||||
@ -212,7 +224,11 @@ export class SqlServerQueryRunner implements QueryRunner {
|
||||
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
|
||||
const values = keys.map((key, index) => "@" + index).join(",");
|
||||
const parameters = keys.map(key => keyValues[key]);
|
||||
const sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) ${ generatedColumn ? "OUTPUT INSERTED." + generatedColumn.name + " " : "" }VALUES (${values})`;
|
||||
|
||||
const sql = columns.length > 0
|
||||
? `INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) ${ generatedColumn ? "OUTPUT INSERTED." + generatedColumn.name + " " : "" }VALUES (${values})`
|
||||
: `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT ${ generatedColumn ? "OUTPUT INSERTED." + generatedColumn.name + " " : "" }VALUES`;
|
||||
|
||||
const result = await this.query(sql, parameters);
|
||||
return generatedColumn ? result instanceof Array ? result[0][generatedColumn.name] : result[generatedColumn.name] : undefined;
|
||||
}
|
||||
@ -236,13 +252,24 @@ export class SqlServerQueryRunner implements QueryRunner {
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void> {
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = this.parametrize(conditions).join(" AND ");
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
const parameters = Object.keys(conditions).map(key => conditions[key]);
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
@ -491,7 +518,7 @@ WHERE columnUsages.TABLE_CATALOG = '${this.dbName}' AND tableConstraints.TABLE_C
|
||||
const promises = foreignKeys.map(foreignKey => {
|
||||
const columnNames = foreignKey.columnNames.map(column => `"` + column + `"`).join(", ");
|
||||
const referencedColumnNames = foreignKey.referencedColumnNames.map(column => `"` + column + `"`).join(",");
|
||||
let sql = `ALTER TABLE "${dbTable.name}" ADD CONSTRAINT "${foreignKey.name}" ` +
|
||||
let sql = `ALTER TABLE "${dbTable.name}" ADD CONSTRAINT "${foreignKey.name}" ` +
|
||||
`FOREIGN KEY (${columnNames}) ` +
|
||||
`REFERENCES "${foreignKey.referencedTableName}"(${referencedColumnNames})`;
|
||||
if (foreignKey.onDelete) sql += " ON DELETE " + foreignKey.onDelete;
|
||||
@ -621,6 +648,8 @@ WHERE columnUsages.TABLE_CATALOG = '${this.dbName}' AND tableConstraints.TABLE_C
|
||||
// c += " PRIMARY KEY";
|
||||
if (column.comment)
|
||||
c += " COMMENT '" + column.comment + "'";
|
||||
if (column.default)
|
||||
c += " DEFAULT '" + column.default + "'";
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
243
src/driver/websql/WebsqlDriver.ts
Normal file
243
src/driver/websql/WebsqlDriver.ts
Normal file
@ -0,0 +1,243 @@
|
||||
import {Driver} from "../Driver";
|
||||
import {ConnectionIsNotSetError} from "../error/ConnectionIsNotSetError";
|
||||
import {DriverOptions} from "../DriverOptions";
|
||||
import {DatabaseConnection} from "../DatabaseConnection";
|
||||
import {DriverUtils} from "../DriverUtils";
|
||||
import {Logger} from "../../logger/Logger";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
|
||||
import {DataTransformationUtils} from "../../util/DataTransformationUtils";
|
||||
import {WebsqlQueryRunner} from "./WebsqlQueryRunner";
|
||||
|
||||
/**
|
||||
* Organizes communication with WebSQL in the browser.
|
||||
*/
|
||||
export class WebsqlDriver implements Driver {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Driver connection options.
|
||||
*/
|
||||
readonly options: DriverOptions;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Connection to database.
|
||||
*/
|
||||
protected databaseConnection: DatabaseConnection|undefined;
|
||||
|
||||
/**
|
||||
* Logger used go log queries and errors.
|
||||
*/
|
||||
protected logger: Logger;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(options: DriverOptions, logger: Logger) {
|
||||
|
||||
this.options = DriverUtils.buildDriverOptions(options);
|
||||
this.logger = logger;
|
||||
|
||||
// validate options to make sure everything is set
|
||||
// if (!this.options.host)
|
||||
// throw new DriverOptionNotSetError("host");
|
||||
// if (!this.options.username)
|
||||
// throw new DriverOptionNotSetError("username");
|
||||
if (!this.options.database)
|
||||
throw new DriverOptionNotSetError("database");
|
||||
// todo: what about extra options: version, description, size
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Performs connection to the database.
|
||||
* Based on pooling options, it can either create connection immediately,
|
||||
* either create a pool and create connection when needed.
|
||||
*/
|
||||
connect(): Promise<void> {
|
||||
|
||||
// build connection options for the driver
|
||||
const options = Object.assign({}, {
|
||||
database: this.options.database,
|
||||
}, this.options.extra || {});
|
||||
|
||||
return new Promise<void>((ok, fail) => {
|
||||
const connection = (window as any).openDatabase(
|
||||
options.database,
|
||||
options.version,
|
||||
options.description,
|
||||
options.size,
|
||||
);
|
||||
this.databaseConnection = {
|
||||
id: 1,
|
||||
connection: connection,
|
||||
isTransactionActive: false
|
||||
};
|
||||
ok();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection with the database.
|
||||
*/
|
||||
disconnect(): Promise<void> {
|
||||
if (!this.databaseConnection)
|
||||
throw new ConnectionIsNotSetError("websql");
|
||||
|
||||
return new Promise<void>((ok, fail) => {
|
||||
// const handler = (err: any) => err ? fail(err) : ok();
|
||||
// todo: find out how to close connection
|
||||
ok();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query runner used for common queries.
|
||||
*/
|
||||
async createQueryRunner(): Promise<QueryRunner> {
|
||||
if (!this.databaseConnection)
|
||||
return Promise.reject(new ConnectionIsNotSetError("websql"));
|
||||
|
||||
const databaseConnection = await this.retrieveDatabaseConnection();
|
||||
return new WebsqlQueryRunner(databaseConnection, this, this.logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to the native implementation of the database.
|
||||
*/
|
||||
nativeInterface() {
|
||||
return {
|
||||
connection: this.databaseConnection ? this.databaseConnection.connection : undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces parameters in the given sql with special escaping character
|
||||
* and an array of parameter names to be passed to a query.
|
||||
*/
|
||||
escapeQueryWithParameters(sql: string, parameters: ObjectLiteral): [string, any[]] {
|
||||
if (!parameters || !Object.keys(parameters).length)
|
||||
return [sql, []];
|
||||
const escapedParameters: any[] = [];
|
||||
const keys = Object.keys(parameters).map(parameter => "(:" + parameter + "\\b)").join("|");
|
||||
sql = sql.replace(new RegExp(keys, "g"), (key: string) => {
|
||||
escapedParameters.push(parameters[key.substr(1)]);
|
||||
return "?";
|
||||
}); // todo: make replace only in value statements, otherwise problems
|
||||
return [sql, escapedParameters];
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a column name.
|
||||
*/
|
||||
escapeColumnName(columnName: string): string {
|
||||
return columnName; // "`" + columnName + "`";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes an alias.
|
||||
*/
|
||||
escapeAliasName(aliasName: string): string {
|
||||
return aliasName; // "`" + aliasName + "`";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a table name.
|
||||
*/
|
||||
escapeTableName(tableName: string): string {
|
||||
return tableName; // "`" + tableName + "`";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type and metadata.
|
||||
*/
|
||||
preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (value === null || value === undefined)
|
||||
return null;
|
||||
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value === true ? 1 : 0;
|
||||
|
||||
case ColumnTypes.DATE:
|
||||
return DataTransformationUtils.mixedDateToDateString(value);
|
||||
|
||||
case ColumnTypes.TIME:
|
||||
return DataTransformationUtils.mixedDateToTimeString(value);
|
||||
|
||||
case ColumnTypes.DATETIME:
|
||||
if (columnMetadata.storeInLocalTimezone) {
|
||||
return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
} else {
|
||||
return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
}
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.stringify(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return DataTransformationUtils.simpleArrayToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
switch (columnMetadata.type) {
|
||||
case ColumnTypes.BOOLEAN:
|
||||
return value ? true : false;
|
||||
|
||||
// case ColumnTypes.DATETIME:
|
||||
// if (value instanceof Date)
|
||||
// return value;
|
||||
//
|
||||
// if (columnMetadata.loadInLocalTimezone) {
|
||||
// return DataTransformationUtils.mixedDateToDatetimeString(value);
|
||||
// } else {
|
||||
// return DataTransformationUtils.mixedDateToUtcDatetimeString(value);
|
||||
// }
|
||||
|
||||
case ColumnTypes.JSON:
|
||||
return JSON.parse(value);
|
||||
|
||||
case ColumnTypes.SIMPLE_ARRAY:
|
||||
return DataTransformationUtils.stringToSimpleArray(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Retrieves a new database connection.
|
||||
* If pooling is enabled then connection from the pool will be retrieved.
|
||||
* Otherwise active connection will be returned.
|
||||
*/
|
||||
protected retrieveDatabaseConnection(): Promise<DatabaseConnection> {
|
||||
if (this.databaseConnection)
|
||||
return Promise.resolve(this.databaseConnection);
|
||||
|
||||
throw new ConnectionIsNotSetError("websql");
|
||||
}
|
||||
|
||||
}
|
||||
622
src/driver/websql/WebsqlQueryRunner.ts
Normal file
622
src/driver/websql/WebsqlQueryRunner.ts
Normal file
@ -0,0 +1,622 @@
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {DatabaseConnection} from "../DatabaseConnection";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {TransactionAlreadyStartedError} from "../error/TransactionAlreadyStartedError";
|
||||
import {TransactionNotStartedError} from "../error/TransactionNotStartedError";
|
||||
import {Logger} from "../../logger/Logger";
|
||||
import {DataTypeNotSupportedByDriverError} from "../error/DataTypeNotSupportedByDriverError";
|
||||
import {ColumnSchema} from "../../schema-builder/schema/ColumnSchema";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {TableSchema} from "../../schema-builder/schema/TableSchema";
|
||||
import {ForeignKeySchema} from "../../schema-builder/schema/ForeignKeySchema";
|
||||
import {PrimaryKeySchema} from "../../schema-builder/schema/PrimaryKeySchema";
|
||||
import {IndexSchema} from "../../schema-builder/schema/IndexSchema";
|
||||
import {QueryRunnerAlreadyReleasedError} from "../../query-runner/error/QueryRunnerAlreadyReleasedError";
|
||||
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
|
||||
import {WebsqlDriver} from "./WebsqlDriver";
|
||||
|
||||
/**
|
||||
* Runs queries on a single websql database connection.
|
||||
*/
|
||||
export class WebsqlQueryRunner implements QueryRunner {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Indicates if connection for this query runner is released.
|
||||
* Once its released, query runner cannot run queries anymore.
|
||||
*/
|
||||
protected isReleased = false;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(protected databaseConnection: DatabaseConnection,
|
||||
protected driver: WebsqlDriver,
|
||||
protected logger: Logger) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Releases database connection. This is needed when using connection pooling.
|
||||
* If connection is not from a pool, it should not be released.
|
||||
* You cannot use this class's methods after its released.
|
||||
*/
|
||||
release(): Promise<void> {
|
||||
if (this.databaseConnection.releaseCallback) {
|
||||
this.isReleased = true;
|
||||
return this.databaseConnection.releaseCallback();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all tables from the currently connected database.
|
||||
*/
|
||||
async clearDatabase(): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
// await this.query(`PRAGMA foreign_keys = OFF;`);
|
||||
await this.beginTransaction();
|
||||
try {
|
||||
const selectDropsQuery = `select 'drop table ' || name || ';' as query from sqlite_master where type = 'table' and name != 'sqlite_sequence'`;
|
||||
const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
|
||||
await Promise.all(dropQueries.map(q => this.query(q["query"])));
|
||||
await this.commitTransaction();
|
||||
|
||||
} catch (error) {
|
||||
await this.rollbackTransaction();
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
await this.release();
|
||||
// await this.query(`PRAGMA foreign_keys = ON;`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts transaction.
|
||||
*/
|
||||
async beginTransaction(): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
if (this.databaseConnection.isTransactionActive)
|
||||
throw new TransactionAlreadyStartedError();
|
||||
|
||||
this.databaseConnection.isTransactionActive = true;
|
||||
// await this.query("BEGIN TRANSACTION");
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits transaction.
|
||||
*/
|
||||
async commitTransaction(): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
if (!this.databaseConnection.isTransactionActive)
|
||||
throw new TransactionNotStartedError();
|
||||
|
||||
// await this.query("COMMIT");
|
||||
this.databaseConnection.isTransactionActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollbacks transaction.
|
||||
*/
|
||||
async rollbackTransaction(): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
if (!this.databaseConnection.isTransactionActive)
|
||||
throw new TransactionNotStartedError();
|
||||
|
||||
// await this.query("ROLLBACK");
|
||||
this.databaseConnection.isTransactionActive = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if transaction is in progress.
|
||||
*/
|
||||
isTransactionActive(): boolean {
|
||||
return this.databaseConnection.isTransactionActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a given SQL query.
|
||||
*/
|
||||
query(query: string, parameters?: any[]): Promise<any> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
this.logger.logQuery(query, parameters);
|
||||
return new Promise((ok, fail) => {
|
||||
|
||||
const db = this.databaseConnection.connection;
|
||||
// todo: check if transaction is not active
|
||||
db.transaction((tx: any) => {
|
||||
tx.executeSql(query, parameters, (tx: any, result: any) => {
|
||||
const rows = Object
|
||||
.keys(result.rows)
|
||||
.filter(key => key !== "length")
|
||||
.map(key => result.rows[key]);
|
||||
ok(rows);
|
||||
|
||||
}, (tx: any, err: any) => {
|
||||
this.logger.logFailedQuery(query, parameters);
|
||||
this.logger.logQueryError(err);
|
||||
return fail(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new row into given table.
|
||||
*/
|
||||
async insert(tableName: string, keyValues: ObjectLiteral, generatedColumn?: ColumnMetadata): Promise<any> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const keys = Object.keys(keyValues);
|
||||
const columns = keys.map(key => this.driver.escapeColumnName(key)).join(", ");
|
||||
const values = keys.map((key, index) => "$" + (index + 1)).join(",");
|
||||
const sql = columns.length > 0 ? (`INSERT INTO ${this.driver.escapeTableName(tableName)}(${columns}) VALUES (${values})`) : `INSERT INTO ${this.driver.escapeTableName(tableName)} DEFAULT VALUES`;
|
||||
const parameters = keys.map(key => keyValues[key]);
|
||||
|
||||
this.logger.logQuery(sql, parameters);
|
||||
return new Promise<any[]>((ok, fail) => {
|
||||
|
||||
const db = this.databaseConnection.connection;
|
||||
// todo: check if transaction is not active
|
||||
db.transaction((tx: any) => {
|
||||
tx.executeSql(sql, parameters, (tx: any, result: any) => {
|
||||
if (generatedColumn)
|
||||
return ok(result["insertId"]);
|
||||
ok();
|
||||
|
||||
}, (tx: any, err: any) => {
|
||||
this.logger.logFailedQuery(sql, parameters);
|
||||
this.logger.logQueryError(err);
|
||||
return fail(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates rows that match given conditions in the given table.
|
||||
*/
|
||||
async update(tableName: string, valuesMap: ObjectLiteral, conditions: ObjectLiteral): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const updateValues = this.parametrize(valuesMap).join(", ");
|
||||
const conditionString = this.parametrize(conditions, Object.keys(valuesMap).length).join(" AND ");
|
||||
const query = `UPDATE ${this.driver.escapeTableName(tableName)} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
|
||||
const updateParams = Object.keys(valuesMap).map(key => valuesMap[key]);
|
||||
const conditionParams = Object.keys(conditions).map(key => conditions[key]);
|
||||
const allParameters = updateParams.concat(conditionParams);
|
||||
await this.query(query, allParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, condition: string, parameters?: any[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral): Promise<void>;
|
||||
|
||||
/**
|
||||
* Deletes from the given table by a given conditions.
|
||||
*/
|
||||
async delete(tableName: string, conditions: ObjectLiteral|string, maybeParameters?: any[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const conditionString = typeof conditions === "string" ? conditions : this.parametrize(conditions).join(" AND ");
|
||||
const parameters = conditions instanceof Object ? Object.keys(conditions).map(key => (conditions as ObjectLiteral)[key]) : maybeParameters;
|
||||
|
||||
const sql = `DELETE FROM ${this.driver.escapeTableName(tableName)} WHERE ${conditionString}`;
|
||||
await this.query(sql, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts rows into closure table.
|
||||
*/
|
||||
async insertIntoClosureTable(tableName: string, newEntityId: any, parentId: any, hasLevel: boolean): Promise<number> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
let sql = "";
|
||||
if (hasLevel) {
|
||||
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant, level) ` +
|
||||
`SELECT ancestor, ${newEntityId}, level + 1 FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
|
||||
`UNION ALL SELECT ${newEntityId}, ${newEntityId}, 1`;
|
||||
} else {
|
||||
sql = `INSERT INTO ${this.driver.escapeTableName(tableName)}(ancestor, descendant) ` +
|
||||
`SELECT ancestor, ${newEntityId} FROM ${this.driver.escapeTableName(tableName)} WHERE descendant = ${parentId} ` +
|
||||
`UNION ALL SELECT ${newEntityId}, ${newEntityId}`;
|
||||
}
|
||||
await this.query(sql);
|
||||
const results: ObjectLiteral[] = await this.query(`SELECT MAX(level) as level FROM ${tableName} WHERE descendant = ${parentId}`);
|
||||
return results && results[0] && results[0]["level"] ? parseInt(results[0]["level"]) + 1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all tables (with given names) from the database and creates a TableSchema from them.
|
||||
*/
|
||||
async loadSchemaTables(tableNames: string[], namingStrategy: NamingStrategyInterface): Promise<TableSchema[]> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
// if no tables given then no need to proceed
|
||||
|
||||
if (!tableNames || !tableNames.length)
|
||||
return [];
|
||||
|
||||
// load tables, columns, indices and foreign keys
|
||||
const dbTables: ObjectLiteral[] = await this.query(`SELECT * FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence'`);
|
||||
|
||||
// if tables were not found in the db, no need to proceed
|
||||
if (!dbTables || !dbTables.length)
|
||||
return [];
|
||||
|
||||
// create table schemas for loaded tables
|
||||
return Promise.all(dbTables.map(async dbTable => {
|
||||
const tableSchema = new TableSchema(dbTable["name"]);
|
||||
|
||||
// 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"]}")`),
|
||||
]);
|
||||
|
||||
// find column name with auto increment
|
||||
let autoIncrementColumnName: string|undefined = undefined;
|
||||
const tableSql: string = dbTable["sql"];
|
||||
if (tableSql.indexOf("AUTOINCREMENT") !== -1) {
|
||||
autoIncrementColumnName = tableSql.substr(0, tableSql.indexOf("AUTOINCREMENT"));
|
||||
const comma = autoIncrementColumnName.lastIndexOf(",");
|
||||
const bracket = autoIncrementColumnName.lastIndexOf("(");
|
||||
if (comma !== -1) {
|
||||
autoIncrementColumnName = autoIncrementColumnName.substr(comma);
|
||||
autoIncrementColumnName = autoIncrementColumnName.substr(0, autoIncrementColumnName.lastIndexOf("\""));
|
||||
autoIncrementColumnName = autoIncrementColumnName.substr(autoIncrementColumnName.indexOf("\"") + 1);
|
||||
|
||||
} else if (bracket !== -1) {
|
||||
autoIncrementColumnName = autoIncrementColumnName.substr(bracket);
|
||||
autoIncrementColumnName = autoIncrementColumnName.substr(0, autoIncrementColumnName.lastIndexOf("\""));
|
||||
autoIncrementColumnName = autoIncrementColumnName.substr(autoIncrementColumnName.indexOf("\"") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// create column schemas from the loaded columns
|
||||
tableSchema.columns = dbColumns.map(dbColumn => {
|
||||
const columnSchema = new ColumnSchema();
|
||||
columnSchema.name = dbColumn["name"];
|
||||
columnSchema.type = dbColumn["type"].toLowerCase();
|
||||
columnSchema.default = dbColumn["dflt_value"] !== null && dbColumn["dflt_value"] !== undefined ? dbColumn["dflt_value"] : undefined;
|
||||
columnSchema.isNullable = dbColumn["notnull"] === 0;
|
||||
columnSchema.isPrimary = dbColumn["pk"] === 1;
|
||||
columnSchema.comment = ""; // todo later
|
||||
columnSchema.isGenerated = autoIncrementColumnName === dbColumn["name"];
|
||||
const columnForeignKeys = dbForeignKeys
|
||||
.filter(foreignKey => foreignKey["from"] === dbColumn["name"])
|
||||
.map(foreignKey => {
|
||||
const keyName = namingStrategy.foreignKeyName(dbTable["name"], [foreignKey["from"]], foreignKey["table"], [foreignKey["to"]]);
|
||||
return new ForeignKeySchema(keyName, [foreignKey["from"]], [foreignKey["to"]], foreignKey["table"], foreignKey["on_delete"]); // todo: how sqlite return from and to when they are arrays? (multiple column foreign keys)
|
||||
});
|
||||
tableSchema.addForeignKeys(columnForeignKeys);
|
||||
return columnSchema;
|
||||
});
|
||||
|
||||
// create primary key schema
|
||||
await Promise.all(dbIndices
|
||||
.filter(index => index["origin"] === "pk")
|
||||
.map(async index => {
|
||||
const indexInfos: ObjectLiteral[] = await this.query(`PRAGMA index_info("${index["name"]}")`);
|
||||
const indexColumns = indexInfos.map(indexInfo => indexInfo["name"]);
|
||||
indexColumns.forEach(indexColumn => {
|
||||
tableSchema.primaryKeys.push(new PrimaryKeySchema(index["name"], indexColumn));
|
||||
});
|
||||
}));
|
||||
|
||||
// create index schemas from the loaded indices
|
||||
const indicesPromises = dbIndices
|
||||
.filter(dbIndex => {
|
||||
return dbIndex["origin"] !== "pk" &&
|
||||
(!tableSchema.foreignKeys.find(foreignKey => foreignKey.name === dbIndex["name"])) &&
|
||||
(!tableSchema.primaryKeys.find(primaryKey => primaryKey.name === dbIndex["name"]));
|
||||
})
|
||||
.map(dbIndex => dbIndex["name"])
|
||||
.filter((value, index, self) => self.indexOf(value) === index) // unqiue
|
||||
.map(async dbIndexName => {
|
||||
const dbIndex = dbIndices.find(dbIndex => dbIndex["name"] === dbIndexName);
|
||||
const indexInfos: ObjectLiteral[] = await this.query(`PRAGMA index_info("${dbIndex!["name"]}")`);
|
||||
const indexColumns = indexInfos.map(indexInfo => indexInfo["name"]);
|
||||
|
||||
// check if db index is generated by sqlite itself and has special use case
|
||||
if (dbIndex!["name"].substr(0, "sqlite_autoindex".length) === "sqlite_autoindex") {
|
||||
if (dbIndex!["unique"] === 1) { // this means we have a special index generated for a column
|
||||
// so we find and update the column
|
||||
indexColumns.forEach(columnName => {
|
||||
const column = tableSchema.columns.find(column => column.name === columnName);
|
||||
if (column)
|
||||
column.isUnique = true;
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
|
||||
} else {
|
||||
return new IndexSchema(dbTable["name"], dbIndex!["name"], indexColumns, dbIndex!["unique"] === "1");
|
||||
}
|
||||
});
|
||||
|
||||
const indices = await Promise.all(indicesPromises);
|
||||
tableSchema.indices = indices.filter(index => !!index) as IndexSchema[];*/
|
||||
|
||||
return tableSchema;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new table from the given table metadata and column metadatas.
|
||||
*/
|
||||
async createTable(table: TableSchema): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
// skip columns with foreign keys, we will add them later
|
||||
const columnDefinitions = table.columns.map(column => this.buildCreateColumnSql(column)).join(", ");
|
||||
let sql = `CREATE TABLE "${table.name}" (${columnDefinitions}`;
|
||||
const primaryKeyColumns = table.columns.filter(column => column.isPrimary && !column.isGenerated);
|
||||
if (primaryKeyColumns.length > 0)
|
||||
sql += `, PRIMARY KEY(${primaryKeyColumns.map(column => `${column.name}`).join(", ")})`; // for some reason column escaping here generates a wrong schema
|
||||
sql += `)`;
|
||||
await this.query(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new column from the column metadata in the table.
|
||||
*/
|
||||
async createColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void> { // todo: remove column metadata returning
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
await this.recreateTable(tableSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a column in the table.
|
||||
* Changed column looses all its keys in the db.
|
||||
*/
|
||||
async changeColumns(tableSchema: TableSchema, changedColumns: { newColumn: ColumnSchema, oldColumn: ColumnSchema }[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
return this.recreateTable(tableSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the columns in the table.
|
||||
*/
|
||||
async dropColumns(tableSchema: TableSchema, columns: ColumnSchema[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const newTable = tableSchema.clone();
|
||||
newTable.removeColumns(columns);
|
||||
return this.recreateTable(newTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates table's primary keys.
|
||||
*/
|
||||
async updatePrimaryKeys(dbTable: TableSchema): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
return this.recreateTable(dbTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new foreign keys.
|
||||
*/
|
||||
async createForeignKeys(tableSchema: TableSchema, foreignKeys: ForeignKeySchema[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const newTable = tableSchema.clone();
|
||||
newTable.addForeignKeys(foreignKeys);
|
||||
return this.recreateTable(newTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a foreign keys from the table.
|
||||
*/
|
||||
async dropForeignKeys(tableSchema: TableSchema, foreignKeys: ForeignKeySchema[]): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const newTable = tableSchema.clone();
|
||||
newTable.removeForeignKeys(foreignKeys);
|
||||
return this.recreateTable(newTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new index.
|
||||
*/
|
||||
async createIndex(index: IndexSchema): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const columnNames = index.columnNames.map(columnName => `"${columnName}"`).join(",");
|
||||
const sql = `CREATE ${index.isUnique ? "UNIQUE " : ""}INDEX "${index.name}" ON "${index.tableName}"(${columnNames})`;
|
||||
await this.query(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops an index from the table.
|
||||
*/
|
||||
async dropIndex(tableName: string, indexName: string, isGenerated: boolean = false): Promise<void> {
|
||||
if (this.isReleased)
|
||||
throw new QueryRunnerAlreadyReleasedError();
|
||||
|
||||
const sql = `DROP INDEX "${indexName}"`;
|
||||
await this.query(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a database type from a given column metadata.
|
||||
*/
|
||||
normalizeType(column: ColumnMetadata) {
|
||||
switch (column.normalizedDataType) {
|
||||
case "string":
|
||||
return "character varying(" + (column.length ? column.length : 255) + ")";
|
||||
case "text":
|
||||
return "text";
|
||||
case "boolean":
|
||||
return "boolean";
|
||||
case "integer":
|
||||
case "int":
|
||||
return "integer";
|
||||
case "smallint":
|
||||
return "smallint";
|
||||
case "bigint":
|
||||
return "bigint";
|
||||
case "float":
|
||||
return "real";
|
||||
case "double":
|
||||
case "number":
|
||||
return "double precision";
|
||||
case "decimal":
|
||||
if (column.precision && column.scale) {
|
||||
return `decimal(${column.precision},${column.scale})`;
|
||||
|
||||
} else if (column.scale) {
|
||||
return `decimal(${column.scale})`;
|
||||
|
||||
} else if (column.precision) {
|
||||
return `decimal(${column.precision})`;
|
||||
|
||||
} else {
|
||||
return "decimal";
|
||||
|
||||
}
|
||||
case "date":
|
||||
return "date";
|
||||
case "time":
|
||||
if (column.timezone) {
|
||||
return "time with time zone";
|
||||
} else {
|
||||
return "time without time zone";
|
||||
}
|
||||
case "datetime":
|
||||
if (column.timezone) {
|
||||
return "timestamp with time zone";
|
||||
} else {
|
||||
return "timestamp without time zone";
|
||||
}
|
||||
case "json":
|
||||
return "json";
|
||||
case "simple_array":
|
||||
return column.length ? "character varying(" + column.length + ")" : "text";
|
||||
}
|
||||
|
||||
throw new DataTypeNotSupportedByDriverError(column.type, "SQLite");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parametrizes given object of values. Used to create column=value queries.
|
||||
*/
|
||||
protected parametrize(objectLiteral: ObjectLiteral, startIndex: number = 0): string[] {
|
||||
return Object.keys(objectLiteral).map((key, index) => this.driver.escapeColumnName(key) + "=$" + (startIndex + index + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a query for create column.
|
||||
*/
|
||||
protected buildCreateColumnSql(column: ColumnSchema): string {
|
||||
let c = "\"" + column.name + "\"";
|
||||
if (column instanceof ColumnMetadata) {
|
||||
c += " " + this.normalizeType(column);
|
||||
} else {
|
||||
c += " " + column.type;
|
||||
}
|
||||
if (column.isNullable !== true)
|
||||
c += " NOT NULL";
|
||||
if (column.isUnique === true)
|
||||
c += " UNIQUE";
|
||||
if (column.isGenerated === true) // don't use skipPrimary here since updates can update already exist primary without auto inc.
|
||||
c += " PRIMARY KEY AUTOINCREMENT";
|
||||
if (column.default)
|
||||
c += " DEFAULT '" + column.default + "'";
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
protected async recreateTable(tableSchema: TableSchema): Promise<void> {
|
||||
// const withoutForeignKeyColumns = columns.filter(column => column.foreignKeys.length === 0);
|
||||
// const createForeignKeys = options && options.createForeignKeys;
|
||||
const columnDefinitions = tableSchema.columns.map(dbColumn => this.buildCreateColumnSql(dbColumn)).join(", ");
|
||||
const columnNames = tableSchema.columns.map(column => `"${column.name}"`).join(", ");
|
||||
|
||||
let sql1 = `CREATE TABLE "temporary_${tableSchema.name}" (${columnDefinitions}`;
|
||||
// if (options && options.createForeignKeys) {
|
||||
tableSchema.foreignKeys.forEach(foreignKey => {
|
||||
const columnNames = foreignKey.columnNames.map(name => `"${name}"`).join(", ");
|
||||
const referencedColumnNames = foreignKey.referencedColumnNames.map(name => `"${name}"`).join(", ");
|
||||
sql1 += `, FOREIGN KEY(${columnNames}) REFERENCES "${foreignKey.referencedTableName}"(${referencedColumnNames})`;
|
||||
if (foreignKey.onDelete) sql1 += " ON DELETE " + foreignKey.onDelete;
|
||||
});
|
||||
|
||||
const primaryKeyColumns = tableSchema.columns.filter(column => column.isPrimary && !column.isGenerated);
|
||||
if (primaryKeyColumns.length > 0)
|
||||
sql1 += `, PRIMARY KEY(${primaryKeyColumns.map(column => `${column.name}`).join(", ")})`; // for some reason column escaping here generate a wrong schema
|
||||
|
||||
sql1 += ")";
|
||||
|
||||
// todo: need also create uniques and indices?
|
||||
|
||||
// recreate a table with a temporary name
|
||||
await this.query(sql1);
|
||||
|
||||
// migrate all data from the table into temporary table
|
||||
const sql2 = `INSERT INTO "temporary_${tableSchema.name}" SELECT ${columnNames} FROM "${tableSchema.name}"`;
|
||||
await this.query(sql2);
|
||||
|
||||
// drop old table
|
||||
const sql3 = `DROP TABLE "${tableSchema.name}"`;
|
||||
await this.query(sql3);
|
||||
|
||||
// rename temporary table
|
||||
const sql4 = `ALTER TABLE "temporary_${tableSchema.name}" RENAME TO "${tableSchema.name}"`;
|
||||
await this.query(sql4);
|
||||
|
||||
// also re-create indices
|
||||
const indexPromises = tableSchema.indices.map(index => this.createIndex(index));
|
||||
// const uniquePromises = tableSchema.uniqueKeys.map(key => this.createIndex(key));
|
||||
await Promise.all(indexPromises/*.concat(uniquePromises)*/);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -162,8 +162,8 @@ export abstract class BaseEntityManager {
|
||||
/**
|
||||
* Creates a new query builder that can be used to build an sql query.
|
||||
*/
|
||||
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>, alias: string): QueryBuilder<Entity> {
|
||||
return this.getRepository(entityClass).createQueryBuilder(alias);
|
||||
createQueryBuilder<Entity>(entityClass: ObjectType<Entity>|Function|string, alias: string): QueryBuilder<Entity> {
|
||||
return this.getRepository(entityClass as any).createQueryBuilder(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -239,7 +239,7 @@ export abstract class BaseEntityManager {
|
||||
if (this.queryRunnerProvider && this.queryRunnerProvider.isReleased)
|
||||
throw new QueryRunnerProviderAlreadyReleasedError();
|
||||
|
||||
const metadata = this.connection.entityMetadatas.findByTarget(entityClassOrName);
|
||||
const metadata = this.connection.getMetadata(entityClassOrName);
|
||||
let repositoryAggregator = this.repositoryAggregators.find(repositoryAggregate => repositoryAggregate.metadata === metadata);
|
||||
if (!repositoryAggregator) {
|
||||
repositoryAggregator = new RepositoryAggregator(
|
||||
|
||||
@ -70,6 +70,9 @@ export class EntityManager extends BaseEntityManager {
|
||||
return this.getRepository<Entity|Entity[]>(target).persist(entity);
|
||||
} else {
|
||||
if (target instanceof Array) {
|
||||
if (target.length === 0)
|
||||
return Promise.resolve(target);
|
||||
|
||||
return this.getRepository<Entity[]>(target[0].constructor).persist(entity as Entity[]);
|
||||
} else {
|
||||
return this.getRepository<Entity>(target.constructor).persist(entity as Entity);
|
||||
@ -124,7 +127,7 @@ export class EntityManager extends BaseEntityManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Finds entities that match given conditions.
|
||||
*/
|
||||
@ -151,10 +154,10 @@ export class EntityManager extends BaseEntityManager {
|
||||
find<Entity>(entityClass: ObjectType<Entity>, conditionsOrFindOptions?: ObjectLiteral|FindOptions, options?: FindOptions): Promise<Entity[]> {
|
||||
if (conditionsOrFindOptions && options) {
|
||||
return this.getRepository(entityClass).find(conditionsOrFindOptions, options);
|
||||
|
||||
|
||||
} else if (conditionsOrFindOptions) {
|
||||
return this.getRepository(entityClass).find(conditionsOrFindOptions);
|
||||
|
||||
|
||||
} else {
|
||||
return this.getRepository(entityClass).find();
|
||||
}
|
||||
@ -208,27 +211,27 @@ export class EntityManager extends BaseEntityManager {
|
||||
/**
|
||||
* Finds first entity that matches given conditions.
|
||||
*/
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>): Promise<Entity>;
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>): Promise<Entity|undefined>;
|
||||
|
||||
/**
|
||||
* Finds first entity that matches given conditions.
|
||||
*/
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, conditions: ObjectLiteral): Promise<Entity>;
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, conditions: ObjectLiteral): Promise<Entity|undefined>;
|
||||
|
||||
/**
|
||||
* Finds first entity that matches given conditions.
|
||||
*/
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, options: FindOptions): Promise<Entity>;
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, options: FindOptions): Promise<Entity|undefined>;
|
||||
|
||||
/**
|
||||
* Finds first entity that matches given conditions.
|
||||
*/
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, conditions: ObjectLiteral, options: FindOptions): Promise<Entity>;
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, conditions: ObjectLiteral, options: FindOptions): Promise<Entity|undefined>;
|
||||
|
||||
/**
|
||||
* Finds first entity that matches given conditions.
|
||||
*/
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, conditionsOrFindOptions?: ObjectLiteral|FindOptions, options?: FindOptions): Promise<Entity> {
|
||||
findOne<Entity>(entityClass: ObjectType<Entity>, conditionsOrFindOptions?: ObjectLiteral|FindOptions, options?: FindOptions): Promise<Entity|undefined> {
|
||||
if (conditionsOrFindOptions && options) {
|
||||
return this.getRepository(entityClass).findOne(conditionsOrFindOptions, options);
|
||||
|
||||
@ -240,10 +243,18 @@ export class EntityManager extends BaseEntityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds entities with ids.
|
||||
* Optionally find options can be applied.
|
||||
*/
|
||||
findByIds<Entity>(entityClass: ObjectType<Entity>, ids: any[], options?: FindOptions): Promise<Entity[]> {
|
||||
return this.getRepository(entityClass).findByIds(ids, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds entity with given id.
|
||||
*/
|
||||
findOneById<Entity>(entityClass: ObjectType<Entity>, id: any, options?: FindOptions): Promise<Entity> {
|
||||
findOneById<Entity>(entityClass: ObjectType<Entity>, id: any, options?: FindOptions): Promise<Entity|undefined> {
|
||||
return this.getRepository(entityClass).findOneById(id, options);
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@ export class NoNeedToReleaseEntityManagerError extends Error {
|
||||
constructor() {
|
||||
super();
|
||||
this.message = `Entity manager is not using single database connection and cannot be released. ` +
|
||||
`Only entity managers created by connection#createEntityManagerWithSingleDatabaseConnection ` +
|
||||
`methods have a single database connection and they should be released.`;
|
||||
`Only entity managers created by connection#createEntityManagerWithSingleDatabaseConnection ` +
|
||||
`methods have a single database connection and they should be released.`;
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ export interface EntitySchema {
|
||||
* Target bind to this entity schema. Optional.
|
||||
*/
|
||||
target?: Function;
|
||||
|
||||
|
||||
/**
|
||||
* Entity name.
|
||||
*/
|
||||
@ -143,7 +143,7 @@ export interface EntitySchema {
|
||||
* Column collation. Note that not all databases support it.
|
||||
*/
|
||||
collation?: string; // todo: looks like this is not used
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@ -188,12 +188,12 @@ export interface EntitySchema {
|
||||
* First column of the join table.
|
||||
*/
|
||||
joinColumn?: JoinColumnOptions;
|
||||
|
||||
|
||||
/**
|
||||
* Second (inverse) column of the join table.
|
||||
*/
|
||||
inverseJoinColumn?: JoinColumnOptions;
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -257,7 +257,7 @@ export interface EntitySchema {
|
||||
* Database cascade action on delete.
|
||||
*/
|
||||
onDelete?: OnDeleteType;
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
|
||||
/**
|
||||
* Options to be passed to find methods.
|
||||
*
|
||||
*
|
||||
* Example:
|
||||
* const options: FindOptions = {
|
||||
* alias: "photo",
|
||||
@ -90,7 +90,7 @@ export interface FindOptions {
|
||||
having?: string;
|
||||
|
||||
/**
|
||||
* WHERE conditions. Key-value object pair, where each key is a column name and value is a column value.
|
||||
* WHERE conditions. Key-value object pair, where each key is a column name and value is a column value.
|
||||
* "AND" is applied between all parameters.
|
||||
*/
|
||||
whereConditions?: ObjectLiteral;
|
||||
@ -115,7 +115,7 @@ export interface FindOptions {
|
||||
* Array of columns to LEFT JOIN.
|
||||
*/
|
||||
leftJoinAndSelect?: { [key: string]: string };
|
||||
|
||||
|
||||
/**
|
||||
* Array of columns to INNER JOIN.
|
||||
*/
|
||||
@ -135,5 +135,10 @@ export interface FindOptions {
|
||||
* Parameters used in the WHERE and HAVING expressions.
|
||||
*/
|
||||
parameters?: Object;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if query builder should add virtual columns to the entity too.
|
||||
*/
|
||||
enabledOptions?: ("RELATION_ID_VALUES")[];
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import {FindOptions} from "./FindOptions";
|
||||
import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
|
||||
@ -27,7 +26,8 @@ export class FindOptionsUtils {
|
||||
!!possibleOptions.innerJoinAndSelect ||
|
||||
!!possibleOptions.leftJoin ||
|
||||
!!possibleOptions.innerJoin ||
|
||||
!!possibleOptions.parameters
|
||||
!!possibleOptions.parameters ||
|
||||
!!possibleOptions.enabledOptions
|
||||
);
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ export class FindOptionsUtils {
|
||||
* Applies give find options to the given query builder.
|
||||
*/
|
||||
static applyOptionsToQueryBuilder(qb: QueryBuilder<any>, options: FindOptions): QueryBuilder<any> {
|
||||
|
||||
|
||||
if (options.limit)
|
||||
qb.setLimit(options.limit);
|
||||
if (options.offset)
|
||||
@ -48,47 +48,47 @@ export class FindOptionsUtils {
|
||||
qb.where(options.where);
|
||||
if (options.having)
|
||||
qb.having(options.having);
|
||||
|
||||
|
||||
if (options.whereConditions) {
|
||||
Object.keys(options.whereConditions).forEach(key => {
|
||||
const name = key.indexOf(".") === -1 ? options.alias + "." + key : key;
|
||||
qb.andWhere(name + "=:" + key);
|
||||
});
|
||||
qb.addParameters(options.whereConditions);
|
||||
qb.setParameters(options.whereConditions);
|
||||
}
|
||||
|
||||
|
||||
if (options.havingConditions) {
|
||||
Object.keys(options.havingConditions).forEach(key => {
|
||||
const name = key.indexOf(".") === -1 ? options.alias + "." + key : key;
|
||||
qb.andHaving(name + "=:" + key);
|
||||
});
|
||||
qb.addParameters(options.havingConditions);
|
||||
qb.setParameters(options.havingConditions);
|
||||
}
|
||||
|
||||
|
||||
if (options.orderBy)
|
||||
Object.keys(options.orderBy).forEach(columnName => qb.addOrderBy(columnName, options.orderBy![columnName]));
|
||||
|
||||
if (options.groupBy)
|
||||
options.groupBy.forEach(groupBy => qb.addGroupBy(groupBy));
|
||||
|
||||
|
||||
if (options.leftJoin)
|
||||
Object.keys(options.leftJoin).forEach(key => {
|
||||
if (options.leftJoin) // this check because of tsc bug
|
||||
qb.leftJoin(options.leftJoin[key], key);
|
||||
});
|
||||
|
||||
|
||||
if (options.innerJoin)
|
||||
Object.keys(options.innerJoin).forEach(key => {
|
||||
if (options.innerJoin) // this check because of tsc bug
|
||||
qb.innerJoin(options.innerJoin[key], key);
|
||||
});
|
||||
|
||||
|
||||
if (options.leftJoinAndSelect)
|
||||
Object.keys(options.leftJoinAndSelect).forEach(key => {
|
||||
if (options.leftJoinAndSelect) // this check because of tsc bug
|
||||
qb.leftJoinAndSelect(options.leftJoinAndSelect[key], key);
|
||||
});
|
||||
|
||||
|
||||
if (options.innerJoinAndSelect)
|
||||
Object.keys(options.innerJoinAndSelect).forEach(key => {
|
||||
if (options.innerJoinAndSelect) // this check because of tsc bug
|
||||
@ -96,9 +96,15 @@ export class FindOptionsUtils {
|
||||
});
|
||||
|
||||
if (options.parameters)
|
||||
qb.addParameters(options.parameters);
|
||||
|
||||
qb.setParameters(options.parameters);
|
||||
|
||||
if (options.enabledOptions) {
|
||||
options.enabledOptions.forEach(option => {
|
||||
qb.enableOption(option);
|
||||
});
|
||||
}
|
||||
|
||||
return qb;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
/*!
|
||||
*/
|
||||
|
||||
import {DriverOptions} from "./driver/DriverOptions";
|
||||
import {ConnectionManager} from "./connection/ConnectionManager";
|
||||
import {Connection} from "./connection/Connection";
|
||||
@ -113,7 +112,7 @@ export function getConnectionManager(): ConnectionManager {
|
||||
* it will try to create connection from environment variables.
|
||||
* There are several environment variables you can set:
|
||||
*
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_URL - database connection url. Should be a string.
|
||||
* - TYPEORM_HOST - database host. Should be a string.
|
||||
* - TYPEORM_PORT - database access port. Should be a number.
|
||||
@ -164,7 +163,7 @@ export function createConnection(optionsOrConnectionNameFromConfig?: ConnectionO
|
||||
* it will try to create connection from environment variables.
|
||||
* There are several environment variables you can set:
|
||||
*
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "mysql2", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_DRIVER_TYPE - driver type. Can be "mysql", "postgres", "mariadb", "sqlite", "oracle" or "mssql".
|
||||
* - TYPEORM_URL - database connection url. Should be a string.
|
||||
* - TYPEORM_HOST - database host. Should be a string.
|
||||
* - TYPEORM_PORT - database access port. Should be a number.
|
||||
|
||||
@ -2,76 +2,106 @@ import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {QueryBuilder} from "../query-builder/QueryBuilder";
|
||||
import {Connection} from "../connection/Connection";
|
||||
|
||||
/**
|
||||
* This class wraps entities and provides functions there to lazily load its relations.
|
||||
*/
|
||||
export class LazyRelationsWrapper {
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private connection: Connection) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
wrap(object: Object, relation: RelationMetadata) {
|
||||
const connection = this.connection;
|
||||
const index = "__" + relation.propertyName + "__";
|
||||
const loadIndex = "__load_" + relation.propertyName + "__";
|
||||
const resolveIndex = "__has_" + relation.propertyName + "__";
|
||||
|
||||
const promiseIndex = "__promise__" + relation.propertyName + "__";
|
||||
const resolveIndex = "__has__" + relation.propertyName + "__";
|
||||
|
||||
Object.defineProperty(object, relation.propertyName, {
|
||||
get: function() {
|
||||
if (this[resolveIndex] === true)
|
||||
return Promise.resolve(this[index]);
|
||||
if (this[loadIndex])
|
||||
return this[loadIndex];
|
||||
if (this[promiseIndex])
|
||||
return this[promiseIndex];
|
||||
|
||||
// create shortcuts for better readability
|
||||
const escapeAlias = (alias: string) => connection.driver.escapeAliasName(alias);
|
||||
const escapeColumn = (column: string) => connection.driver.escapeColumnName(column);
|
||||
|
||||
const qb = new QueryBuilder(connection);
|
||||
if (relation.isManyToMany || relation.isOneToMany) {
|
||||
if (relation.isManyToMany) {
|
||||
|
||||
if (relation.hasInverseSide) { // if we don't have inverse side then we can't select and join by relation from inverse side
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName);
|
||||
} else {
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.type, relation.propertyName)
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, relation.junctionEntityMetadata.name, "ON",
|
||||
`${relation.junctionEntityMetadata.name}.${relation.name}=:${relation.propertyName}Id`)
|
||||
.setParameter(relation.propertyName + "Id", this[relation.referencedColumnName]);
|
||||
}
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.type, relation.propertyName)
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, relation.junctionEntityMetadata.name,
|
||||
`${escapeAlias(relation.junctionEntityMetadata.name)}.${escapeColumn(relation.name)}=:${relation.propertyName}Id`)
|
||||
.setParameter(relation.propertyName + "Id", this[relation.referencedColumnName]);
|
||||
|
||||
this[loadIndex] = qb.getResults().then(results => {
|
||||
this[promiseIndex] = qb.getMany().then(results => {
|
||||
this[index] = results;
|
||||
this[resolveIndex] = true;
|
||||
delete this[loadIndex];
|
||||
delete this[promiseIndex];
|
||||
return this[index];
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
return this[loadIndex];
|
||||
return this[promiseIndex];
|
||||
|
||||
} else if (relation.isOneToMany) {
|
||||
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName)
|
||||
.andWhereInIds([relation.entityMetadata.getEntityIdMixedMap(this)]);
|
||||
|
||||
this[promiseIndex] = qb.getMany().then(results => {
|
||||
this[index] = results;
|
||||
this[resolveIndex] = true;
|
||||
delete this[promiseIndex];
|
||||
return this[index];
|
||||
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
return this[promiseIndex];
|
||||
|
||||
} else {
|
||||
|
||||
if (relation.hasInverseSide) {
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName);
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName)
|
||||
.andWhereInIds([relation.entityMetadata.getEntityIdMixedMap(this)]);
|
||||
|
||||
} else {
|
||||
// (ow) post.category<=>category.post
|
||||
// loaded: category from post
|
||||
// example: SELECT category.id AS category_id, category.name AS category_name FROM category category
|
||||
// INNER JOIN post Post ON Post.category=category.id WHERE Post.id=1
|
||||
qb.select(relation.propertyName) // category
|
||||
.from(relation.type, relation.propertyName) // Category, category
|
||||
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name, "ON",
|
||||
`${relation.entityMetadata.name}.${relation.propertyName}=:${relation.propertyName}Id`) // Post, post, post.category = categoryId
|
||||
.setParameter(relation.propertyName + "Id", this[relation.referencedColumnName]);
|
||||
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name,
|
||||
`${escapeAlias(relation.entityMetadata.name)}.${escapeColumn(relation.propertyName)}=${escapeAlias(relation.propertyName)}.${escapeColumn(relation.referencedColumn.propertyName)}`)
|
||||
.andWhereInIds([relation.entityMetadata.getEntityIdMixedMap(this)]);
|
||||
}
|
||||
// console.log(qb.getSql());
|
||||
this[loadIndex] = qb.getSingleResult().then(result => {
|
||||
|
||||
this[promiseIndex] = qb.getOne().then(result => {
|
||||
this[index] = result;
|
||||
this[resolveIndex] = true;
|
||||
delete this[loadIndex];
|
||||
delete this[promiseIndex];
|
||||
return this[index];
|
||||
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
return this[loadIndex];
|
||||
return this[promiseIndex];
|
||||
}
|
||||
},
|
||||
set: function(promise: Promise<any>) {
|
||||
@ -88,5 +118,5 @@ export class LazyRelationsWrapper {
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import {LoggerOptions} from "./LoggerOptions";
|
||||
import {PlatformTools} from "../platform/PlatformTools";
|
||||
|
||||
/**
|
||||
* Performs logging of the events in TypeORM.
|
||||
@ -21,8 +22,8 @@ export class Logger {
|
||||
*/
|
||||
logQuery(query: string, parameters?: any[]) {
|
||||
if (this.options.logQueries ||
|
||||
process.env.LOGGER_CLI_SCHEMA_SYNC)
|
||||
this.log("log", `executing query: ${query}${parameters && parameters.length ? " -- PARAMETERS: " + JSON.stringify(parameters) : ""}`);
|
||||
PlatformTools.getEnvVariable("LOGGER_CLI_SCHEMA_SYNC"))
|
||||
this.log("log", `executing query: ${query}${parameters && parameters.length ? " -- PARAMETERS: " + this.stringifyParams(parameters) : ""}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,8 +32,8 @@ export class Logger {
|
||||
logFailedQuery(query: string, parameters?: any[]) {
|
||||
if (this.options.logQueries ||
|
||||
this.options.logOnlyFailedQueries ||
|
||||
process.env.LOGGER_CLI_SCHEMA_SYNC)
|
||||
this.log("error", `query failed: ${query}${parameters && parameters.length ? " -- PARAMETERS: " + JSON.stringify(parameters) : ""}`);
|
||||
PlatformTools.getEnvVariable("LOGGER_CLI_SCHEMA_SYNC"))
|
||||
this.log("error", `query failed: ${query}${parameters && parameters.length ? " -- PARAMETERS: " + this.stringifyParams(parameters) : ""}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +41,7 @@ export class Logger {
|
||||
*/
|
||||
logQueryError(error: any) {
|
||||
if (this.options.logFailedQueryError ||
|
||||
process.env.LOGGER_CLI_SCHEMA_SYNC)
|
||||
PlatformTools.getEnvVariable("LOGGER_CLI_SCHEMA_SYNC"))
|
||||
this.log("error", "error during executing query:" + error);
|
||||
}
|
||||
|
||||
@ -49,7 +50,7 @@ export class Logger {
|
||||
*/
|
||||
logSchemaBuild(message: string) {
|
||||
if (this.options.logSchemaCreation ||
|
||||
process.env.LOGGER_CLI_SCHEMA_SYNC)
|
||||
PlatformTools.getEnvVariable("LOGGER_CLI_SCHEMA_SYNC"))
|
||||
this.log("info", message);
|
||||
}
|
||||
|
||||
@ -80,4 +81,21 @@ export class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Converts parameters to a string.
|
||||
* Sometimes parameters can have circular objects and therefor we are handle this case too.
|
||||
*/
|
||||
protected stringifyParams(parameters: any[]) {
|
||||
try {
|
||||
return JSON.stringify(parameters);
|
||||
|
||||
} catch (error) { // most probably circular objects in parameters
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -18,14 +18,14 @@ export interface ColumnMetadataArgs {
|
||||
|
||||
/**
|
||||
* Class's property type (reflected) to which column is applied.
|
||||
*
|
||||
*
|
||||
* todo: check when this is not set, because for the entity schemas we don't set it.
|
||||
*/
|
||||
readonly propertyType?: string;
|
||||
|
||||
/**
|
||||
* Column mode in which column will work.
|
||||
*
|
||||
*
|
||||
* todo: find name better then "mode".
|
||||
*/
|
||||
readonly mode: ColumnMode;
|
||||
@ -34,5 +34,5 @@ export interface ColumnMetadataArgs {
|
||||
* Extra column options.
|
||||
*/
|
||||
readonly options: ColumnOptions;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -12,5 +12,5 @@ export interface DiscriminatorValueMetadataArgs {
|
||||
* Discriminator value.
|
||||
*/
|
||||
readonly value: any;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -17,5 +17,5 @@ export interface EmbeddedMetadataArgs {
|
||||
* Type of the class to be embedded.
|
||||
*/
|
||||
readonly type: ((type?: any) => Function);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -19,5 +19,5 @@ export interface EntityListenerMetadataArgs {
|
||||
* The type of the listener.
|
||||
*/
|
||||
readonly type: EventListenerType;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import {EmbeddedMetadata} from "../metadata/EmbeddedMetadata";
|
||||
*/
|
||||
export interface EntityMetadataArgs {
|
||||
|
||||
readonly junction: boolean;
|
||||
readonly target: Function|string;
|
||||
readonly tablesPrefix?: string;
|
||||
readonly inheritanceType?: "single-table"|"class-table";
|
||||
@ -22,5 +23,5 @@ export interface EntityMetadataArgs {
|
||||
readonly indexMetadatas?: IndexMetadata[];
|
||||
readonly foreignKeyMetadatas?: ForeignKeyMetadata[];
|
||||
readonly embeddedMetadatas?: EmbeddedMetadata[];
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Arguments for EntitySubscriberMetadata class.
|
||||
*/
|
||||
@ -8,5 +7,5 @@ export interface EntitySubscriberMetadataArgs {
|
||||
* Class to which subscriber is applied.
|
||||
*/
|
||||
readonly target: Function;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Arguments for IndexMetadata class.
|
||||
*/
|
||||
@ -23,5 +22,5 @@ export interface IndexMetadataArgs {
|
||||
* Indicates if index must be unique or not.
|
||||
*/
|
||||
readonly unique: boolean;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -12,5 +12,5 @@ export interface InheritanceMetadataArgs {
|
||||
* Inheritance type.
|
||||
*/
|
||||
readonly type: "single-table"|"class-table";
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -22,5 +22,5 @@ export interface JoinColumnMetadataArgs {
|
||||
* Name of the column in the entity to which this column is referenced.
|
||||
*/
|
||||
readonly referencedColumnName?: string;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -30,5 +30,5 @@ export interface JoinTableMetadataArgs {
|
||||
* Second (inverse) column of the join table.
|
||||
*/
|
||||
readonly inverseJoinColumn?: JoinColumnOptions;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ export class MetadataArgsStorage {
|
||||
const allTableMetadataArgs = classes ? this.tables.filterByTargets(classes) : this.tables;
|
||||
const tableMetadatas = allTableMetadataArgs.filter(table => table.type === "regular" || table.type === "closure" || table.type === "class-table-child");
|
||||
|
||||
return tableMetadatas.map(tableMetadata => {
|
||||
return tableMetadatas.toArray().map(tableMetadata => {
|
||||
return this.mergeWithAbstract(allTableMetadataArgs, tableMetadata);
|
||||
});
|
||||
}
|
||||
@ -68,7 +68,7 @@ export class MetadataArgsStorage {
|
||||
const tables = classes ? this.tables.filterByTargets(classes) : this.tables;
|
||||
const embeddableTableMetadatas = tables.filter(table => table.type === "embeddable");
|
||||
|
||||
return embeddableTableMetadatas.map(embeddableTableMetadata => {
|
||||
return embeddableTableMetadatas.toArray().map(embeddableTableMetadata => {
|
||||
return this.mergeWithEmbeddable(embeddableTableMetadatas, embeddableTableMetadata);
|
||||
});
|
||||
}
|
||||
@ -79,8 +79,8 @@ export class MetadataArgsStorage {
|
||||
|
||||
/**
|
||||
*/
|
||||
private mergeWithAbstract(allTableMetadatas: TargetMetadataArgsCollection<TableMetadataArgs>,
|
||||
table: TableMetadataArgs) {
|
||||
protected mergeWithAbstract(allTableMetadatas: TargetMetadataArgsCollection<TableMetadataArgs>,
|
||||
table: TableMetadataArgs) {
|
||||
|
||||
const indices = this.indices.filterByTarget(table.target);
|
||||
const columns = this.columns.filterByTarget(table.target);
|
||||
@ -92,21 +92,21 @@ export class MetadataArgsStorage {
|
||||
const relationIds = this.relationIds.filterByTarget(table.target);
|
||||
const embeddeds = this.embeddeds.filterByTarget(table.target);
|
||||
const inheritances = this.inheritances.filterByTarget(table.target);
|
||||
const inheritance = (inheritances.length > 0) ? inheritances[0] : undefined;
|
||||
const inheritance = (inheritances.length > 0) ? inheritances.toArray()[0] : undefined;
|
||||
const discriminatorValues: DiscriminatorValueMetadataArgs[] = [];
|
||||
|
||||
// find parent if this table is class-table-child
|
||||
let parent: TableMetadataArgs|undefined = undefined;
|
||||
|
||||
// merge metadata from abstract tables
|
||||
allTableMetadatas.forEach(inheritedTable => {
|
||||
allTableMetadatas.toArray().forEach(inheritedTable => {
|
||||
if (table.type === "single-table-child") return;
|
||||
if (!table.target || !inheritedTable.target) return;
|
||||
if (!(table.target instanceof Function) || !(inheritedTable.target instanceof Function)) return;
|
||||
if (!this.isInherited(table.target, inheritedTable.target)) return;
|
||||
|
||||
// check if inheritedTable is a class with class table inheritance - then we don't need to merge its columns, relations, etc. things
|
||||
if (!!this.inheritances.filterByTarget(inheritedTable.target).find(inheritance => inheritance.type === "class-table")) {
|
||||
if (!!this.inheritances.filterByTarget(inheritedTable.target).toArray().find(inheritance => inheritance.type === "class-table")) {
|
||||
parent = inheritedTable;
|
||||
return;
|
||||
}
|
||||
@ -114,36 +114,44 @@ export class MetadataArgsStorage {
|
||||
const metadatasFromAbstract = this.mergeWithAbstract(allTableMetadatas, inheritedTable);
|
||||
|
||||
metadatasFromAbstract.columns
|
||||
.filterRepeatedMetadatas(columns)
|
||||
.forEach(metadata => columns.push(metadata));
|
||||
.filterRepeatedMetadatas(columns.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => columns.add(metadata));
|
||||
|
||||
metadatasFromAbstract.relations
|
||||
.filterRepeatedMetadatas(relations)
|
||||
.forEach(metadata => relations.push(metadata));
|
||||
.filterRepeatedMetadatas(relations.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => relations.add(metadata));
|
||||
|
||||
metadatasFromAbstract.joinColumns
|
||||
.filterRepeatedMetadatas(joinColumns)
|
||||
.forEach(metadata => joinColumns.push(metadata));
|
||||
.filterRepeatedMetadatas(joinColumns.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => joinColumns.add(metadata));
|
||||
|
||||
metadatasFromAbstract.joinTables
|
||||
.filterRepeatedMetadatas(joinTables)
|
||||
.forEach(metadata => joinTables.push(metadata));
|
||||
.filterRepeatedMetadatas(joinTables.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => joinTables.add(metadata));
|
||||
|
||||
metadatasFromAbstract.entityListeners
|
||||
.filterRepeatedMetadatas(entityListeners)
|
||||
.forEach(metadata => entityListeners.push(metadata));
|
||||
.filterRepeatedMetadatas(entityListeners.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => entityListeners.add(metadata));
|
||||
|
||||
metadatasFromAbstract.relationCounts
|
||||
.filterRepeatedMetadatas(relationCounts)
|
||||
.forEach(metadata => relationCounts.push(metadata));
|
||||
.filterRepeatedMetadatas(relationCounts.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => relationCounts.add(metadata));
|
||||
|
||||
metadatasFromAbstract.relationIds
|
||||
.filterRepeatedMetadatas(relationIds)
|
||||
.forEach(metadata => relationIds.push(metadata));
|
||||
.filterRepeatedMetadatas(relationIds.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => relationIds.add(metadata));
|
||||
|
||||
metadatasFromAbstract.embeddeds
|
||||
.filterRepeatedMetadatas(embeddeds)
|
||||
.forEach(metadata => embeddeds.push(metadata));
|
||||
.filterRepeatedMetadatas(embeddeds.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => embeddeds.add(metadata));
|
||||
|
||||
});
|
||||
|
||||
@ -151,7 +159,7 @@ export class MetadataArgsStorage {
|
||||
const children: TableMetadataArgs[] = [];
|
||||
|
||||
if (inheritance && inheritance.type === "single-table") {
|
||||
allTableMetadatas.forEach(childTable => {
|
||||
allTableMetadatas.toArray().forEach(childTable => {
|
||||
if (childTable.type !== "single-table-child") return;
|
||||
if (!childTable.target || !table.target) return;
|
||||
if (!(childTable.target instanceof Function) || !(table.target instanceof Function)) return;
|
||||
@ -160,6 +168,7 @@ export class MetadataArgsStorage {
|
||||
children.push(childTable);
|
||||
this.discriminatorValues
|
||||
.filterByTarget(childTable.target)
|
||||
.toArray()
|
||||
.forEach(metadata => discriminatorValues.push(metadata));
|
||||
|
||||
// for single table inheritance we also merge all columns, relation, etc. into same table
|
||||
@ -167,36 +176,44 @@ export class MetadataArgsStorage {
|
||||
const metadatasFromAbstract = this.mergeWithAbstract(allTableMetadatas, childTable);
|
||||
|
||||
metadatasFromAbstract.columns
|
||||
.filterRepeatedMetadatas(columns)
|
||||
.forEach(metadata => columns.push(metadata));
|
||||
.filterRepeatedMetadatas(columns.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => columns.add(metadata));
|
||||
|
||||
metadatasFromAbstract.relations
|
||||
.filterRepeatedMetadatas(relations)
|
||||
.forEach(metadata => relations.push(metadata));
|
||||
.filterRepeatedMetadatas(relations.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => relations.add(metadata));
|
||||
|
||||
metadatasFromAbstract.joinColumns
|
||||
.filterRepeatedMetadatas(joinColumns)
|
||||
.forEach(metadata => joinColumns.push(metadata));
|
||||
.filterRepeatedMetadatas(joinColumns.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => joinColumns.add(metadata));
|
||||
|
||||
metadatasFromAbstract.joinTables
|
||||
.filterRepeatedMetadatas(joinTables)
|
||||
.forEach(metadata => joinTables.push(metadata));
|
||||
.filterRepeatedMetadatas(joinTables.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => joinTables.add(metadata));
|
||||
|
||||
metadatasFromAbstract.entityListeners
|
||||
.filterRepeatedMetadatas(entityListeners)
|
||||
.forEach(metadata => entityListeners.push(metadata));
|
||||
.filterRepeatedMetadatas(entityListeners.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => entityListeners.add(metadata));
|
||||
|
||||
metadatasFromAbstract.relationCounts
|
||||
.filterRepeatedMetadatas(relationCounts)
|
||||
.forEach(metadata => relationCounts.push(metadata));
|
||||
.filterRepeatedMetadatas(relationCounts.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => relationCounts.add(metadata));
|
||||
|
||||
metadatasFromAbstract.relationIds
|
||||
.filterRepeatedMetadatas(relationIds)
|
||||
.forEach(metadata => relationIds.push(metadata));
|
||||
.filterRepeatedMetadatas(relationIds.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => relationIds.add(metadata));
|
||||
|
||||
metadatasFromAbstract.embeddeds
|
||||
.filterRepeatedMetadatas(embeddeds)
|
||||
.forEach(metadata => embeddeds.push(metadata));
|
||||
.filterRepeatedMetadatas(embeddeds.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => embeddeds.add(metadata));
|
||||
|
||||
metadatasFromAbstract.children
|
||||
.forEach(metadata => children.push(metadata));
|
||||
@ -221,11 +238,11 @@ export class MetadataArgsStorage {
|
||||
discriminatorValues: discriminatorValues
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
private mergeWithEmbeddable(allTableMetadatas: TargetMetadataArgsCollection<TableMetadataArgs>,
|
||||
tableMetadata: TableMetadataArgs) {
|
||||
protected mergeWithEmbeddable(allTableMetadatas: TargetMetadataArgsCollection<TableMetadataArgs>,
|
||||
tableMetadata: TableMetadataArgs) {
|
||||
const columns = this.columns.filterByTarget(tableMetadata.target);
|
||||
|
||||
allTableMetadatas
|
||||
@ -234,12 +251,14 @@ export class MetadataArgsStorage {
|
||||
if (!(tableMetadata.target instanceof Function) || !(metadata.target instanceof Function)) return false;
|
||||
return this.isInherited(tableMetadata.target, metadata.target); // todo: fix it for entity schema
|
||||
})
|
||||
.toArray()
|
||||
.forEach(parentMetadata => {
|
||||
const metadatasFromParents = this.mergeWithEmbeddable(allTableMetadatas, parentMetadata);
|
||||
|
||||
metadatasFromParents.columns
|
||||
.filterRepeatedMetadatas(columns)
|
||||
.forEach(metadata => columns.push(metadata));
|
||||
.filterRepeatedMetadatas(columns.toArray())
|
||||
.toArray()
|
||||
.forEach(metadata => columns.add(metadata));
|
||||
});
|
||||
|
||||
return {
|
||||
@ -251,12 +270,11 @@ export class MetadataArgsStorage {
|
||||
/**
|
||||
* Checks if this table is inherited from another table.
|
||||
*/
|
||||
private isInherited(target1: Function, target2: Function) {
|
||||
protected isInherited(target1: Function, target2: Function) {
|
||||
// we cannot use instanceOf in this method, because we need order of inherited tables, to ensure that
|
||||
// properties get inherited in a right order. To achieve it we can only check a first parent of the class
|
||||
// return this.target.prototype instanceof anotherTable.target;
|
||||
return Object.getPrototypeOf(target1.prototype).constructor === target2;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -12,5 +12,5 @@ export interface NamingStrategyMetadataArgs {
|
||||
* Strategy name.
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -17,5 +17,5 @@ export interface RelationCountMetadataArgs {
|
||||
* Target's relation which it should count.
|
||||
*/
|
||||
readonly relation: string|((object: any) => any);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -17,5 +17,5 @@ export interface RelationIdMetadataArgs {
|
||||
* Target's relation which it should count.
|
||||
*/
|
||||
readonly relation: string|((object: any) => any);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export interface RelationMetadataArgs {
|
||||
|
||||
/**
|
||||
* Original (reflected) class's property type.
|
||||
*
|
||||
*
|
||||
* todo: this can be empty for relations from entity schemas.
|
||||
*/
|
||||
readonly propertyType?: any;
|
||||
@ -65,5 +65,5 @@ export interface RelationMetadataArgs {
|
||||
* Indicates if this is a children (can be only one-to-many relation) relation in the tree tables.
|
||||
*/
|
||||
readonly isTreeChildren?: boolean;
|
||||
|
||||
|
||||
}
|
||||
@ -38,5 +38,5 @@ export interface TableMetadataArgs {
|
||||
* Whether table must be synced during schema build or not
|
||||
*/
|
||||
readonly skipSchemaSync?: boolean;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
import {EntityMetadata} from "../../metadata/EntityMetadata";
|
||||
import {EntityMetadataNotFound} from "../error/EntityMetadataNotFound";
|
||||
|
||||
/**
|
||||
* Array for the entity metadatas.
|
||||
*/
|
||||
export class EntityMetadataCollection extends Array<EntityMetadata> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
hasTarget(target: Function|string) {
|
||||
return !!this.find(metadata => metadata.target === target || (typeof target === "string" && metadata.targetName === target));
|
||||
}
|
||||
|
||||
findByTarget(target: Function|string): EntityMetadata {
|
||||
const metadata = this.find(metadata => metadata.target === target || (typeof target === "string" && metadata.targetName === target));
|
||||
if (!metadata)
|
||||
throw new EntityMetadataNotFound(target);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
findByName(name: string) {
|
||||
const metadata = this.find(metadata => metadata.name === name);
|
||||
if (!metadata)
|
||||
throw new EntityMetadataNotFound(name);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
filter(callbackfn: (value: EntityMetadata, index?: number, array?: Array<EntityMetadata>) => any, thisArg?: any): EntityMetadataCollection {
|
||||
thisArg = thisArg || void 0;
|
||||
return this.reduce(function(out: EntityMetadataCollection, val: EntityMetadata, index: number, array: Array<EntityMetadata>) {
|
||||
if (callbackfn.call(thisArg, val, index, array)) {
|
||||
out.push(val);
|
||||
}
|
||||
return out;
|
||||
}, new EntityMetadataCollection());
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import {TargetMetadataArgsCollection} from "./TargetMetadataArgsCollection";
|
||||
|
||||
export class PropertyMetadataArgsCollection<T extends { target?: Function|string, propertyName?: string }> extends TargetMetadataArgsCollection<T> {
|
||||
export class PropertyMetadataArgsCollection<T extends { target?: Function|string, propertyName?: string }> extends TargetMetadataArgsCollection<T> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
@ -13,7 +13,7 @@ export class PropertyMetadataArgsCollection<T extends { target?: Function|string
|
||||
}
|
||||
|
||||
findByProperty(propertyName: string) {
|
||||
return this.find(item => item.propertyName === propertyName);
|
||||
return this.items.find(item => item.propertyName === propertyName);
|
||||
}
|
||||
|
||||
hasWithProperty(propertyName: string) {
|
||||
|
||||
@ -1,24 +1,37 @@
|
||||
import {MetadataAlreadyExistsError} from "../../metadata-builder/error/MetadataAlreadyExistsError";
|
||||
|
||||
export class TargetMetadataArgsCollection<T extends { target?: Function|string }> extends Array<T> {
|
||||
|
||||
export class TargetMetadataArgsCollection<T extends { target?: Function|string }> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
protected items: T[] = [];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
get length() {
|
||||
return this.items.length;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): this {
|
||||
const collection = new (<any> this.constructor)();
|
||||
super.filter(callbackfn)
|
||||
.forEach(metadata => collection.add(metadata));
|
||||
this.items.filter(callbackfn).forEach(metadata => collection.add(metadata));
|
||||
return collection;
|
||||
}
|
||||
|
||||
filterByTarget(cls?: Function|string): this {
|
||||
|
||||
|
||||
// if no class specified then simply return empty collection
|
||||
if (!cls)
|
||||
return new (<any> this.constructor)();
|
||||
|
||||
|
||||
return this.filterByTargets([cls]);
|
||||
}
|
||||
|
||||
@ -38,15 +51,19 @@ export class TargetMetadataArgsCollection<T extends { target?: Function|string }
|
||||
throw new MetadataAlreadyExistsError((<any> metadata.constructor).name, metadata.target);
|
||||
}
|
||||
|
||||
this.push(metadata);
|
||||
this.items.push(metadata);
|
||||
}
|
||||
|
||||
|
||||
toArray() {
|
||||
return this.items.map(item => item);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private hasWithTarget(constructor: Function): boolean {
|
||||
return !!this.find(metadata => metadata.target === constructor);
|
||||
return !!this.items.find(metadata => metadata.target === constructor);
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,7 +23,7 @@ export interface ClosureJunctionEntityMetadataBuilderArgs {
|
||||
* Helps to create EntityMetadatas for junction tables for closure tables.
|
||||
*/
|
||||
export class ClosureJunctionEntityMetadataBuilder {
|
||||
|
||||
|
||||
build(driver: Driver, lazyRelationsWrapper: LazyRelationsWrapper, args: ClosureJunctionEntityMetadataBuilderArgs) {
|
||||
|
||||
const columns = [
|
||||
@ -37,7 +37,7 @@ export class ClosureJunctionEntityMetadataBuilder {
|
||||
type: args.primaryColumn.type,
|
||||
name: "ancestor"
|
||||
}
|
||||
}),
|
||||
}),
|
||||
new ColumnMetadata(<ColumnMetadataArgs> {
|
||||
target: "__virtual__",
|
||||
propertyName: "__virtual__",
|
||||
@ -50,7 +50,7 @@ export class ClosureJunctionEntityMetadataBuilder {
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
|
||||
if (args.hasTreeLevelColumn) {
|
||||
columns.push(new ColumnMetadata(<ColumnMetadataArgs> {
|
||||
target: "__virtual__",
|
||||
@ -71,6 +71,7 @@ export class ClosureJunctionEntityMetadataBuilder {
|
||||
});
|
||||
|
||||
return new EntityMetadata({
|
||||
junction: true,
|
||||
target: "__virtual__",
|
||||
tablesPrefix: driver.options.tablesPrefix,
|
||||
namingStrategy: args.namingStrategy,
|
||||
@ -82,5 +83,5 @@ export class ClosureJunctionEntityMetadataBuilder {
|
||||
]
|
||||
}, lazyRelationsWrapper);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -34,7 +34,7 @@ export class EntityMetadataBuilder {
|
||||
// todo: check if multiple tree parent metadatas in validator
|
||||
// todo: tree decorators can be used only on closure table (validation)
|
||||
// todo: throw error if parent tree metadata was not specified in a closure table
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
@ -47,7 +47,7 @@ export class EntityMetadataBuilder {
|
||||
|
||||
// extract into separate class?
|
||||
schemas.forEach(schema => {
|
||||
|
||||
|
||||
// add table metadata args from the schema
|
||||
const tableSchema = schema.table || {} as any;
|
||||
const table: TableMetadataArgs = {
|
||||
@ -58,7 +58,7 @@ export class EntityMetadataBuilder {
|
||||
orderBy: tableSchema.orderBy
|
||||
};
|
||||
metadataArgsStorage.tables.add(table);
|
||||
|
||||
|
||||
// add columns metadata args from the schema
|
||||
Object.keys(schema.columns).forEach(columnName => {
|
||||
const columnSchema = schema.columns[columnName];
|
||||
@ -73,7 +73,7 @@ export class EntityMetadataBuilder {
|
||||
mode = "treeChildrenCount";
|
||||
if (columnSchema.treeLevel)
|
||||
mode = "treeLevel";
|
||||
|
||||
|
||||
const column: ColumnMetadataArgs = {
|
||||
target: schema.target || schema.name,
|
||||
mode: mode,
|
||||
@ -93,10 +93,10 @@ export class EntityMetadataBuilder {
|
||||
scale: columnSchema.scale
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
metadataArgsStorage.columns.add(column);
|
||||
});
|
||||
|
||||
|
||||
// add relation metadata args from the schema
|
||||
if (schema.relations) {
|
||||
Object.keys(schema.relations).forEach(relationName => {
|
||||
@ -129,7 +129,7 @@ export class EntityMetadataBuilder {
|
||||
target: schema.target || schema.name,
|
||||
propertyName: relationName
|
||||
};
|
||||
metadataArgsStorage.joinColumns.push(joinColumn);
|
||||
metadataArgsStorage.joinColumns.add(joinColumn);
|
||||
} else {
|
||||
const joinColumn: JoinColumnMetadataArgs = {
|
||||
target: schema.target || schema.name,
|
||||
@ -137,7 +137,7 @@ export class EntityMetadataBuilder {
|
||||
name: relationSchema.joinColumn.name,
|
||||
referencedColumnName: relationSchema.joinColumn.referencedColumnName
|
||||
};
|
||||
metadataArgsStorage.joinColumns.push(joinColumn);
|
||||
metadataArgsStorage.joinColumns.add(joinColumn);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ export class EntityMetadataBuilder {
|
||||
target: schema.target || schema.name,
|
||||
propertyName: relationName
|
||||
};
|
||||
metadataArgsStorage.joinTables.push(joinTable);
|
||||
metadataArgsStorage.joinTables.add(joinTable);
|
||||
} else {
|
||||
const joinTable: JoinTableMetadataArgs = {
|
||||
target: schema.target || schema.name,
|
||||
@ -157,13 +157,13 @@ export class EntityMetadataBuilder {
|
||||
joinColumn: relationSchema.joinTable.joinColumn,
|
||||
inverseJoinColumn: relationSchema.joinTable.inverseJoinColumn
|
||||
};
|
||||
metadataArgsStorage.joinTables.push(joinTable);
|
||||
metadataArgsStorage.joinTables.add(joinTable);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return this.build(driver, lazyRelationsWrapper, metadataArgsStorage, namingStrategy);
|
||||
}
|
||||
|
||||
@ -191,43 +191,43 @@ export class EntityMetadataBuilder {
|
||||
const allMergedArgs = metadataArgsStorage.getMergedTableMetadatas(entityClasses);
|
||||
allMergedArgs.forEach(mergedArgs => {
|
||||
|
||||
|
||||
|
||||
const tables = [mergedArgs.table].concat(mergedArgs.children);
|
||||
tables.forEach(tableArgs => {
|
||||
|
||||
// find embeddable tables for embeddeds registered in this table and create EmbeddedMetadatas from them
|
||||
const embeddeds: EmbeddedMetadata[] = [];
|
||||
mergedArgs.embeddeds.forEach(embedded => {
|
||||
mergedArgs.embeddeds.toArray().forEach(embedded => {
|
||||
const embeddableTable = embeddableMergedArgs.find(embeddedMergedArgs => embeddedMergedArgs.table.target === embedded.type());
|
||||
if (embeddableTable) {
|
||||
const table = new TableMetadata(embeddableTable.table);
|
||||
const columns = embeddableTable.columns.map(args => new ColumnMetadata(args));
|
||||
const columns = embeddableTable.columns.toArray().map(args => new ColumnMetadata(args));
|
||||
embeddeds.push(new EmbeddedMetadata(embedded.type(), embedded.propertyName, table, columns));
|
||||
}
|
||||
});
|
||||
|
||||
// create metadatas from args
|
||||
const argsForTable = mergedArgs.inheritance && mergedArgs.inheritance.type === "single-table" ? mergedArgs.table : tableArgs;
|
||||
|
||||
|
||||
const table = new TableMetadata(argsForTable);
|
||||
const columns = mergedArgs.columns.map(args => {
|
||||
const columns = mergedArgs.columns.toArray().map(args => {
|
||||
|
||||
// if column's target is a child table then this column should have all nullable columns
|
||||
if (mergedArgs.inheritance &&
|
||||
mergedArgs.inheritance.type === "single-table" &&
|
||||
args.target !== mergedArgs.table.target &&
|
||||
!!mergedArgs.children.find(childTable => childTable.target === args.target)) {
|
||||
args.target !== mergedArgs.table.target && !!mergedArgs.children.find(childTable => childTable.target === args.target)) {
|
||||
args.options.nullable = true;
|
||||
}
|
||||
return new ColumnMetadata(args);
|
||||
});
|
||||
const relations = mergedArgs.relations.map(args => new RelationMetadata(args));
|
||||
const indices = mergedArgs.indices.map(args => new IndexMetadata(args));
|
||||
const relations = mergedArgs.relations.toArray().map(args => new RelationMetadata(args));
|
||||
const indices = mergedArgs.indices.toArray().map(args => new IndexMetadata(args));
|
||||
const discriminatorValueArgs = mergedArgs.discriminatorValues.find(discriminatorValueArgs => {
|
||||
return discriminatorValueArgs.target === tableArgs.target;
|
||||
});
|
||||
// create a new entity metadata
|
||||
const entityMetadata = new EntityMetadata({
|
||||
junction: false,
|
||||
target: tableArgs.target,
|
||||
tablesPrefix: driver.options.tablesPrefix,
|
||||
namingStrategy: namingStrategy,
|
||||
@ -274,7 +274,7 @@ export class EntityMetadataBuilder {
|
||||
|
||||
// save relation id-s data
|
||||
entityMetadata.relations.forEach(relation => {
|
||||
const relationIdMetadata = mergedArgs.relationIds.find(relationId => {
|
||||
const relationIdMetadata = mergedArgs.relationIds.toArray().find(relationId => {
|
||||
if (relationId.relation instanceof Function)
|
||||
return relation.propertyName === relationId.relation(entityMetadata.createPropertiesMap());
|
||||
|
||||
@ -290,7 +290,7 @@ export class EntityMetadataBuilder {
|
||||
|
||||
// save relation counter-s data
|
||||
entityMetadata.relations.forEach(relation => {
|
||||
const relationCountMetadata = mergedArgs.relationCounts.find(relationCount => {
|
||||
const relationCountMetadata = mergedArgs.relationCounts.toArray().find(relationCount => {
|
||||
if (relationCount.relation instanceof Function)
|
||||
return relation.propertyName === relationCount.relation(entityMetadata.createPropertiesMap());
|
||||
|
||||
@ -318,7 +318,7 @@ export class EntityMetadataBuilder {
|
||||
const inverseEntityMetadata = entityMetadatas.find(m => m.target === relation.type || (typeof relation.type === "string" && m.targetName === relation.type));
|
||||
if (!inverseEntityMetadata)
|
||||
throw new Error("Entity metadata for " + entityMetadata.name + "#" + relation.propertyName + " was not found.");
|
||||
|
||||
|
||||
relation.inverseEntityMetadata = inverseEntityMetadata;
|
||||
});
|
||||
});
|
||||
@ -335,9 +335,6 @@ export class EntityMetadataBuilder {
|
||||
}
|
||||
});
|
||||
|
||||
// check for errors in a built metadata schema (we need to check after relationEntityMetadata is set)
|
||||
getFromContainer(EntityMetadataValidator).validateMany(entityMetadatas);
|
||||
|
||||
// generate columns and foreign keys for tables with relations
|
||||
entityMetadatas.forEach(metadata => {
|
||||
metadata.relationsWithJoinColumns.forEach(relation => {
|
||||
@ -357,6 +354,7 @@ export class EntityMetadataBuilder {
|
||||
primary: relation.isPrimary
|
||||
}
|
||||
});
|
||||
relationalColumn.relationMetadata = relation;
|
||||
metadata.addColumn(relationalColumn);
|
||||
}
|
||||
|
||||
@ -379,7 +377,7 @@ export class EntityMetadataBuilder {
|
||||
|
||||
if (metadata.primaryColumns.length > 1)
|
||||
throw new Error(`Cannot use given entity ${metadata.name} as a closure table, because it have multiple primary keys. Entities with multiple primary keys are not supported in closure tables.`);
|
||||
|
||||
|
||||
const closureJunctionEntityMetadata = getFromContainer(ClosureJunctionEntityMetadataBuilder).build(driver, lazyRelationsWrapper, {
|
||||
namingStrategy: namingStrategy,
|
||||
table: metadata.table,
|
||||
@ -389,7 +387,7 @@ export class EntityMetadataBuilder {
|
||||
metadata.closureJunctionTable = closureJunctionEntityMetadata;
|
||||
entityMetadatas.push(closureJunctionEntityMetadata);
|
||||
});
|
||||
|
||||
|
||||
// generate junction tables for all many-to-many tables
|
||||
entityMetadatas.forEach(metadata => {
|
||||
metadata.ownerManyToManyRelations.forEach(relation => {
|
||||
@ -461,7 +459,10 @@ export class EntityMetadataBuilder {
|
||||
metadata.foreignKeys.push(foreignKey);
|
||||
});
|
||||
|
||||
// check for errors in a built metadata schema (we need to check after relationEntityMetadata is set)
|
||||
getFromContainer(EntityMetadataValidator).validateMany(entityMetadatas);
|
||||
|
||||
return entityMetadatas;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
export class EntityMetadataFactory {
|
||||
|
||||
createEntityMetadataBuilder() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,6 +6,8 @@ import {MissingJoinColumnError} from "./error/MissingJoinColumnError";
|
||||
import {MissingJoinTableError} from "./error/MissingJoinTableError";
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {MissingPrimaryColumnError} from "./error/MissingPrimaryColumnError";
|
||||
import {CircularRelationsError} from "./error/CircularRelationsError";
|
||||
import {DepGraph} from "../util/DepGraph";
|
||||
|
||||
/// todo: add check if there are multiple tables with the same name
|
||||
/// todo: add checks when generated column / table names are too long for the specific driver
|
||||
@ -24,6 +26,7 @@ export class EntityMetadataValidator {
|
||||
*/
|
||||
validateMany(entityMetadatas: EntityMetadata[]) {
|
||||
entityMetadatas.forEach(entityMetadata => this.validate(entityMetadata, entityMetadatas));
|
||||
this.validateDependencies(entityMetadatas);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,7 +35,7 @@ export class EntityMetadataValidator {
|
||||
validate(entityMetadata: EntityMetadata, allEntityMetadatas: EntityMetadata[]) {
|
||||
|
||||
// check if table metadata has an id
|
||||
if (!entityMetadata.table.isClassTableChild && !entityMetadata.primaryColumns.length)
|
||||
if (!entityMetadata.table.isClassTableChild && !entityMetadata.primaryColumns.length && !entityMetadata.junction)
|
||||
throw new MissingPrimaryColumnError(entityMetadata);
|
||||
|
||||
// validate if table is using inheritance it has a discriminator
|
||||
@ -78,6 +81,10 @@ export class EntityMetadataValidator {
|
||||
if (relation.hasInverseSide && relation.inverseRelation.joinColumn && relation.isOneToOne)
|
||||
throw new UsingJoinColumnOnlyOnOneSideAllowedError(entityMetadata, relation);
|
||||
|
||||
// check if join column really has referenced column
|
||||
if (relation.joinColumn && !relation.joinColumn.referencedColumn)
|
||||
throw new Error(`Join column does not have referenced column set`);
|
||||
|
||||
}
|
||||
|
||||
// if its a one-to-one relation and JoinColumn is missing on both sides of the relation
|
||||
@ -89,13 +96,40 @@ export class EntityMetadataValidator {
|
||||
// or its one-side relation without JoinTable we should give an error
|
||||
if (!relation.joinTable && relation.isManyToMany && (!relation.hasInverseSide || !relation.inverseRelation.joinTable))
|
||||
throw new MissingJoinTableError(entityMetadata, relation);
|
||||
|
||||
|
||||
|
||||
|
||||
// todo: validate if its one-to-one and side which does not have join column MUST have inverse side
|
||||
// todo: validate if its many-to-many and side which does not have join table MUST have inverse side
|
||||
// todo: if there is a relation, and inverse side is specified only on one side, shall we give error
|
||||
// todo: with message like: "Inverse side is specified only on one side of the relationship. Specify on other side too to prevent confusion".
|
||||
|
||||
// todo: add validation if there two entities with the same target, and show error message with description of the problem (maybe file was renamed/moved but left in output directory)
|
||||
// todo: check if there are multiple columns on the same column applied.
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates dependencies of the entity metadatas.
|
||||
*/
|
||||
protected validateDependencies(entityMetadatas: EntityMetadata[]) {
|
||||
|
||||
const graph = new DepGraph();
|
||||
entityMetadatas.forEach(entityMetadata => {
|
||||
graph.addNode(entityMetadata.name);
|
||||
});
|
||||
entityMetadatas.forEach(entityMetadata => {
|
||||
entityMetadata.relationsWithJoinColumns
|
||||
.filter(relation => !relation.isNullable)
|
||||
.forEach(relation => {
|
||||
graph.addDependency(entityMetadata.name, relation.inverseEntityMetadata.name);
|
||||
});
|
||||
});
|
||||
try {
|
||||
graph.overallOrder();
|
||||
|
||||
} catch (err) {
|
||||
throw new CircularRelationsError(err.toString().replace("Error: Dependency Cycle Found: ", ""));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,12 +23,12 @@ export interface JunctionEntityMetadataBuilderArgs {
|
||||
* Helps to create EntityMetadatas for junction tables.
|
||||
*/
|
||||
export class JunctionEntityMetadataBuilder {
|
||||
|
||||
|
||||
build(driver: Driver, lazyRelationsWrapper: LazyRelationsWrapper, args: JunctionEntityMetadataBuilderArgs) {
|
||||
|
||||
const column1 = args.joinTable.referencedColumn;
|
||||
const column2 = args.joinTable.inverseReferencedColumn;
|
||||
|
||||
|
||||
const tableMetadata = new TableMetadata({
|
||||
target: "",
|
||||
name: args.joinTable.name,
|
||||
@ -61,14 +61,15 @@ export class JunctionEntityMetadataBuilder {
|
||||
primary: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const entityMetadata = new EntityMetadata({
|
||||
junction: true,
|
||||
target: "__virtual__",
|
||||
tablesPrefix: driver.options.tablesPrefix,
|
||||
namingStrategy: args.namingStrategy,
|
||||
tableMetadata: tableMetadata,
|
||||
columnMetadatas: [
|
||||
junctionColumn1,
|
||||
junctionColumn1,
|
||||
junctionColumn2
|
||||
],
|
||||
foreignKeyMetadatas: [
|
||||
@ -86,5 +87,5 @@ export class JunctionEntityMetadataBuilder {
|
||||
|
||||
return entityMetadata;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
11
src/metadata-builder/error/CircularRelationsError.ts
Normal file
11
src/metadata-builder/error/CircularRelationsError.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Thrown when circular relations detected with nullable set to false.
|
||||
*/
|
||||
export class CircularRelationsError extends Error {
|
||||
name = "CircularRelationsError";
|
||||
|
||||
constructor(path: string) {
|
||||
super(`Circular relations detected: ${path}. To resolve this issue you need to set nullable: false somewhere in this dependency structure.`);
|
||||
}
|
||||
|
||||
}
|
||||
@ -10,10 +10,10 @@ export class MissingJoinColumnError extends Error {
|
||||
super();
|
||||
if (relation.hasInverseSide) {
|
||||
this.message = `JoinColumn is missing on both sides of ${entityMetadata.name}#${relation.propertyName} and ` +
|
||||
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} one-to-one relationship. ` +
|
||||
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} one-to-one relationship. ` +
|
||||
`You need to put JoinColumn decorator on one of the sides.`;
|
||||
} else {
|
||||
this.message = `JoinColumn is missing on ${entityMetadata.name}#${relation.propertyName} one-to-one relationship. ` +
|
||||
this.message = `JoinColumn is missing on ${entityMetadata.name}#${relation.propertyName} one-to-one relationship. ` +
|
||||
`You need to put JoinColumn decorator on it.`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,11 +10,11 @@ export class MissingJoinTableError extends Error {
|
||||
super();
|
||||
|
||||
if (relation.hasInverseSide) {
|
||||
this.message = `JoinTable is missing on both sides of ${entityMetadata.name}#${relation.propertyName} and ` +
|
||||
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} many-to-many relationship. ` +
|
||||
this.message = `JoinTable is missing on both sides of ${entityMetadata.name}#${relation.propertyName} and ` +
|
||||
`${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} many-to-many relationship. ` +
|
||||
`You need to put decorator decorator on one of the sides.`;
|
||||
} else {
|
||||
this.message = `JoinTable is missing on ${entityMetadata.name}#${relation.propertyName} many-to-many relationship. ` +
|
||||
this.message = `JoinTable is missing on ${entityMetadata.name}#${relation.propertyName} many-to-many relationship. ` +
|
||||
`You need to put JoinTable decorator on it.`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ export class MissingPrimaryColumnError extends Error {
|
||||
|
||||
constructor(entityMetadata: EntityMetadata) {
|
||||
super();
|
||||
this.message = `Entity "${entityMetadata.name}" does not have a primary column. Primary column is required to ` +
|
||||
this.message = `Entity "${entityMetadata.name}" does not have a primary column. Primary column is required to ` +
|
||||
`have in all your entities. Use @PrimaryColumn decorator to add a primary column to your entity.`;
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ export class UsingJoinColumnIsNotAllowedError extends Error {
|
||||
|
||||
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
|
||||
super();
|
||||
this.message = `Using JoinColumn on ${entityMetadata.name}#${relation.propertyName} is wrong. ` +
|
||||
this.message = `Using JoinColumn on ${entityMetadata.name}#${relation.propertyName} is wrong. ` +
|
||||
`You can use JoinColumn only on one-to-one and many-to-one relations.`;
|
||||
}
|
||||
|
||||
|
||||
@ -8,8 +8,8 @@ export class UsingJoinColumnOnlyOnOneSideAllowedError extends Error {
|
||||
|
||||
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
|
||||
super();
|
||||
this.message = `Using JoinColumn is allowed only on one side of the one-to-one relationship. ` +
|
||||
`Both ${entityMetadata.name}#${relation.propertyName} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} ` +
|
||||
this.message = `Using JoinColumn is allowed only on one side of the one-to-one relationship. ` +
|
||||
`Both ${entityMetadata.name}#${relation.propertyName} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} ` +
|
||||
`has JoinTable decorators. Choose one of them and left JoinTable decorator only on it.`;
|
||||
}
|
||||
|
||||
|
||||
@ -8,8 +8,8 @@ export class UsingJoinTableIsNotAllowedError extends Error {
|
||||
|
||||
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
|
||||
super();
|
||||
this.message = `Using JoinTable on ${entityMetadata.name}#${relation.propertyName} is wrong. ` +
|
||||
`${entityMetadata.name}#${relation.propertyName} has ${relation.relationType} relation, ` +
|
||||
this.message = `Using JoinTable on ${entityMetadata.name}#${relation.propertyName} is wrong. ` +
|
||||
`${entityMetadata.name}#${relation.propertyName} has ${relation.relationType} relation, ` +
|
||||
`however you can use JoinTable only on many-to-many relations.`;
|
||||
}
|
||||
|
||||
|
||||
@ -8,8 +8,8 @@ export class UsingJoinTableOnlyOnOneSideAllowedError extends Error {
|
||||
|
||||
constructor(entityMetadata: EntityMetadata, relation: RelationMetadata) {
|
||||
super();
|
||||
this.message = `Using JoinTable is allowed only on one side of the many-to-many relationship. ` +
|
||||
`Both ${entityMetadata.name}#${relation.propertyName} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} ` +
|
||||
this.message = `Using JoinTable is allowed only on one side of the many-to-many relationship. ` +
|
||||
`Both ${entityMetadata.name}#${relation.propertyName} and ${relation.inverseEntityMetadata.name}#${relation.inverseRelation.propertyName} ` +
|
||||
`has JoinTable decorators. Choose one of them and left JoinColumn decorator only on it.`;
|
||||
}
|
||||
|
||||
|
||||
@ -2,10 +2,11 @@ import {ColumnMetadataArgs} from "../metadata-args/ColumnMetadataArgs";
|
||||
import {ColumnType} from "./types/ColumnTypes";
|
||||
import {EntityMetadata} from "./EntityMetadata";
|
||||
import {EmbeddedMetadata} from "./EmbeddedMetadata";
|
||||
import {RelationMetadata} from "./RelationMetadata";
|
||||
|
||||
/**
|
||||
* Kinda type of the column. Not a type in the database, but locally used type to determine what kind of column
|
||||
* we are working with.
|
||||
* we are working with.
|
||||
* For example, "primary" means that it will be a primary column, or "createDate" means that it will create a create
|
||||
* date column.
|
||||
*/
|
||||
@ -30,6 +31,11 @@ export class ColumnMetadata {
|
||||
*/
|
||||
embeddedMetadata: EmbeddedMetadata;
|
||||
|
||||
/**
|
||||
* If this column is foreign key of some relation then this relation's metadata will be here.
|
||||
*/
|
||||
relationMetadata: RelationMetadata;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public Readonly Properties
|
||||
// ---------------------------------------------------------------------
|
||||
@ -77,7 +83,7 @@ export class ColumnMetadata {
|
||||
/**
|
||||
* Indicates if value in the database should be unique or not.
|
||||
*/
|
||||
readonly isUnique: boolean= false;
|
||||
readonly isUnique: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates if column can contain nulls or not.
|
||||
@ -113,6 +119,20 @@ export class ColumnMetadata {
|
||||
*/
|
||||
readonly timezone: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if date object must be stored in given date's timezone.
|
||||
* By default date is saved in UTC timezone.
|
||||
* Works only with "datetime" columns.
|
||||
*/
|
||||
readonly storeInLocalTimezone?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if date object must be loaded and set to the Date object in local timezone.
|
||||
* By default date is loaded in UTC timezone.
|
||||
* Works only with "datetime" columns.
|
||||
*/
|
||||
readonly loadInLocalTimezone?: boolean;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Private Properties
|
||||
// ---------------------------------------------------------------------
|
||||
@ -140,7 +160,7 @@ export class ColumnMetadata {
|
||||
this.type = args.options.type;
|
||||
|
||||
if (args.options.length)
|
||||
this.length = args.options.length;
|
||||
this.length = String(args.options.length);
|
||||
if (args.options.primary)
|
||||
this.isPrimary = args.options.primary;
|
||||
if (args.options.generated)
|
||||
@ -159,6 +179,10 @@ export class ColumnMetadata {
|
||||
this.precision = args.options.precision;
|
||||
if (args.options.timezone)
|
||||
this.timezone = args.options.timezone;
|
||||
if (args.options.storeInLocalTimezone)
|
||||
this.storeInLocalTimezone = args.options.storeInLocalTimezone;
|
||||
if (args.options.loadInLocalTimezone)
|
||||
this.loadInLocalTimezone = args.options.loadInLocalTimezone;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@ -179,7 +203,7 @@ export class ColumnMetadata {
|
||||
* Column name in the database.
|
||||
*/
|
||||
get name(): string {
|
||||
|
||||
|
||||
// if this column is embedded's column then apply different entity
|
||||
if (this.embeddedMetadata)
|
||||
return this.embeddedMetadata.entityMetadata.namingStrategy.embeddedColumnName(this.embeddedMetadata.propertyName, this.propertyName, this._name);
|
||||
@ -187,7 +211,7 @@ export class ColumnMetadata {
|
||||
// if there is a naming strategy then use it to normalize propertyName as column name
|
||||
if (this.entityMetadata)
|
||||
return this.entityMetadata.namingStrategy.columnName(this.propertyName, this._name);
|
||||
|
||||
|
||||
throw new Error(`Column ${this._name ? this._name + " " : ""}is not attached to any entity or embedded.`);
|
||||
}
|
||||
|
||||
@ -246,10 +270,10 @@ export class ColumnMetadata {
|
||||
get embeddedProperty() {
|
||||
if (!this.embeddedMetadata)
|
||||
throw new Error(`This column${ this._name ? this._name + " " : "" } is not in embedded entity.`);
|
||||
|
||||
|
||||
return this.embeddedMetadata.propertyName;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// ---------------------------------------------------------------------
|
||||
@ -257,13 +281,13 @@ export class ColumnMetadata {
|
||||
hasEntityValue(entity: any) {
|
||||
if (!entity)
|
||||
return false;
|
||||
|
||||
|
||||
if (this.isInEmbedded) {
|
||||
return entity[this.embeddedProperty] !== undefined &&
|
||||
entity[this.embeddedProperty] !== null &&
|
||||
entity[this.embeddedProperty][this.propertyName] !== undefined &&
|
||||
return entity[this.embeddedProperty] !== undefined &&
|
||||
entity[this.embeddedProperty] !== null &&
|
||||
entity[this.embeddedProperty][this.propertyName] !== undefined &&
|
||||
entity[this.embeddedProperty][this.propertyName] !== null;
|
||||
|
||||
|
||||
} else {
|
||||
return entity[this.propertyName] !== undefined &&
|
||||
entity[this.propertyName] !== null;
|
||||
|
||||
@ -53,7 +53,7 @@ export class EmbeddedMetadata {
|
||||
column.embeddedMetadata = this;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@ -30,7 +30,7 @@ export class EntityMetadata {
|
||||
* Parent's entity metadata. Used in inheritance patterns.
|
||||
*/
|
||||
parentEntityMetadata: EntityMetadata;
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Readonly Properties
|
||||
// -------------------------------------------------------------------------
|
||||
@ -46,6 +46,11 @@ export class EntityMetadata {
|
||||
*/
|
||||
readonly target: Function|string;
|
||||
|
||||
/**
|
||||
* Indicates if this entity metadata of a junction table, or not.
|
||||
*/
|
||||
readonly junction: boolean;
|
||||
|
||||
/**
|
||||
* Entity's table metadata.
|
||||
*/
|
||||
@ -104,6 +109,7 @@ export class EntityMetadata {
|
||||
constructor(args: EntityMetadataArgs,
|
||||
private lazyRelationsWrapper: LazyRelationsWrapper) {
|
||||
this.target = args.target;
|
||||
this.junction = args.junction;
|
||||
this.tablesPrefix = args.tablesPrefix;
|
||||
this.namingStrategy = args.namingStrategy;
|
||||
this.table = args.tableMetadata;
|
||||
@ -136,7 +142,7 @@ export class EntityMetadata {
|
||||
get name(): string {
|
||||
if (!this.table)
|
||||
throw new Error("No table target set to the entity metadata.");
|
||||
|
||||
|
||||
return this.targetName ? this.targetName : this.table.name;
|
||||
}
|
||||
|
||||
@ -183,7 +189,7 @@ export class EntityMetadata {
|
||||
|
||||
if (this.target instanceof Function)
|
||||
return (<any> this.target).name;
|
||||
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -243,13 +249,6 @@ export class EntityMetadata {
|
||||
return this.primaryColumns[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if entity has any primary columns.
|
||||
|
||||
get hasPrimaryColumns(): ColumnMetadata[] {
|
||||
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Gets the primary columns.
|
||||
*/
|
||||
@ -303,7 +302,7 @@ export class EntityMetadata {
|
||||
const column = this._columns.find(column => column.mode === "createDate");
|
||||
if (!column)
|
||||
throw new Error(`CreateDateColumn was not found in entity ${this.name}`);
|
||||
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
@ -339,7 +338,7 @@ export class EntityMetadata {
|
||||
const column = this._columns.find(column => column.mode === "version");
|
||||
if (!column)
|
||||
throw new Error(`VersionColumn was not found in entity ${this.name}`);
|
||||
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
@ -537,6 +536,9 @@ export class EntityMetadata {
|
||||
return typeof nameOrFn === "string" ? nameOrFn : nameOrFn(this.createPropertiesMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* todo: undefined entities should not go there
|
||||
*/
|
||||
getEntityIdMap(entity: any): ObjectLiteral|undefined {
|
||||
if (!entity)
|
||||
return undefined;
|
||||
@ -544,22 +546,142 @@ export class EntityMetadata {
|
||||
const map: ObjectLiteral = {};
|
||||
if (this.parentEntityMetadata) {
|
||||
this.primaryColumnsWithParentIdColumns.forEach(column => {
|
||||
map[column.propertyName] = entity[column.propertyName];
|
||||
const entityValue = entity[column.propertyName];
|
||||
if (entityValue === null || entityValue === undefined)
|
||||
return;
|
||||
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else {
|
||||
map[column.propertyName] = entityValue;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
this.primaryColumns.forEach(column => {
|
||||
map[column.propertyName] = entity[column.propertyName];
|
||||
const entityValue = entity[column.propertyName];
|
||||
if (entityValue === null || entityValue === undefined)
|
||||
return;
|
||||
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.propertyName] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else {
|
||||
map[column.propertyName] = entityValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
const hasAllIds = this.primaryColumns.every(primaryColumn => {
|
||||
return map[primaryColumn.propertyName] !== undefined && map[primaryColumn.propertyName] !== null;
|
||||
return Object.keys(map).length > 0 ? map : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getEntityIdMap, but instead of id column property names it returns database column names.
|
||||
*/
|
||||
getDatabaseEntityIdMap(entity: ObjectLiteral): ObjectLiteral|undefined {
|
||||
const map: ObjectLiteral = {};
|
||||
if (this.parentEntityMetadata) {
|
||||
this.primaryColumnsWithParentIdColumns.forEach(column => {
|
||||
const entityValue = entity[column.propertyName];
|
||||
if (entityValue === null || entityValue === undefined)
|
||||
return;
|
||||
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.name] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.name] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else {
|
||||
map[column.name] = entityValue;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
this.primaryColumns.forEach(column => {
|
||||
const entityValue = entity[column.propertyName];
|
||||
if (entityValue === null || entityValue === undefined)
|
||||
return;
|
||||
|
||||
// if entity id is a relation, then extract referenced column from that relation
|
||||
const columnRelation = this.relations.find(relation => relation.propertyName === column.propertyName);
|
||||
|
||||
if (columnRelation && columnRelation.joinColumn) {
|
||||
map[column.name] = entityValue[columnRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else if (columnRelation && columnRelation.inverseRelation.joinColumn) {
|
||||
map[column.name] = entityValue[columnRelation.inverseRelation.joinColumn.referencedColumn.propertyName];
|
||||
} else {
|
||||
map[column.name] = entityValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
const hasAllIds = Object.keys(map).every(key => {
|
||||
return map[key] !== undefined && map[key] !== null;
|
||||
});
|
||||
return hasAllIds ? map : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `getEntityIdMap` but the key of the map will be the column names instead of the property names.
|
||||
|
||||
createSimpleIdMap(id: any): ObjectLiteral {
|
||||
const map: ObjectLiteral = {};
|
||||
if (this.parentEntityMetadata) {
|
||||
this.primaryColumnsWithParentIdColumns.forEach(column => {
|
||||
map[column.propertyName] = id;
|
||||
});
|
||||
|
||||
} else {
|
||||
this.primaryColumns.forEach(column => {
|
||||
map[column.propertyName] = id;
|
||||
});
|
||||
}
|
||||
return map;
|
||||
} */
|
||||
|
||||
/**
|
||||
* Same as createSimpleIdMap, but instead of id column property names it returns database column names.
|
||||
|
||||
createSimpleDatabaseIdMap(id: any): ObjectLiteral {
|
||||
const map: ObjectLiteral = {};
|
||||
if (this.parentEntityMetadata) {
|
||||
this.primaryColumnsWithParentIdColumns.forEach(column => {
|
||||
map[column.name] = id;
|
||||
});
|
||||
|
||||
} else {
|
||||
this.primaryColumns.forEach(column => {
|
||||
map[column.name] = id;
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* todo: undefined entities should not go there??
|
||||
* todo: shouldnt be entity ObjectLiteral here?
|
||||
*/
|
||||
getEntityIdMixedMap(entity: any): any {
|
||||
if (!entity)
|
||||
return undefined;
|
||||
|
||||
if (this.hasMultiplePrimaryKeys) {
|
||||
return this.getEntityIdMap(entity);
|
||||
} else {
|
||||
return entity[this.firstPrimaryColumn.propertyName]; // todo: what about parent primary column?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `getEntityIdMap` but the key of the map will be the column names instead of the property names.
|
||||
*/
|
||||
getEntityIdColumnMap(entity: any): ObjectLiteral|undefined {
|
||||
return this.transformIdMapToColumnNames(this.getEntityIdMap(entity));
|
||||
@ -582,7 +704,7 @@ export class EntityMetadata {
|
||||
getColumnByPropertyName(propertyName: string) {
|
||||
return this._columns.find(column => column.propertyName === propertyName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if column with the given property name exist.
|
||||
*/
|
||||
@ -611,7 +733,7 @@ export class EntityMetadata {
|
||||
const relation = this.relations.find(relation => relation.propertyName === propertyName);
|
||||
if (!relation)
|
||||
throw new Error(`Relation with property name ${propertyName} in ${this.name} entity was not found.`);
|
||||
|
||||
|
||||
return relation;
|
||||
}
|
||||
|
||||
@ -632,7 +754,7 @@ export class EntityMetadata {
|
||||
|
||||
return relation;
|
||||
}
|
||||
|
||||
|
||||
addColumn(column: ColumnMetadata) {
|
||||
this._columns.push(column);
|
||||
column.entityMetadata = this;
|
||||
@ -682,29 +804,82 @@ export class EntityMetadata {
|
||||
return this.compareIds(firstEntityIds, secondEntityIds);
|
||||
}
|
||||
|
||||
compareIds(firstIds: ObjectLiteral|undefined, secondIds: ObjectLiteral|undefined): boolean {
|
||||
if (!firstIds || !secondIds)
|
||||
compareIds(firstId: ObjectLiteral|undefined, secondId: ObjectLiteral|undefined): boolean {
|
||||
if (firstId === undefined || firstId === null || secondId === undefined || secondId === null)
|
||||
return false;
|
||||
|
||||
return Object.keys(firstIds).every(key => {
|
||||
return firstIds[key] === secondIds[key];
|
||||
return Object.keys(firstId).every(key => {
|
||||
return firstId[key] === secondId[key];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two entity ids.
|
||||
* If any of the given value is empty then it will return false.
|
||||
*/
|
||||
compareEntityMixedIds(firstId: any, secondId: any): boolean {
|
||||
if (firstId === undefined || firstId === null || secondId === undefined || secondId === null)
|
||||
return false;
|
||||
|
||||
if (this.hasMultiplePrimaryKeys) {
|
||||
return Object.keys(firstId).every(key => {
|
||||
return firstId[key] === secondId[key];
|
||||
});
|
||||
} else {
|
||||
return firstId === secondId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates throw entity and finds and extracts all values from relations in the entity.
|
||||
* If relation value is an array its being flattened.
|
||||
*/
|
||||
extractRelationValuesFromEntity(entity: ObjectLiteral, relations: RelationMetadata[]): [RelationMetadata, any][] {
|
||||
const relationsAndValues: [RelationMetadata, any][] = [];
|
||||
extractRelationValuesFromEntity(entity: ObjectLiteral, relations: RelationMetadata[]): [RelationMetadata, any, EntityMetadata][] {
|
||||
const relationsAndValues: [RelationMetadata, any, EntityMetadata][] = [];
|
||||
relations.forEach(relation => {
|
||||
const value = relation.getEntityValue(entity);
|
||||
if (value instanceof Array) {
|
||||
value.forEach(subValue => relationsAndValues.push([relation, subValue]));
|
||||
value.forEach(subValue => relationsAndValues.push([relation, subValue, relation.inverseEntityMetadata]));
|
||||
} else if (value) {
|
||||
relationsAndValues.push([relation, value]);
|
||||
relationsAndValues.push([relation, value, relation.inverseEntityMetadata]);
|
||||
}
|
||||
});
|
||||
return relationsAndValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given entity has an id.
|
||||
*/
|
||||
hasId(entity: ObjectLiteral): boolean {
|
||||
|
||||
// if (this.metadata.parentEntityMetadata) {
|
||||
// return this.metadata.parentEntityMetadata.parentIdColumns.every(parentIdColumn => {
|
||||
// const columnName = parentIdColumn.propertyName;
|
||||
// return !!entity &&
|
||||
// entity.hasOwnProperty(columnName) &&
|
||||
// entity[columnName] !== null &&
|
||||
// entity[columnName] !== undefined &&
|
||||
// entity[columnName] !== "";
|
||||
// });
|
||||
|
||||
// } else {
|
||||
return this.primaryColumns.every(primaryColumn => {
|
||||
const columnName = primaryColumn.propertyName;
|
||||
return !!entity &&
|
||||
entity.hasOwnProperty(columnName) &&
|
||||
entity[columnName] !== null &&
|
||||
entity[columnName] !== undefined &&
|
||||
entity[columnName] !== "";
|
||||
});
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there any non-nullable column exist in this entity.
|
||||
*/
|
||||
get hasNonNullableColumns(): boolean {
|
||||
return this.relationsWithJoinColumns.some(relation => !relation.isNullable || relation.isPrimary);
|
||||
// return this.relationsWithJoinColumns.some(relation => relation.isNullable || relation.isPrimary);
|
||||
}
|
||||
|
||||
}
|
||||
@ -13,5 +13,5 @@ export class EntitySubscriberMetadata {
|
||||
constructor(args: EntitySubscriberMetadataArgs) {
|
||||
this.target = args.target;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user