Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LimitOffset implementation #249

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

miunau
Copy link

@miunau miunau commented Jul 31, 2022

To address #234

I've added a skeleton for a limitOffset next to limit that would pass in an offset value for pg-typed and mysql-typed.

This probably still needs some work but I just wanted to ask if you're open for such a feature.

I also added some tests that seem to run.

@rollingversions
Copy link

There is no change log for this pull request yet.

Create a changelog

Copy link
Owner

@ForbesLindesay ForbesLindesay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very open to a feature like this. Offset is often the easiest way to handle pagination. I think rather than a combined limitOffset function that does both the offset and limit, I'd prefer that we add an .offset(count: number) method that you can then chain a .limit(count: number) onto:

export interface OrderedSelectQueryWithOffset<TRecord> extends SelectQuery<TRecord> {
  first(): Promise<TRecord | null>;
  limit(count: number): Promise<TRecord[]>;
}
export interface OrderedSelectQuery<TRecord> extends OrderedSelectQueryWithOffset<TRecord> {
  offset(count: number): Promise<OrderedSelectQueryWithOffset<TRecord>>;
}

Splitting out these two interfaces ensures you cannot call .offset(...) multiple times.

Usage would be:

import db, {users} from './database';

// Example for simple offset-based pagination
export async function offsetPaginatedEmails(offset?: number = 0) {
  const records = await users(db)
    .find()
    .orderByAsc(`email`)
    .offset(offset)
    .limit(10);
  return {
    records: records.map((record) => record.email),
  };
}

@ForbesLindesay
Copy link
Owner

P.S. it would save me time when merging if you also fill in the change log on Rolling Versions (see automated comment) for pg-typed and mysql-typed.

@miunau
Copy link
Author

miunau commented Aug 2, 2022

OK I'm moving house right now but will take a look soon.

@miunau
Copy link
Author

miunau commented Aug 20, 2022

@ForbesLindesay I gave it a shot but I'm not sure how the interface composing should turn out based on your example. I've ended up with tests not recognising offset or limit, or orderByAsc/orderByDesc breaking due to mismatched types. For example, returning Promise<OrderedSelectQueryWithOffset<TRecord>> from offset() will block .limit chaining. Could you give more guidance on how you were thinking about the implementation?

Copy link
Owner

@ForbesLindesay ForbesLindesay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see any attempt here to make the suggested change. I can offer feedback if you have a go and it doesn't work, but all this code just uses limitOffset as a single method. Please push what you have, even if it's not perfect, so I can see/review it.

@@ -226,6 +226,7 @@ Return the first `count` rows. N.B. you can only use this method if you have fir
```typescript
import db, {users} from './database';

// Example for an endless pagination. Expects an email to be passed in, from where it returns 10 more rows.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this comment makes sense. There's nothing inherently "endless" about this pagination. It's commonly known as "token based pagination"

@@ -253,6 +254,27 @@ export async function printAllEmails() {
}
```

### limitOffset(count, offset)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need updating for the new .offset(count).limit(count) API.

@@ -484,7 +484,8 @@ Return the first `count` rows. N.B. you can only use this method if you have fir
```typescript
import db, {users} from './database';

export async function paginatedEmails(nextPageToken?: string) {
// Example for an endless pagination. Expects an email to be passed in, from where it returns 10 more rows.
export async function endlessPaginatedEmails(nextPageToken?: string) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I don't think "endless" adds clarity here. I think this example can be left more or less as is. If you want to give it a name, the two types of pagination are "offset based pagination" and "token based pagination".

@tripflex
Copy link

i agree to match chaining it should be .offset honestly i'm surprised this doesn't exist already

@tripflex
Copy link

I'm very open to a feature like this. Offset is often the easiest way to handle pagination. I think rather than a combined limitOffset function that does both the offset and limit, I'd prefer that we add an .offset(count: number) method that you can then chain a .limit(count: number) onto:

export interface OrderedSelectQueryWithOffset<TRecord> extends SelectQuery<TRecord> {
  first(): Promise<TRecord | null>;
  limit(count: number): Promise<TRecord[]>;
}
export interface OrderedSelectQuery<TRecord> extends OrderedSelectQueryWithOffset<TRecord> {
  offset(count: number): Promise<OrderedSelectQueryWithOffset<TRecord>>;
}

Splitting out these two interfaces ensures you cannot call .offset(...) multiple times.

Usage would be:

import db, {users} from './database';

// Example for simple offset-based pagination
export async function offsetPaginatedEmails(offset?: number = 0) {
  const records = await users(db)
    .find()
    .orderByAsc(`email`)
    .offset(offset)
    .limit(10);
  return {
    records: records.map((record) => record.email),
  };
}

Seems pretty simple to implement, should I start a new PR or did you want to do it or what would you prefer?

@tripflex
Copy link

@ForbesLindesay I took my first stab at this, can you please take a look and let me know of any changes required or any input on handling this?

#300

Thanks!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants