mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
implemented mongo indices
This commit is contained in:
parent
0ae68e49aa
commit
665c9e0f55
@ -249,8 +249,12 @@ export class Connection {
|
||||
if (dropBeforeSync)
|
||||
await this.dropDatabase();
|
||||
|
||||
if (!(this.driver instanceof MongoDriver)) // todo: temporary
|
||||
if (this.driver instanceof MongoDriver) { // todo: temporary
|
||||
await this.driver.syncSchema(this.entityMetadatas);
|
||||
|
||||
} else {
|
||||
await this.createSchemaBuilder().build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -25,21 +25,21 @@ export function Index(fields: string[], options?: IndexOptions): Function;
|
||||
/**
|
||||
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
|
||||
*/
|
||||
export function Index(fields: (object: any) => any[], options?: IndexOptions): Function;
|
||||
export function Index(fields: (object?: any) => (any[]|{ [key: string]: number }), options?: IndexOptions): Function;
|
||||
|
||||
/**
|
||||
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
|
||||
*/
|
||||
export function Index(name: string, fields: (object: any) => any[], options?: IndexOptions): Function;
|
||||
export function Index(name: string, fields: (object?: any) => (any[]|{ [key: string]: number }), options?: IndexOptions): Function;
|
||||
|
||||
/**
|
||||
* Composite index must be set on entity classes and must specify entity's fields to be indexed.
|
||||
*/
|
||||
export function Index(nameOrFieldsOrOptions: string|string[]|((object: any) => any[])|IndexOptions,
|
||||
maybeFieldsOrOptions?: ((object: any) => any[])|IndexOptions|string[],
|
||||
maybeFieldsOrOptions?: ((object?: any) => (any[]|{ [key: string]: number }))|IndexOptions|string[],
|
||||
maybeOptions?: IndexOptions): Function {
|
||||
const name = typeof nameOrFieldsOrOptions === "string" ? nameOrFieldsOrOptions : undefined;
|
||||
const fields = typeof nameOrFieldsOrOptions === "string" ? <((object: any) => any[])|string[]> maybeFieldsOrOptions : nameOrFieldsOrOptions as string[];
|
||||
const fields = typeof nameOrFieldsOrOptions === "string" ? <((object?: any) => (any[]|{ [key: string]: number }))|string[]> maybeFieldsOrOptions : nameOrFieldsOrOptions as string[];
|
||||
let options = (typeof nameOrFieldsOrOptions === "object" && !Array.isArray(nameOrFieldsOrOptions)) ? nameOrFieldsOrOptions as IndexOptions : maybeOptions;
|
||||
if (!options)
|
||||
options = (typeof maybeFieldsOrOptions === "object" && !Array.isArray(maybeFieldsOrOptions)) ? nameOrFieldsOrOptions as IndexOptions : maybeOptions;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {ColumnOptions} from "../options/ColumnOptions";
|
||||
import {ColumnTypeUndefinedError} from "../error/ColumnTypeUndefinedError";
|
||||
import {GeneratedOnlyForPrimaryError} from "../error/GeneratedOnlyForPrimaryError";
|
||||
import {getMetadataArgsStorage} from "../../index";
|
||||
import {ColumnType, ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
|
||||
@ -8,4 +8,11 @@ export interface IndexOptions {
|
||||
*/
|
||||
readonly unique?: boolean;
|
||||
|
||||
/**
|
||||
* If true, the index only references documents with the specified field.
|
||||
* These indexes use less space but behave differently in some situations (particularly sorts).
|
||||
* This option is only supported for mongodb database.
|
||||
*/
|
||||
readonly sparse?: boolean;
|
||||
|
||||
}
|
||||
@ -11,6 +11,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import {DriverOptionNotSetError} from "../error/DriverOptionNotSetError";
|
||||
import {PlatformTools} from "../../platform/PlatformTools";
|
||||
import {NamingStrategyInterface} from "../../naming-strategy/NamingStrategyInterface";
|
||||
import {EntityMetadata} from "../../metadata/EntityMetadata";
|
||||
|
||||
/**
|
||||
* Organizes communication with MongoDB.
|
||||
@ -216,6 +217,20 @@ export class MongoDriver implements Driver {
|
||||
return value;
|
||||
}
|
||||
|
||||
// todo: make better abstraction
|
||||
async syncSchema(entityMetadatas: EntityMetadata[]): Promise<void> {
|
||||
const queryRunner = await this.createQueryRunner() as MongoQueryRunner;
|
||||
const promises: Promise<any>[] = [];
|
||||
await Promise.all(entityMetadatas.map(metadata => {
|
||||
metadata.indices.forEach(index => {
|
||||
const columns = index.buildColumnsAsMap(1);
|
||||
const options = { name: index.name };
|
||||
promises.push(queryRunner.createCollectionIndex(metadata.table.name, columns, options));
|
||||
});
|
||||
}));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -16,7 +16,7 @@ export interface IndexMetadataArgs {
|
||||
/**
|
||||
* Columns combination to be used as index.
|
||||
*/
|
||||
readonly columns: ((object: any) => any[])|string[];
|
||||
readonly columns: ((object?: any) => (any[]|{ [key: string]: number }))|string[];
|
||||
|
||||
/**
|
||||
* Indicates if index must be unique or not.
|
||||
|
||||
@ -41,7 +41,7 @@ export class IndexMetadata {
|
||||
/**
|
||||
* Columns combination to be used as index.
|
||||
*/
|
||||
private readonly _columns: ((object: any) => any[])|string[];
|
||||
private readonly _columns: ((object?: any) => (any[]|{ [key: string]: number }))|string[];
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Constructor
|
||||
@ -84,12 +84,14 @@ export class IndexMetadata {
|
||||
} else {
|
||||
// if columns is a function that returns array of field names then execute it and get columns names from it
|
||||
const propertiesMap = this.entityMetadata.createPropertiesMap();
|
||||
columnPropertyNames = this._columns(propertiesMap).map((i: any) => String(i));
|
||||
const columnsFnResult = this._columns(propertiesMap);
|
||||
const columnsNamesFromFnResult = columnsFnResult instanceof Array ? columnsFnResult : Object.keys(columnsFnResult);
|
||||
columnPropertyNames = columnsNamesFromFnResult.map((i: any) => String(i));
|
||||
}
|
||||
|
||||
const columns = this.entityMetadata.columns.filter(column => columnPropertyNames.indexOf(column.propertyName) !== -1);
|
||||
const missingColumnNames = columnPropertyNames.filter(columnPropertyName => !this.entityMetadata.columns.find(column => column.propertyName === columnPropertyName));
|
||||
if (missingColumnNames.length > 0) {
|
||||
if (missingColumnNames.length > 0) { // todo: better to extract all validation into single place is possible
|
||||
// console.log(this.entityMetadata.columns);
|
||||
throw new Error(`Index ${this._name ? "\"" + this._name + "\" " : ""}contains columns that are missing in the entity: ` + missingColumnNames.join(", "));
|
||||
}
|
||||
@ -97,4 +99,38 @@ export class IndexMetadata {
|
||||
return columns.map(column => column.fullName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds columns as a map of values where column name is key of object and value is a value provided by
|
||||
* function or default value given to this function.
|
||||
*/
|
||||
buildColumnsAsMap(defaultValue = 0): { [key: string]: number } {
|
||||
|
||||
const map: { [key: string]: number } = {};
|
||||
|
||||
// if columns already an array of string then simply create a map from it
|
||||
if (this._columns instanceof Array) {
|
||||
this._columns.forEach(columnName => map[columnName] = defaultValue);
|
||||
|
||||
} else {
|
||||
// if columns is a function that returns array of field names then execute it and get columns names from it
|
||||
const propertiesMap = this.entityMetadata.createPropertiesMap();
|
||||
const columnsFnResult = this._columns(propertiesMap);
|
||||
if (columnsFnResult instanceof Array) {
|
||||
columnsFnResult.forEach(columnName => map[columnName] = defaultValue);
|
||||
} else {
|
||||
Object.keys(columnsFnResult).forEach(columnName => map[columnName] = columnsFnResult[columnName]);
|
||||
}
|
||||
}
|
||||
|
||||
// replace each propertyNames with column names
|
||||
return Object.keys(map).reduce((updatedMap, key) => {
|
||||
const column = this.entityMetadata.columns.find(column => column.propertyName === key);
|
||||
if (!column)
|
||||
throw new Error(`Index ${this._name ? "\"" + this._name + "\" " : ""}contains columns that are missing in the entity: ${key}`);
|
||||
|
||||
updatedMap[column.name] = map[key];
|
||||
return updatedMap;
|
||||
}, {} as { [key: string]: number });
|
||||
}
|
||||
|
||||
}
|
||||
29
test/functional/mongodb/basic/mongo-index/entity/Post.ts
Normal file
29
test/functional/mongodb/basic/mongo-index/entity/Post.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {Entity} from "../../../../../../src/decorator/entity/Entity";
|
||||
import {Column} from "../../../../../../src/decorator/columns/Column";
|
||||
import {ObjectID} from "mongodb";
|
||||
import {ObjectIdColumn} from "../../../../../../src/decorator/columns/ObjectIdColumn";
|
||||
import {Index} from "../../../../../../src/decorator/Index";
|
||||
|
||||
@Entity()
|
||||
@Index(["title", "name"])
|
||||
@Index(() => ({ title: -1, name: -1, count: 1 }))
|
||||
@Index("title_name_count", () => ({ title: 1, name: 1, count: 1 }))
|
||||
@Index("title_name_count_reversed", () => ({ title: -1, name: -1, count: -1 }))
|
||||
export class Post {
|
||||
|
||||
@ObjectIdColumn()
|
||||
id: ObjectID;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
title: string;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
count: number;
|
||||
|
||||
}
|
||||
32
test/functional/mongodb/basic/mongo-index/mongo-index.ts
Normal file
32
test/functional/mongodb/basic/mongo-index/mongo-index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import "reflect-metadata";
|
||||
import {Connection} from "../../../../../src/connection/Connection";
|
||||
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils";
|
||||
import {Post} from "./entity/Post";
|
||||
import {expect} from "chai";
|
||||
|
||||
describe("mongodb > indices", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
entities: [Post],
|
||||
enabledDrivers: ["mongodb"]
|
||||
}));
|
||||
beforeEach(() => reloadTestingDatabases(connections));
|
||||
after(() => closeTestingConnections(connections));
|
||||
|
||||
it("should insert entity with indices correctly", () => Promise.all(connections.map(async connection => {
|
||||
const postRepository = connection.getRepository(Post);
|
||||
|
||||
// save a post
|
||||
const post = new Post();
|
||||
post.title = "Post";
|
||||
post.name = "About Post";
|
||||
await postRepository.persist(post);
|
||||
|
||||
// check saved post
|
||||
const loadedPost = await postRepository.findOne({ title: "Post" });
|
||||
|
||||
expect(loadedPost).to.be.not.empty;
|
||||
})));
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user