diff --git a/src/decorator/options/ColumnEmbeddedOptions.ts b/src/decorator/options/ColumnEmbeddedOptions.ts index 1256fd1d2c..385278f878 100644 --- a/src/decorator/options/ColumnEmbeddedOptions.ts +++ b/src/decorator/options/ColumnEmbeddedOptions.ts @@ -9,4 +9,10 @@ export interface ColumnEmbeddedOptions { */ prefix?: string | boolean; + /** + * Indicates if this embedded is in array mode. + * + * This option works only in mongodb. + */ + array?: boolean; } diff --git a/src/metadata/ColumnMetadata.ts b/src/metadata/ColumnMetadata.ts index 583c6643c7..ca97c42a8d 100644 --- a/src/metadata/ColumnMetadata.ts +++ b/src/metadata/ColumnMetadata.ts @@ -526,31 +526,40 @@ export class ColumnMetadata { // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters] const propertyNames = [...this.embeddedMetadata.parentPropertyNames]; + const isEmbeddedArray = this.embeddedMetadata.isArray; // now need to access post[data][information][counters] to get column value from the counters // and on each step we need to create complex literal object, e.g. first { data }, // then { data: { information } }, then { data: { information: { counters } } }, // then { data: { information: { counters: [this.propertyName]: entity[data][information][counters][this.propertyName] } } } // this recursive function helps doing that - const extractEmbeddedColumnValue = (propertyNames: string[], value: ObjectLiteral, map: ObjectLiteral): any => { + const extractEmbeddedColumnValue = (propertyNames: string[], value: ObjectLiteral): ObjectLiteral => { + if (value === undefined) { + return {}; + } + const propertyName = propertyNames.shift(); - if (value === undefined) - return map; if (propertyName) { - const submap: ObjectLiteral = {}; - extractEmbeddedColumnValue(propertyNames, value[propertyName], submap); + const submap = extractEmbeddedColumnValue(propertyNames, value[propertyName]); if (Object.keys(submap).length > 0) { - map[propertyName] = submap; + return { [propertyName]: submap }; } - return map; + return {}; } - if (value[this.propertyName] !== undefined && (returnNulls === false || value[this.propertyName] !== null)) - map[this.propertyName] = value[this.propertyName]; - return map; + + if (isEmbeddedArray && Array.isArray(value)) { + return value.map(v => ({ [this.propertyName]: v[this.propertyName] })); + } + + if (value[this.propertyName] !== undefined && (returnNulls === false || value[this.propertyName] !== null)) { + return { [this.propertyName]: value[this.propertyName] }; + } + + return {}; }; - const map: ObjectLiteral = {}; - extractEmbeddedColumnValue(propertyNames, entity, map); + const map = extractEmbeddedColumnValue(propertyNames, entity); + return Object.keys(map).length > 0 ? map : undefined; } else { // no embeds - no problems. Simply return column property name and its value of the entity @@ -589,6 +598,7 @@ export class ColumnMetadata { // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters] const propertyNames = [...this.embeddedMetadata.parentPropertyNames]; + const isEmbeddedArray = this.embeddedMetadata.isArray; // next we need to access post[data][information][counters][this.propertyName] to get column value from the counters // this recursive function takes array of generated property names and gets the post[data][information][counters] embed @@ -616,6 +626,8 @@ export class ColumnMetadata { } else if (this.referencedColumn) { value = this.referencedColumn.getEntityValue(embeddedObject[this.propertyName]); + } else if (isEmbeddedArray && Array.isArray(embeddedObject)) { + value = embeddedObject.map(o => o[this.propertyName]); } else { value = embeddedObject[this.propertyName]; } diff --git a/test/functional/mongodb/basic/array-columns/entity/Post.ts b/test/functional/mongodb/basic/array-columns/entity/Post.ts index 397edc0b71..4fc96c8cbf 100644 --- a/test/functional/mongodb/basic/array-columns/entity/Post.ts +++ b/test/functional/mongodb/basic/array-columns/entity/Post.ts @@ -25,10 +25,10 @@ export class Post { @Column() booleans: boolean[]; - @Column(type => Counters) + @Column(type => Counters, { array: true }) other1: Counters[]; - @Column(type => Counters) + @Column(type => Counters, { array: true }) other2: Counters[]; } \ No newline at end of file diff --git a/test/functional/mongodb/basic/array-columns/mongodb-array-columns.ts b/test/functional/mongodb/basic/array-columns/mongodb-array-columns.ts index fa38407056..1069150659 100644 --- a/test/functional/mongodb/basic/array-columns/mongodb-array-columns.ts +++ b/test/functional/mongodb/basic/array-columns/mongodb-array-columns.ts @@ -118,4 +118,25 @@ describe("mongodb > array columns", () => { }))); + it("should retrieve arrays from the column metadata", () => Promise.all(connections.map(async connection => { + const post = new Post(); + post.title = "Post"; + post.names = ["umed", "dima", "bakhrom"]; + post.numbers = [1, 0, 1]; + post.booleans = [true, false, false]; + post.counters = [ + new Counters(1, "number #1"), + new Counters(2, "number #2"), + new Counters(3, "number #3"), + ]; + post.other1 = []; + + const column = connection.getMetadata(Post) + .columns + .find(c => c.propertyPath === 'counters.text')!; + + const value = column.getEntityValue(post); + + expect(value).to.eql([ "number #1", "number #2", "number #3" ]); + }))); }); diff --git a/test/functional/mongodb/basic/mongo-repository/mongo-repository.ts b/test/functional/mongodb/basic/mongo-repository/mongo-repository.ts index 8839e8d8ed..11abe9579e 100644 --- a/test/functional/mongodb/basic/mongo-repository/mongo-repository.ts +++ b/test/functional/mongodb/basic/mongo-repository/mongo-repository.ts @@ -1,4 +1,5 @@ import "reflect-metadata"; +import {expect} from "chai"; import {Connection} from "../../../../../src/connection/Connection"; import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../../utils/test-utils"; import {Post} from "./entity/Post"; @@ -112,4 +113,20 @@ describe("mongodb > MongoRepository", () => { loadedPosts[1].text.should.be.equal("Everything about post #2"); }))); + + it("should ignore non-column properties", () => Promise.all(connections.map(async connection => { + // Github issue #5321 + const postRepository = connection.getMongoRepository(Post); + + await postRepository.save({ + title: "Hello", + text: "World", + unreal: "Not a Column" + }); + + const loadedPosts = await postRepository.find(); + + expect(loadedPosts).to.have.length(1); + expect(loadedPosts[0]).to.not.have.property("unreal"); + }))); });