Skip to content

Commit

Permalink
fix(model.d): fix type for count and findAndCountAll (sequelize#1…
Browse files Browse the repository at this point in the history
…3786)

* fix(model): add stop mysql-8 script

* fix(model): fix type for `count` and `findAndCountAll`

Co-authored-by: Zoé <zoe@ephys.dev>
  • Loading branch information
2 people authored and aliatsis committed Jun 2, 2022
1 parent f0e6cfb commit 9e5bead
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 100 deletions.
171 changes: 88 additions & 83 deletions docs/manual/core-concepts/model-querying-finders.md
@@ -1,83 +1,88 @@
# Model Querying - Finders

Finder methods are the ones that generate `SELECT` queries.

By default, the results of all finder methods are instances of the model class (as opposed to being just plain JavaScript objects). This means that after the database returns the results, Sequelize automatically wraps everything in proper instance objects. In a few cases, when there are too many results, this wrapping can be inefficient. To disable this wrapping and receive a plain response instead, pass `{ raw: true }` as an option to the finder method.

## `findAll`

The `findAll` method is already known from the previous tutorial. It generates a standard `SELECT` query which will retrieve all entries from the table (unless restricted by something like a `where` clause, for example).

## `findByPk`

The `findByPk` method obtains only a single entry from the table, using the provided primary key.

```js
const project = await Project.findByPk(123);
if (project === null) {
console.log('Not found!');
} else {
console.log(project instanceof Project); // true
// Its primary key is 123
}
```

## `findOne`

The `findOne` method obtains the first entry it finds (that fulfills the optional query options, if provided).

```js
const project = await Project.findOne({ where: { title: 'My Title' } });
if (project === null) {
console.log('Not found!');
} else {
console.log(project instanceof Project); // true
console.log(project.title); // 'My Title'
}
```

## `findOrCreate`

The method `findOrCreate` will create an entry in the table unless it can find one fulfilling the query options. In both cases, it will return an instance (either the found instance or the created instance) and a boolean indicating whether that instance was created or already existed.

The `where` option is considered for finding the entry, and the `defaults` option is used to define what must be created in case nothing was found. If the `defaults` do not contain values for every column, Sequelize will take the values given to `where` (if present).

Let's assume we have an empty database with a `User` model which has a `username` and a `job`.

```js
const [user, created] = await User.findOrCreate({
where: { username: 'sdepold' },
defaults: {
job: 'Technical Lead JavaScript'
}
});
console.log(user.username); // 'sdepold'
console.log(user.job); // This may or may not be 'Technical Lead JavaScript'
console.log(created); // The boolean indicating whether this instance was just created
if (created) {
console.log(user.job); // This will certainly be 'Technical Lead JavaScript'
}
```

## `findAndCountAll`

The `findAndCountAll` method is a convenience method that combines `findAll` and `count`. This is useful when dealing with queries related to pagination where you want to retrieve data with a `limit` and `offset` but also need to know the total number of records that match the query.

The `findAndCountAll` method returns an object with two properties:

* `count` - an integer - the total number records matching the query
* `rows` - an array of objects - the obtained records

```js
const { count, rows } = await Project.findAndCountAll({
where: {
title: {
[Op.like]: 'foo%'
}
},
offset: 10,
limit: 2
});
console.log(count);
console.log(rows);
```
# Model Querying - Finders

Finder methods are the ones that generate `SELECT` queries.

By default, the results of all finder methods are instances of the model class (as opposed to being just plain JavaScript objects). This means that after the database returns the results, Sequelize automatically wraps everything in proper instance objects. In a few cases, when there are too many results, this wrapping can be inefficient. To disable this wrapping and receive a plain response instead, pass `{ raw: true }` as an option to the finder method.

## `findAll`

The `findAll` method is already known from the previous tutorial. It generates a standard `SELECT` query which will retrieve all entries from the table (unless restricted by something like a `where` clause, for example).

## `findByPk`

The `findByPk` method obtains only a single entry from the table, using the provided primary key.

```js
const project = await Project.findByPk(123);
if (project === null) {
console.log('Not found!');
} else {
console.log(project instanceof Project); // true
// Its primary key is 123
}
```

## `findOne`

The `findOne` method obtains the first entry it finds (that fulfills the optional query options, if provided).

```js
const project = await Project.findOne({ where: { title: 'My Title' } });
if (project === null) {
console.log('Not found!');
} else {
console.log(project instanceof Project); // true
console.log(project.title); // 'My Title'
}
```

## `findOrCreate`

The method `findOrCreate` will create an entry in the table unless it can find one fulfilling the query options. In both cases, it will return an instance (either the found instance or the created instance) and a boolean indicating whether that instance was created or already existed.

The `where` option is considered for finding the entry, and the `defaults` option is used to define what must be created in case nothing was found. If the `defaults` do not contain values for every column, Sequelize will take the values given to `where` (if present).

Let's assume we have an empty database with a `User` model which has a `username` and a `job`.

```js
const [user, created] = await User.findOrCreate({
where: { username: 'sdepold' },
defaults: {
job: 'Technical Lead JavaScript'
}
});
console.log(user.username); // 'sdepold'
console.log(user.job); // This may or may not be 'Technical Lead JavaScript'
console.log(created); // The boolean indicating whether this instance was just created
if (created) {
console.log(user.job); // This will certainly be 'Technical Lead JavaScript'
}
```

## `findAndCountAll`

The `findAndCountAll` method is a convenience method that combines `findAll` and `count`. This is useful when dealing with queries related to pagination where you want to retrieve data with a `limit` and `offset` but also need to know the total number of records that match the query.

When `group` is not provided, the `findAndCountAll` method returns an object with two properties:

* `count` - an integer - the total number records matching the query
* `rows` - an array of objects - the obtained records

When `group` is provided, the `findAndCountAll` method returns an object with two properties:

* `count` - an array of objects - contains the count in each group and the projected attributes
* `rows` - an array of objects - the obtained records

```js
const { count, rows } = await Project.findAndCountAll({
where: {
title: {
[Op.like]: 'foo%'
}
},
offset: 10,
limit: 2
});
console.log(count);
console.log(rows);
```
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -236,6 +236,7 @@
"start-db2": "bash dev/db2/11.5/start.sh",
"stop-mariadb": "bash dev/mariadb/10.3/stop.sh",
"stop-mysql": "bash dev/mysql/5.7/stop.sh",
"stop-mysql-8": "bash dev/mysql/8.0/stop.sh",
"stop-postgres": "bash dev/postgres/10/stop.sh",
"stop-mssql": "bash dev/mssql/2019/stop.sh",
"stop-db2": "bash dev/db2/11.5/stop.sh",
Expand Down
32 changes: 20 additions & 12 deletions types/lib/model.d.ts
Expand Up @@ -634,17 +634,15 @@ export interface CountOptions<TAttributes = any>
/**
* Options for Model.count when GROUP BY is used
*/
export interface CountWithOptions<TAttributes = any> extends CountOptions<TAttributes> {
/**
* GROUP BY in sql
* Used in conjunction with `attributes`.
* @see Projectable
*/
group: GroupOption;
}
export type CountWithOptions<TAttributes = any> = SetRequired<CountOptions<TAttributes>, 'group'>

export interface FindAndCountOptions<TAttributes = any> extends CountOptions<TAttributes>, FindOptions<TAttributes> { }

interface GroupedCountResultItem {
[key: string]: unknown // projected attributes
count: number // the count for each group
}

/**
* Options for Model.build method
*/
Expand Down Expand Up @@ -1903,25 +1901,27 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut

/**
* Count number of records if group by is used
* @return Returns count for each group and the projected attributes.
*/
public static count<M extends Model>(
this: ModelStatic<M>,
options: CountWithOptions<M['_attributes']>
): Promise<Array<{ [groupKey: string]: unknown, count: number }>>;
): Promise<GroupedCountResultItem[]>;

/**
* Count the number of records matching the provided where clause.
*
* If you provide an `include` option, the number of matching associations will be counted instead.
* @return Returns count for each group and the projected attributes.
*/
public static count<M extends Model>(
this: ModelStatic<M>,
options?: CountOptions<M['_attributes']>
options?: Omit<CountOptions<M['_attributes']>, 'group'>
): Promise<number>;

/**
* Find all the rows matching your query, within a specified offset / limit, and get the total number of
* rows matching your query. This is very usefull for paging
* rows matching your query. This is very useful for paging
*
* ```js
* Model.findAndCountAll({
Expand Down Expand Up @@ -1953,6 +1953,14 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
* who have a profile will be counted. If we remove `required` from the include, both users with and
* without
* profiles will be counted
*
* This function also support grouping, when `group` is provided, the count will be an array of objects
* containing the count for each group and the projected attributes.
* ```js
* User.findAndCountAll({
* group: 'type'
* });
* ```
*/
public static findAndCountAll<M extends Model>(
this: ModelStatic<M>,
Expand All @@ -1961,7 +1969,7 @@ export abstract class Model<TModelAttributes extends {} = any, TCreationAttribut
public static findAndCountAll<M extends Model>(
this: ModelStatic<M>,
options: SetRequired<FindAndCountOptions<M['_attributes']>, 'group'>
): Promise<{ rows: M[]; count: number[] }>;
): Promise<{ rows: M[]; count: GroupedCountResultItem[] }>;

/**
* Find the maximum value of field
Expand Down
3 changes: 1 addition & 2 deletions types/test/count.ts → types/test/model-count.ts
Expand Up @@ -4,8 +4,7 @@ import { Model, Op } from 'sequelize';
class MyModel extends Model {}

expectTypeOf(MyModel.count()).toEqualTypeOf<Promise<number>>();
expectTypeOf(MyModel.count({ group: 'tag' }))
.toEqualTypeOf<Promise<Array<{ [groupKey: string]: unknown, count: number }>>>();
expectTypeOf(MyModel.count({ group: 'tag' })).toEqualTypeOf<Promise<({ [key: string]: unknown, count: number })[]>>();
expectTypeOf(MyModel.count({ col: 'tag', distinct: true })).toEqualTypeOf<Promise<number>>();
expectTypeOf(MyModel.count({
where: {
Expand Down
15 changes: 12 additions & 3 deletions types/test/model.ts
Expand Up @@ -52,13 +52,22 @@ MyModel.findAndCountAll({ include: OtherModel }).then(({ count, rows }) => {
});

MyModel.findAndCountAll({ include: OtherModel, group: ['MyModel.num'] }).then(({ count, rows }) => {
expectTypeOf(count).toEqualTypeOf<number[]>();
expectTypeOf(count).toEqualTypeOf<({ [key: string]: unknown, count: number })[]>();
expectTypeOf(rows).toEqualTypeOf<MyModel[]>();
});

MyModel.count({ include: OtherModel });
MyModel.count({ include: OtherModel }).then((count) => {
expectTypeOf(count).toEqualTypeOf<number>();
});

MyModel.count({ include: [MyModel], where: { '$num$': [10, 120] } }).then((count) => {
expectTypeOf(count).toEqualTypeOf<number>();
});

MyModel.count({ include: [MyModel], where: { '$num$': [10, 120] } });
MyModel.count({ group: 'type' }).then((result) => {
expectTypeOf(result).toEqualTypeOf<({ [key: string]: unknown, count: number })[]>();
expectTypeOf(result[0]).toMatchTypeOf<{ count: number }>();
});

MyModel.build({ int: 10 }, { include: OtherModel });

Expand Down

0 comments on commit 9e5bead

Please sign in to comment.