Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nebkat committed Sep 27, 2021
1 parent 21c25ae commit 50b9230
Show file tree
Hide file tree
Showing 23 changed files with 248 additions and 237 deletions.
21 changes: 12 additions & 9 deletions src/data-source/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ export class DataSource {
this.buildMetadatas();

await this.driver.afterConnect();

if (this.options.dropSchema)
await this.dropSchema();

// if option is set - automatically synchronize a schema
if (this.options.synchronize)
await this.synchronizeSchema();

} catch (error) {
// if for some reason build metadata fail (for example validation error during entity metadata check)
// data-source needs to be closed
Expand Down Expand Up @@ -186,7 +194,8 @@ export class DataSource {
const queryRunner = this.createQueryRunner();
try {
if (this.driver.config.multiDatabase) {
const databases = [...new Set(this.entityMetadatas.map(metadata => metadata.database))];
const databases = [...new Set(this.entityMetadatas.map(metadata => metadata.database))]
.filter(db => db !== undefined);
if (this.driver.database) databases.unshift(this.driver.database);
for (const database of databases) {
await queryRunner.clearDatabase(database);
Expand Down Expand Up @@ -294,14 +303,8 @@ export class DataSource {
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: EntityTarget<Entity>, alias: string, queryRunner?: QueryRunner): QueryBuilderFactory<Entity>;
createQueryBuilder(queryRunner?: QueryRunner): QueryBuilderFactory<any>;
createQueryBuilder<Entity>(entityOrRunner?: EntityTarget<Entity>|QueryRunner, alias?: string, queryRunner?: QueryRunner): QueryBuilderFactory<Entity> {
if (alias) {
return new QueryBuilderFactory(this, queryRunner, entityOrRunner as EntityTarget<Entity>, alias);
} else {
return new QueryBuilderFactory(this, entityOrRunner as QueryRunner|undefined);
}
createQueryBuilder(queryRunner?: QueryRunner): QueryBuilderFactory<any> {
return new QueryBuilderFactory(this, queryRunner);
}

/**
Expand Down
18 changes: 18 additions & 0 deletions src/data-source/DataSourceOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ export interface DataSourceOptions {
*/
readonly entities?: Function[];

/**
* Indicates if database schema should be auto created on every application launch.
* Be careful with this option and don't use this in production - otherwise you can lose production data.
* This option is useful during debug and development.
* Alternative to it, you can use CLI and run schema:sync command.
*
* Note that for MongoDB database it does not create schema, because MongoDB is schemaless.
* Instead, it syncs just by creating indices.
*/
readonly synchronize?: boolean;

/**
* Drops the schema each time connection is being established.
* Be careful with this option and don't use this in production - otherwise you'll lose all production data.
* This option is useful during debug and development.
*/
readonly dropSchema?: boolean;

/**
* Migrations to be loaded for this data-source.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/driver/mysql/MysqlDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { LockNotSupportedOnGivenDriverError } from "../../error/LockNotSupported
import {Expression, ExpressionBuilder} from "../../expression-builder/Expression";
import {Fn} from "../../expression-builder/expression/Function";
import {CurrentTimestamp} from "../../expression-builder/expression/datetime/CurrentTimestamp";
import {DefaultExpressionBuildInterface} from "../../expression-builder/DefaultExpressionBuildInterface";

/**
* Organizes communication with MySQL DBMS.
Expand Down Expand Up @@ -638,6 +639,10 @@ export class MysqlDriver implements Driver {
normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
const defaultValue = columnMetadata.default;

if (defaultValue instanceof ExpressionBuilder) {
return new DefaultExpressionBuildInterface(this).buildExpression(null, defaultValue);
}

if ((columnMetadata.type === "enum" || columnMetadata.type === "simple-enum") && defaultValue !== undefined) {
return `'${defaultValue}'`;
}
Expand Down
24 changes: 11 additions & 13 deletions src/entity-manager/EntityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,8 @@ export class EntityManager {
/**
* Creates a new query builder that can be used to build a sql query.
*/
createQueryBuilder<Entity>(entityClass: EntityTarget<Entity>, alias: string, queryRunner?: QueryRunner): QueryBuilderFactory<Entity>;
createQueryBuilder(queryRunner?: QueryRunner): QueryBuilderFactory<any>;
createQueryBuilder<Entity>(entityClassOrQueryRunner?: EntityTarget<Entity> | QueryRunner, alias?: string, queryRunner?: QueryRunner): QueryBuilderFactory<Entity> {
return this.source.createQueryBuilder(entityClassOrQueryRunner as any, alias as any, queryRunner ?? this.queryRunner);
createQueryBuilder(queryRunner?: QueryRunner): QueryBuilderFactory<any> {
return this.source.createQueryBuilder(queryRunner ?? this.queryRunner);
}

/**
Expand Down Expand Up @@ -291,7 +289,7 @@ export class EntityManager {
async count<Entity>(entityClass: EntityTarget<Entity>, optionsOrConditions?: FindConditions<Entity>|FindOneOptions<Entity>|FindManyOptions<Entity>): Promise<number> {
const metadata = this.source.getMetadata(entityClass);
const alias = FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) ?? metadata.name;
const qb = this.createQueryBuilder(entityClass, alias).select();
const qb = this.createQueryBuilder().select(alias).from(entityClass, alias);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getCount();
}

Expand All @@ -303,7 +301,7 @@ export class EntityManager {
async find<Entity>(entityClass: EntityTarget<Entity>, optionsOrConditions?: FindManyOptions<Entity>|FindConditions<Entity>): Promise<Entity[]> {
const metadata = this.source.getMetadata(entityClass);
const alias = FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) ?? metadata.name;
const qb = this.createQueryBuilder<Entity>(entityClass, alias).select();
const qb = this.createQueryBuilder().select(alias).from(entityClass, alias);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getMany();
}

Expand All @@ -317,7 +315,7 @@ export class EntityManager {
async findAndCount<Entity>(entityClass: EntityTarget<Entity>, optionsOrConditions?: FindConditions<Entity>|FindManyOptions<Entity>): Promise<[Entity[], number]> {
const metadata = this.source.getMetadata(entityClass);
const alias = FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) ?? metadata.name;
const qb = this.createQueryBuilder<Entity>(entityClass, alias).select();
const qb = this.createQueryBuilder().select(alias).from(entityClass, alias);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getManyAndCount();
}

Expand All @@ -333,7 +331,7 @@ export class EntityManager {

const metadata = this.source.getMetadata(entityClass);
const alias = FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) ?? metadata.name;
const qb = this.createQueryBuilder<Entity>(entityClass, alias).select();
const qb = this.createQueryBuilder().select(alias).from(entityClass, alias);
FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions);
return qb.andWhereInIds(ids).getMany();
}
Expand All @@ -345,7 +343,7 @@ export class EntityManager {
const metadata = this.source.getMetadata(entityClass);
let alias: string = metadata.name;
if (options?.join !== undefined) alias = options.join.alias;
const qb = this.createQueryBuilder<Entity>(entityClass, alias).select();
const qb = this.createQueryBuilder().select(alias).from(entityClass, alias);

FindOptionsUtils.applyOptionsToQueryBuilder(qb, options);

Expand Down Expand Up @@ -378,8 +376,8 @@ export class EntityManager {
throw new Error(`Column ${propertyPath} was not found in ${metadata.targetName} entity.`);

return this
.createQueryBuilder<Entity>(entityClass as any, "entity")
.update(entityClass)
.createQueryBuilder()
.update(entityClass, "entity")
.set(column.createValueMap(Plus(value)) as any)
.where(conditions)
.execute();
Expand All @@ -399,8 +397,8 @@ export class EntityManager {
throw new Error(`Column ${propertyPath} was not found in ${metadata.targetName} entity.`);

return this
.createQueryBuilder<Entity>(entityClass as any, "entity")
.update(entityClass)
.createQueryBuilder()
.update(entityClass, "entity")
.set(column.createValueMap(Minus(value)) as any)
.where(conditions)
.execute();
Expand Down
49 changes: 49 additions & 0 deletions src/expression-builder/DefaultExpressionBuildInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {QueryBuilder} from "../query-builder/builder/QueryBuilder";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {Driver} from "../driver/Driver";
import {BuildableExpression, Expression} from "./Expression";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {Alias} from "../query-builder/Alias";
import {QueryBuilderFactory} from "../query-builder/builder/QueryBuilderFactory";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {ExpressionBuildInterface} from "./ExpressionBuildInterface";

export class DefaultExpressionBuildInterface implements ExpressionBuildInterface {
constructor(readonly driver: Driver) {
}

buildExpression(context: any, expression: Expression): string {
return expression instanceof BuildableExpression ? expression.build(this as any, context) : this.buildLiteral(context, expression, false)
}

buildColumn(context: any, column?: string | ColumnMetadata, alias?: string | Alias): string {
throw new Error("A");
}
buildTable(context: any, table: string | EntityMetadata): string {
throw new Error("B");
}
buildLiteral(context: any, literal: Expression, raw?: boolean): string {
throw new Error("C");
}
buildRaw(context: any, expression: string): string {
throw new Error("D");
}

buildConditions(context: any, conditions: ObjectLiteral): string {
throw new Error("E");
}

buildSubQuery(context: any, qb: QueryBuilder<any>): string {
throw new Error("F");
}
createSubQuery(context: any): QueryBuilderFactory<any> {
throw new Error("G");
}

enterAliasContext(context: any, alias: string): any {
throw new Error("H");
};
enterPathContext(context: any, path: string): any {
throw new Error("I");
};
}
2 changes: 2 additions & 0 deletions src/expression-builder/ExpressionBuildInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {Expression} from "./Expression";
import {ColumnMetadata} from "../metadata/ColumnMetadata";
import {Alias} from "../query-builder/Alias";
import {QueryBuilderFactory} from "../query-builder/builder/QueryBuilderFactory";
import {EntityMetadata} from "../metadata/EntityMetadata";

export interface ExpressionBuildInterface {
buildExpression(context: any, expression: Expression): string;

buildColumn(context: any, column?: string | ColumnMetadata, alias?: string | Alias): string;
buildTable(context: any, table: string | EntityMetadata): string;
buildLiteral(context: any, literal: Expression, raw?: boolean): string;
buildRaw(context: any, expression: string): string;

Expand Down
15 changes: 15 additions & 0 deletions src/expression-builder/expression/Table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ExpressionBuilder } from "../Expression";
import { ExpressionBuildInterface } from "../ExpressionBuildInterface";
import {EntityMetadata} from "../../metadata/EntityMetadata";

export function Table(table: string | EntityMetadata): TableBuilder {
return new TableBuilder(table);
}

export class TableBuilder extends ExpressionBuilder {
constructor(readonly table: string | EntityMetadata) {
super();
}

build = (eb: ExpressionBuildInterface, ctx: any): string => eb.buildTable(ctx, this.table);
}
33 changes: 19 additions & 14 deletions src/find-options/FindOptionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {SelectQueryBuilder} from "../query-builder/builder/SelectQueryBuilder";
import {FindRelationsNotFoundError} from "../error/FindRelationsNotFoundError";
import {EntityMetadata} from "../metadata/EntityMetadata";
import {shorten} from "../util/StringUtils";
import {AliasedRelationPath} from "../query-builder/ColumnPath";
import {Alias} from "../query-builder/Alias";
import {RelationMetadata} from "../metadata/RelationMetadata";

/**
* Utilities to work with FindOptions.
Expand Down Expand Up @@ -136,7 +139,7 @@ export class FindOptionsUtils {

if (options.relations) {
const allRelations = options.relations.map(relation => relation);
this.applyRelationsRecursively(qb, allRelations, qb.expressionMap.mainAlias!.name, qb.expressionMap.mainAlias!.metadata, "");
this.applyRelationsRecursively(qb, allRelations, qb.expressionMap.mainAlias!, qb.expressionMap.mainAlias!.metadata, "");
// recursive removes found relations from allRelations array
// if there are relations left in this array it means those relations were not found in the entity structure
// so, we give an exception about not found relations
Expand Down Expand Up @@ -206,40 +209,42 @@ export class FindOptionsUtils {
/**
* Adds joins for all relations and sub-relations of the given relations provided in the find options.
*/
protected static applyRelationsRecursively(qb: SelectQueryBuilder<any>, allRelations: string[], alias: string, metadata: EntityMetadata, prefix: string): void {
protected static applyRelationsRecursively(qb: SelectQueryBuilder<any>, allRelations: string[], alias: Alias, metadata: EntityMetadata, prefix: string): void {

// find all relations that match given prefix
let matchedBaseRelations: string[] = [];
let matchedBaseRelations: RelationMetadata[];
if (prefix) {
const regexp = new RegExp("^" + prefix.replace(".", "\\.") + "\\.");
matchedBaseRelations = allRelations
.filter(relation => relation.match(regexp))
.map(relation => relation.replace(regexp, ""))
.filter(relation => metadata.findRelationWithPropertyPath(relation));
.filter(relation => relation.startsWith(`${prefix}.`))
.map(relation => relation.replace(`${prefix}.`, ""))
.map(relation => metadata.findRelationWithPropertyPath(relation))
.filter((relation): relation is RelationMetadata => relation !== undefined);
} else {
matchedBaseRelations = allRelations.filter(relation => metadata.findRelationWithPropertyPath(relation));
matchedBaseRelations = allRelations.map(relation => metadata.findRelationWithPropertyPath(relation))
.filter((relation): relation is RelationMetadata => relation !== undefined);
}

// go through all matched relations and add join for them
matchedBaseRelations.forEach(relation => {

// generate a relation alias
let relationAlias: string = alias + "__" + relation;
let relationAlias: string = `${alias.name}__${relation.propertyPath}`;
// shorten it if needed by the driver
if (qb.connection.driver.config.maxAliasLength && relationAlias.length > qb.connection.driver.config.maxAliasLength) {
relationAlias = shorten(relationAlias);
}

// add a join for the found relation
const selection = alias + "." + relation;
const selection = `${alias.name}.${relation.propertyPath}` as AliasedRelationPath;
qb.leftJoinAndSelect(selection, relationAlias);

const fullPath = prefix ? prefix + "." + relation.propertyPath : relation.propertyPath;

// remove added relations from the allRelations array, this is needed to find all not found relations at the end
allRelations.splice(allRelations.indexOf(prefix ? prefix + "." + relation : relation), 1);
allRelations.splice(allRelations.indexOf(fullPath), 1);

// try to find sub-relations
const join = qb.expressionMap.joinAttributes.find(join => join.entityOrProperty === selection);
this.applyRelationsRecursively(qb, allRelations, join!.alias.name, join!.metadata!, prefix ? prefix + "." + relation : relation);
const join = qb.expressionMap.joinAttributes.find(join => join.parentAlias === alias && join.relation === relation);
this.applyRelationsRecursively(qb, allRelations, join!.alias, join!.metadata!, fullPath);
});
}

Expand Down
10 changes: 6 additions & 4 deletions src/find-options/JoinOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
* }
* };
*/
import {AliasedRelationPath} from "../query-builder/ColumnPath";

export interface JoinOptions {

/**
Expand All @@ -43,30 +45,30 @@ export interface JoinOptions {
*
* The columns of the joined table are included in the selection.
*/
leftJoinAndSelect?: { [key: string]: string };
leftJoinAndSelect?: { [key: string]: AliasedRelationPath };

/**
* Object where each key represents the INNER JOIN alias,
* and the corresponding value represents the relation path.
*
* The columns of the joined table are included in the selection.
*/
innerJoinAndSelect?: { [key: string]: string };
innerJoinAndSelect?: { [key: string]: AliasedRelationPath };

/**
* Object where each key represents the LEFT JOIN alias,
* and the corresponding value represents the relation path.
*
* This method does not select the columns of the joined table.
*/
leftJoin?: { [key: string]: string };
leftJoin?: { [key: string]: AliasedRelationPath };

/**
* Object where each key represents the INNER JOIN alias,
* and the corresponding value represents the relation path.
*
* This method does not select the columns of the joined table.
*/
innerJoin?: { [key: string]: string };
innerJoin?: { [key: string]: AliasedRelationPath };

}
4 changes: 2 additions & 2 deletions src/metadata-builder/EntityMetadataBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class EntityMetadataBuilder {
entityMetadata.ownEmbeddeds = this.createEmbeddedsRecursively(entityMetadata, metadataArgsStorage.treeEmbeddeds);

entityMetadata.ownColumns = metadataArgsStorage.treeColumns
.map(args => new ColumnMetadata({ entityMetadata, args }));
.map(args => new ColumnMetadata({ entityMetadata, args, columnModeDefaultArgs: this.connection.driver.columnModeDefaultArgs }));

entityMetadata.ownRelations = metadataArgsStorage.treeRelations
.map(args => new RelationMetadata({ entityMetadata, args }));
Expand All @@ -253,7 +253,7 @@ export class EntityMetadataBuilder {
const metadataArgsStorage = MetadataArgsStorage.get(embeddedMetadata.target);

embeddedMetadata.ownColumns = metadataArgsStorage.treeColumns
.map(args => new ColumnMetadata({ entityMetadata, embeddedMetadata, args}));
.map(args => new ColumnMetadata({ entityMetadata, embeddedMetadata, args, columnModeDefaultArgs: this.connection.driver.columnModeDefaultArgs }));

embeddedMetadata.ownRelations = metadataArgsStorage.treeRelations
.map(args => new RelationMetadata({ entityMetadata, embeddedMetadata, args }));
Expand Down
Loading

0 comments on commit 50b9230

Please sign in to comment.