Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/mysql-typed.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

export async function paginatedEmails(nextPageToken?: string) {
const records = await users(db)
.find({
Expand Down Expand Up @@ -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.


Return the first `count` rows offset by `offset` number of rows. N.B. you can only use this method if you have first called `orderByAsc` or `orderByDesc` at least once.

If you have a large number of rows (more than some thousands), using an offset is inefficient as it will scan through the results. For larger sets, see the endless pagination example above.

```typescript
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`)
.limitOffset(10, offset);
return {
records: records.map((record) => record.email),
};
}
```

### first()

Return the first record. If there are no records, `null` is returned. N.B. you can only use this method if you have first called `orderByAsc` or `orderByDesc` at least once.
Expand Down
28 changes: 25 additions & 3 deletions docs/pg-typed.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".

const records = await users(db)
.find({
...(nextPageToken ? {email: gt(nextPageToken)} : {}),
Expand All @@ -498,19 +499,40 @@ export async function paginatedEmails(nextPageToken?: string) {
}

export async function printAllEmails() {
let page = await paginatedEmails();
let page = await endlessPaginatedEmails();
while (page.records.length) {
for (const email of page.records) {
console.log(email);
}
if (!page.nextPageToken) {
break;
}
page = await paginatedEmails(page.nextPageToken);
page = await endlessPaginatedEmails(page.nextPageToken);
}
}
```

### limitOffset(count, offset)

Return the first `count` rows offset by `offset` number of rows. N.B. you can only use this method if you have first called `orderByAsc` or `orderByDesc` at least once.

If you have a large number of rows (more than some thousands), using an offset is inefficient as it will scan through the results. For larger sets, see the endless pagination example above.

```typescript
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`)
.limitOffset(10, offset);
return {
records: records.map((record) => record.email),
};
}
```

### first()

Return the first record. If there are no records, `null` is returned. N.B. you can only use this method if you have first called `orderByAsc` or `orderByDesc` at least once.
Expand Down
17 changes: 16 additions & 1 deletion packages/mock-db-typed/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {sql, SQLQuery, Queryable} from '@databases/mock-db';
import type {Queryable, SQLQuery, sql} from '@databases/mock-db';

export interface SelectQuery<TRecord> {
all(): Promise<TRecord[]>;
Expand All @@ -14,6 +14,7 @@ export interface SelectQuery<TRecord> {
export interface OrderedSelectQuery<TRecord> extends SelectQuery<TRecord> {
first(): Promise<TRecord | null>;
limit(count: number): Promise<TRecord[]>;
limitOffset(count: number, offset: number): Promise<TRecord[]>;
}

class FieldQuery<T> {
Expand Down Expand Up @@ -134,6 +135,7 @@ class SelectQueryImplementation<TRecord>
{
public readonly orderByQueries: SQLQuery[] = [];
public limitCount: number | undefined;
public offsetCount: number | undefined;
private _selectFields: SQLQuery | undefined;

constructor(
Expand Down Expand Up @@ -164,6 +166,9 @@ class SelectQueryImplementation<TRecord>
if (this.limitCount) {
parts.push(sql`LIMIT ${this.limitCount}`);
}
if(this.offsetCount) {
parts.push(sql`OFFSET ${this.offsetCount}`);
}
return this._executeQuery(
parts.length === 1 ? parts[0] : sql.join(parts, sql` `),
);
Expand Down Expand Up @@ -205,6 +210,16 @@ class SelectQueryImplementation<TRecord>
this.limitCount = count;
return await this._getResults('limit');
}
public async limitOffset(count: number, offset: number) {
if (!this.orderByQueries.length) {
throw new Error(
'You cannot call "limitOffset" until after you call "orderByAsc" or "orderByDesc".',
);
}
this.limitCount = count;
this.offsetCount = offset;
return await this._getResults('limitOffset');
}
public async first() {
if (!this.orderByQueries.length) {
throw new Error(
Expand Down
13 changes: 13 additions & 0 deletions packages/mysql-typed/src/__tests__/index.test.mysql.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import connect, {sql} from '@databases/mysql';

import declareTables from '..';

// JSON added in 5.7
Expand Down Expand Up @@ -115,6 +116,18 @@ t('create users', async () => {
]
`);

const photoRecordsOffset = await photos(db)
.find({owner_user_id: 1})
.orderByAsc('cdn_url')
.limitOffset(2, 1);
expect(photoRecordsOffset.map((p) => p.cdn_url)).toMatchInlineSnapshot(`
Array [
"http://example.com/2",
"http://example.com/3",
]
`);


const photoRecordsDesc = await photos(db)
.find({owner_user_id: 1})
.orderByDesc('cdn_url')
Expand Down
17 changes: 16 additions & 1 deletion packages/mysql-typed/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {sql, SQLQuery, Queryable} from '@databases/mysql';
import {Queryable, SQLQuery, sql} from '@databases/mysql';

export interface SelectQuery<TRecord> {
all(): Promise<TRecord[]>;
Expand All @@ -14,6 +14,7 @@ export interface SelectQuery<TRecord> {
export interface OrderedSelectQuery<TRecord> extends SelectQuery<TRecord> {
first(): Promise<TRecord | null>;
limit(count: number): Promise<TRecord[]>;
limitOffset(count: number, offset: number): Promise<TRecord[]>;
}

class FieldQuery<T> {
Expand Down Expand Up @@ -134,6 +135,7 @@ class SelectQueryImplementation<TRecord>
{
public readonly orderByQueries: SQLQuery[] = [];
public limitCount: number | undefined;
public offsetCount: number | undefined;
private _selectFields: SQLQuery | undefined;

constructor(
Expand Down Expand Up @@ -164,6 +166,9 @@ class SelectQueryImplementation<TRecord>
if (this.limitCount) {
parts.push(sql`LIMIT ${this.limitCount}`);
}
if(this.offsetCount) {
parts.push(sql`OFFSET ${this.offsetCount}`);
}
return this._executeQuery(
parts.length === 1 ? parts[0] : sql.join(parts, sql` `),
);
Expand Down Expand Up @@ -205,6 +210,16 @@ class SelectQueryImplementation<TRecord>
this.limitCount = count;
return await this._getResults('limit');
}
public async limitOffset(count: number, offset: number) {
if (!this.orderByQueries.length) {
throw new Error(
'You cannot call "limitOffset" until after you call "orderByAsc" or "orderByDesc".',
);
}
this.limitCount = count;
this.offsetCount = offset;
return await this._getResults('limitOffset');
}
public async first() {
if (!this.orderByQueries.length) {
throw new Error(
Expand Down
11 changes: 11 additions & 0 deletions packages/pg-typed/src/__tests__/index.test.pg.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import connect, {pgFormat, sql} from '@databases/pg';

import Schema from './__generated__';
import defineTables from '..';

Expand Down Expand Up @@ -87,6 +88,16 @@ test('create users', async () => {
"http://example.com/2",
]
`);
const photoRecordsOffset = await photos(db)
.find({owner_user_id: forbes.id})
.orderByAsc('cdn_url')
.limitOffset(2, 1);
expect(photoRecordsOffset.map((p) => p.cdn_url)).toMatchInlineSnapshot(`
Array [
"http://example.com/2",
"http://example.com/3",
]
`);

const photoRecordsDesc = await photos(db)
.find({owner_user_id: forbes.id})
Expand Down
26 changes: 22 additions & 4 deletions packages/pg-typed/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import assertNever from 'assert-never';
import {SQLQuery, Queryable} from '@databases/pg';
import {
bulkUpdate,
bulkDelete,
BulkOperationOptions,
bulkCondition,
bulkDelete,
bulkInsertStatement,
bulkUpdate,
} from '@databases/pg-bulk';
import {Queryable, SQLQuery} from '@databases/pg';

import assertNever from 'assert-never';

const NO_RESULT_FOUND = `NO_RESULT_FOUND`;
const MULTIPLE_RESULTS_FOUND = `MULTIPLE_RESULTS_FOUND`;
Expand Down Expand Up @@ -69,11 +70,13 @@ export interface DistinctOrderedSelectQuery<TRecord>
orderByDescDistinct(key: keyof TRecord): DistinctOrderedSelectQuery<TRecord>;
first(): Promise<TRecord | null>;
limit(count: number): Promise<TRecord[]>;
limitOffset(count: number, offset: number): Promise<TRecord[]>;
}

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

type SpecialFieldQuery<T> =
Expand Down Expand Up @@ -453,6 +456,7 @@ interface SelectQueryOptions<TRecord> {
readonly direction: 'ASC' | 'DESC';
}[];
readonly limit: number | undefined;
readonly offset: number | undefined;
}
class SelectQueryImplementation<TRecord>
implements DistinctOrderedSelectQuery<TRecord>
Expand All @@ -463,6 +467,7 @@ class SelectQueryImplementation<TRecord>
direction: 'ASC' | 'DESC';
}[] = [];
private _limitCount: number | undefined;
private _offsetCount: number | undefined;
private _selectFields: readonly string[] | undefined;
private readonly _whereAnd: WhereCondition<TRecord>[] = [];

Expand All @@ -486,6 +491,7 @@ class SelectQueryImplementation<TRecord>
selectColumnNames: this._selectFields,
orderBy: this._orderByQueries,
limit: this._limitCount,
offset: this._offsetCount,
distinctColumnNames: this._distinctColumnNames,
whereAnd: this._whereAnd,
});
Expand Down Expand Up @@ -559,6 +565,16 @@ class SelectQueryImplementation<TRecord>
this._limitCount = count;
return await this._getResults('limit');
}
public async limitOffset(count: number, offset: number) {
if (!this._orderByQueries.length) {
throw new Error(
'You cannot call "limitOffset" until after you call "orderByAsc" or "orderByDesc".',
);
}
this._limitCount = count;
this._offsetCount = offset;
return await this._getResults('limitOffset');
}
public async first() {
if (!this._orderByQueries.length) {
throw new Error(
Expand Down Expand Up @@ -1078,6 +1094,7 @@ class Table<TRecord, TInsertParameters> {
selectColumnNames: selectFields,
orderBy: orderByQueries,
limit: limitCount,
offset: offsetCount,
distinctColumnNames,
whereAnd,
}) => {
Expand Down Expand Up @@ -1128,6 +1145,7 @@ class Table<TRecord, TInsertParameters> {
)}`
: null,
limitCount ? sql`LIMIT ${limitCount}` : null,
offsetCount ? sql`OFFSET ${offsetCount}` : null,
].filter(<T>(v: T): v is Exclude<T, null> => v !== null),
sql` `,
),
Expand Down
Loading