Skip to content

Commit

Permalink
feat(core): add Collection.slice() method (#4608)
Browse files Browse the repository at this point in the history
Adds support for `Collection.slice()`

---------

Co-authored-by: Martin Adámek <banan23@gmail.com>
  • Loading branch information
rubiin and B4nan committed Aug 11, 2023
1 parent 79af75c commit 7c99c37
Show file tree
Hide file tree
Showing 22 changed files with 104 additions and 17 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Expand Up @@ -148,7 +148,8 @@ module.exports = {
'space-before-blocks': ['error', 'always'],
'brace-style': ['error', '1tbs', { allowSingleLine: true }],
'keyword-spacing': ['error', { before: true, after: true }],
'space-in-parens': ['error', 'never']
'space-in-parens': ['error', 'never'],
'comma-spacing': 'error',
},
'settings': {}
};
3 changes: 3 additions & 0 deletions docs/docs/collections.md
Expand Up @@ -40,6 +40,9 @@ author.books.remove(book);
console.log(author.books.contains(book)); // false
author.books.add(book);
console.log(author.books.count()); // 1
console.log(author.books.slice(0, 1)); // Book[]
console.log(author.books.slice()); // Book[]
console.log(author.books.slice().length); // 1
author.books.removeAll();
console.log(author.books.isEmpty()); // true
console.log(author.books.contains(book)); // false
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/entity/ArrayCollection.ts
Expand Up @@ -177,6 +177,31 @@ export class ArrayCollection<T extends object, O extends object> {
return this.items.has(entity);
}


/**
* Extracts a slice of elements starting at position start to end (exclusive) of the collection.
* If end is null it returns all elements from start to the end of the collection.
*/
slice(start = 0, end?: number): T[] {
let index = 0;
end ??= this.items.size;
const items: T[] = [];

for (const item of this.items) {
if (index === end) {
break;
}

if (index >= start && index < end) {
items.push(item);
}

index++;
}

return items;
}

count(): number {
return this.items.size;
}
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/entity/Collection.ts
Expand Up @@ -204,6 +204,14 @@ export class Collection<T extends object, O extends object = object> extends Arr
return super.isEmpty();
}

/**
* @inheritDoc
*/
slice(start?: number, end?: number): T[] {
this.checkInitialized();
return super.slice(start, end);
}

shouldPopulate(): boolean {
return this._populated && !this._lazyInitialized;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/seeder/src/SeedManager.ts
@@ -1,5 +1,5 @@
import globby from 'globby';
import type { Constructor, EntityManager, ISeedManager , MikroORM } from '@mikro-orm/core';
import type { Constructor, EntityManager, ISeedManager, MikroORM } from '@mikro-orm/core';
import { Utils } from '@mikro-orm/core';
import type { Seeder } from './Seeder';
import { ensureDir, writeFile } from 'fs-extra';
Expand Down
6 changes: 6 additions & 0 deletions tests/EntityManager.mongo.test.ts
Expand Up @@ -846,6 +846,12 @@ describe('EntityManagerMongo', () => {
book = (await orm.em.findOne(Book, book._id))!;
expect(book.tags.count()).toBe(2);

// slice
expect(book.tags.slice()).toEqual(items);
expect(book.tags.slice(0, 2)).toEqual(items);
expect(book.tags.slice(1)).toEqual([items[1], items[2]]);
expect(book.tags.slice(0, 1)).toEqual([items[0]]);

// contains
expect(book.tags.contains(tagRepository.getReference(tag1.id))).toBe(true);
expect(book.tags.contains(tagRepository.getReference(tag2.id))).toBe(false);
Expand Down
6 changes: 6 additions & 0 deletions tests/EntityManager.mysql.test.ts
Expand Up @@ -1188,6 +1188,12 @@ describe('EntityManagerMySql', () => {
book = (await orm.em.findOne(Book2, book.uuid, { populate: ['tags'] as const }))!;
expect(book.tags.count()).toBe(3);

// slice
expect(book.tags.slice().length).toBe(3);
expect(book.tags.slice(0, 3).length).toBe(3);
expect(book.tags.slice(0, 1)).toEqual([book.tags[0]]);
expect(book.tags.slice(1, 2)).toEqual([book.tags[1]]);

// contains
expect(book.tags.contains(tagRepository.getReference(tag1.id))).toBe(true);
expect(book.tags.contains(tagRepository.getReference(tag2.id))).toBe(false);
Expand Down
4 changes: 3 additions & 1 deletion tests/EntityManager.postgre.test.ts
Expand Up @@ -1133,6 +1133,8 @@ describe('EntityManagerPostgre', () => {
orm.em.clear();
book = (await orm.em.findOne(Book2, book.uuid, { populate: ['tags'] as const }))!;
expect(book.tags.count()).toBe(3);
expect(book.tags.slice().length).toBe(3);
expect(book.tags.slice(0, 1)).toEqual([book.tags[0]]);

// contains
expect(book.tags.contains(tagRepository.getReference(tag1.id))).toBe(true);
Expand Down Expand Up @@ -1956,7 +1958,7 @@ describe('EntityManagerPostgre', () => {
}

console.time('perf: one to many via em.create()');
const author = orm.em.create(Author2,{
const author = orm.em.create(Author2, {
name: 'Jon Snow',
email: 'snow@wall.st',
books,
Expand Down
5 changes: 5 additions & 0 deletions tests/EntityManager.sqlite.test.ts
Expand Up @@ -671,6 +671,11 @@ describe('EntityManagerSqlite', () => {
book = (await orm.em.findOne(Book3, book.id, { populate: ['tags'] as const }))!;
expect(book.tags.count()).toBe(2);

// slice
expect(book.tags.slice().length).toBe(2);
expect(book.tags.slice(0, 2).length).toBe(2);
expect(book.tags.slice(0, 1)).toEqual([book.tags[0]]);

// contains
expect(book.tags.contains(tagRepository.getReference(tag1.id))).toBe(true);
expect(book.tags.contains(tagRepository.getReference(tag2.id))).toBe(false);
Expand Down
5 changes: 5 additions & 0 deletions tests/EntityManager.sqlite2.test.ts
Expand Up @@ -674,6 +674,11 @@ describe.each(['sqlite', 'better-sqlite'] as const)('EntityManager (%s)', driver
book = (await orm.em.findOne(Book4, book.id, { populate: ['tags'] as const }))!;
expect(book.tags.count()).toBe(2);

// slice
expect(book.tags.slice().length).toBe(2);
expect(book.tags.slice(0, 2).length).toBe(2);
expect(book.tags.slice(0, 1)).toEqual([book.tags[0]]);

// contains
expect(book.tags.contains(tagRepository.getReference(tag1.id))).toBe(true);
expect(book.tags.contains(tagRepository.getReference(tag2.id))).toBe(false);
Expand Down
2 changes: 1 addition & 1 deletion tests/QueryBuilder.test.ts
Expand Up @@ -2942,7 +2942,7 @@ describe('QueryBuilder', () => {
spy.mockRestore();
});

test('limit of 0 limits results to 0',()=>{
test('limit of 0 limits results to 0', () => {
const expected = 'select `e0`.`id` from `book2` as `e0` limit 0';
const sql = orm.em.createQueryBuilder(Book2).select('id').limit(0).getFormattedQuery();
expect(sql).toBe(expected);
Expand Down
2 changes: 1 addition & 1 deletion tests/entities-schema/Book4.ts
@@ -1,4 +1,4 @@
import type { Collection, Reference , OptionalProps } from '@mikro-orm/core';
import type { Collection, Reference, OptionalProps } from '@mikro-orm/core';
import { EntitySchema, t } from '@mikro-orm/core';
import type { IBaseEntity5 } from './BaseEntity5';
import type { IAuthor4 } from './Author4';
Expand Down
2 changes: 1 addition & 1 deletion tests/entities-schema/BookTag4.ts
@@ -1,4 +1,4 @@
import type { Collection , OptionalProps } from '@mikro-orm/core';
import type { Collection, OptionalProps } from '@mikro-orm/core';
import { EntitySchema } from '@mikro-orm/core';
import type { IBaseEntity5 } from './BaseEntity5';
import type { IBook4 } from './Book4';
Expand Down
2 changes: 1 addition & 1 deletion tests/entities-schema/Publisher4.ts
@@ -1,4 +1,4 @@
import type { Collection , OptionalProps } from '@mikro-orm/core';
import type { Collection, OptionalProps } from '@mikro-orm/core';
import { EntitySchema } from '@mikro-orm/core';
import type { IBaseEntity5 } from './BaseEntity5';
import type { IBook4 } from './Book4';
Expand Down
Expand Up @@ -201,6 +201,11 @@ describe('custom pivot entity for m:n with additional properties (auto-discovere
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(3);

// slice
expect(order.products.slice().length).toBe(3);
expect(order.products.slice(0, 3).length).toBe(3);
expect(order.products.slice(0, 1)).toEqual([order.products[0]]);

// contains
expect(order.products.contains(productRepository.getReference(product1.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product2.id))).toBe(true);
Expand Down
Expand Up @@ -162,6 +162,11 @@ describe('custom pivot entity for m:n with additional properties (unidirectional
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(3);

// slice
expect(order.products.slice().length).toBe(3);
expect(order.products.slice(0, 3).length).toBe(3);
expect(order.products.slice(0, 1)).toEqual([order.products[0]]);

// contains
expect(order.products.contains(productRepository.getReference(product1.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product2.id))).toBe(true);
Expand Down
Expand Up @@ -156,6 +156,12 @@ describe('custom pivot entity for m:n with additional properties (unidirectional
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(3);


// slice
expect(order.products.slice().length).toBe(3);
expect(order.products.slice(0, 3).length).toBe(3);
expect(order.products.slice(0, 1)).toEqual([order.products[0]]);

// contains
expect(order.products.contains(productRepository.getReference(product1.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product2.id))).toBe(true);
Expand Down
Expand Up @@ -197,6 +197,11 @@ describe('custom pivot entity for m:n with additional properties (bidirectional,
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(3);

// slice
expect(order.products.slice().length).toBe(3);
expect(order.products.slice(0, 3).length).toBe(3);
expect(order.products.slice(0, 1)).toEqual([order.products[0]]);

// contains
expect(order.products.contains(productRepository.getReference(product1.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product2.id))).toBe(true);
Expand Down
Expand Up @@ -197,6 +197,11 @@ describe('custom pivot entity for m:n with additional properties (bidirectional)
order = (await orm.em.findOne(Order, order.id, { populate: ['products'] as const }))!;
expect(order.products.count()).toBe(3);

// slice
expect(order.products.slice().length).toBe(3);
expect(order.products.slice(0, 3).length).toBe(3);
expect(order.products.slice(0, 1)).toEqual([order.products[0]]);

// contains
expect(order.products.contains(productRepository.getReference(product1.id))).toBe(true);
expect(order.products.contains(productRepository.getReference(product2.id))).toBe(true);
Expand Down
16 changes: 8 additions & 8 deletions tests/features/read-replicas.test.ts
Expand Up @@ -81,7 +81,7 @@ describe('read-replicas', () => {
expect(mock.mock.calls[4][0]).toMatch(/via read connection 'read-.*'/);

// explicitly set to read
await orm.em.findOne(Author2, author, { connectionType: 'read',refresh: true });
await orm.em.findOne(Author2, author, { connectionType: 'read', refresh: true });
expect(mock.mock.calls[5][0]).toMatch(/via read connection 'read-.*'/);

// explicitly set to write
Expand Down Expand Up @@ -109,16 +109,16 @@ describe('read-replicas', () => {
expect(mock.mock.calls[4][0]).toMatch(/via read connection 'read-.*'/);

// explicitly set to read
await orm.em.count(Author2, {},{ connectionType: 'read' });
await orm.em.count(Author2, {}, { connectionType: 'read' });
expect(mock.mock.calls[5][0]).toMatch(/via read connection 'read-.*'/);

// explicitly set to write
await orm.em.count(Author2, {},{ connectionType: 'write' });
await orm.em.count(Author2, {}, { connectionType: 'write' });
expect(mock.mock.calls[6][0]).toMatch(/via write connection '127\.0\.0\.1'/);

// when running in a transaction will always use a write connection
await orm.em.transactional(async em => {
return em.count(Author2, {},{ connectionType: 'read' });
return em.count(Author2, {}, { connectionType: 'read' });
});
expect(mock.mock.calls[7][0]).toMatch(/begin.*via write connection '127\.0\.0\.1'/);
expect(mock.mock.calls[8][0]).toMatch(/select.*via write connection '127\.0\.0\.1'/);
Expand Down Expand Up @@ -185,7 +185,7 @@ describe('read-replicas', () => {
expect(mock.mock.calls[4][0]).toMatch(/via write connection '127\.0\.0\.1'/);

// explicitly set to read
await orm.em.findOne(Author2, author, { connectionType: 'read',refresh: true });
await orm.em.findOne(Author2, author, { connectionType: 'read', refresh: true });
expect(mock.mock.calls[5][0]).toMatch(/via read connection 'read-.*'/);

// explicitly set to write
Expand Down Expand Up @@ -215,16 +215,16 @@ describe('read-replicas', () => {
expect(mock.mock.calls[4][0]).toMatch(/via write connection '127\.0\.0\.1'/);

// explicitly set to read
await orm.em.count(Author2, {},{ connectionType: 'read' });
await orm.em.count(Author2, {}, { connectionType: 'read' });
expect(mock.mock.calls[5][0]).toMatch(/via read connection 'read-.*'/);

// explicitly set to write
await orm.em.count(Author2, {},{ connectionType: 'write' });
await orm.em.count(Author2, {}, { connectionType: 'write' });
expect(mock.mock.calls[6][0]).toMatch(/via write connection '127\.0\.0\.1'/);

// when running in a transaction will always use a write connection
await orm.em.transactional(async em => {
return em.count(Author2, {},{ connectionType: 'read' });
return em.count(Author2, {}, { connectionType: 'read' });
});
expect(mock.mock.calls[7][0]).toMatch(/begin.*via write connection '127\.0\.0\.1'/);
expect(mock.mock.calls[8][0]).toMatch(/select.*via write connection '127\.0\.0\.1'/);
Expand Down
2 changes: 1 addition & 1 deletion tests/issues/GH2379.test.ts
Expand Up @@ -110,7 +110,7 @@ describe('GH issue 2379', () => {

beforeAll(async () => {
orm = await MikroORM.init({
entities: [Order, Job, VendorBuyerRelationship,Member],
entities: [Order, Job, VendorBuyerRelationship, Member],
dbName: ':memory:',
driver: SqliteDriver,
});
Expand Down
2 changes: 1 addition & 1 deletion tests/issues/GH2815.test.ts
Expand Up @@ -29,7 +29,7 @@ export class Position2 {
@PrimaryKey()
id!: number;

@OneToOne(() => Leg2, (leg: Leg2) => leg.position, { owner: true, nullable: true , orphanRemoval: true })
@OneToOne(() => Leg2, (leg: Leg2) => leg.position, { owner: true, nullable: true, orphanRemoval: true })
leg?: any;

}
Expand Down

0 comments on commit 7c99c37

Please sign in to comment.