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

Add nullslast and nullsfirst support to getList #164

Merged
merged 1 commit into from
Sep 4, 2024
Merged
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,12 @@ const [create, { isLoading, error }] = useCreate(
```

### Null sort order

Postgrest supports specifying the position of nulls in [sort ordering](https://postgrest.org/en/v12/references/api/tables_views.html#ordering). This can be configured via an optional data provider parameter:

```jsx
import { PostgRestSortOrder, IDataProviderConfig } from '@raphiniert/ra-data-postgrest';

const config: IDataProviderConfig = {
...
sortOrder: PostgRestSortOrder.AscendingNullsLastDescendingNullsLast
Expand All @@ -207,8 +210,23 @@ const config: IDataProviderConfig = {
const dataProvider = postgrestRestProvider(config);
```

This parameter impacts the `getList` and `getManyReference` calls.

It is important to note that null positioning in sort will impact index utilization so in some cases you'll want to add corresponding index on the database side.

You can also override this parameter on a per-query basis by passing `nullsfirst: true` or `nullslast: true` in the `meta` object of the query:

```jsx
const { data, total, isLoading, error } = useGetList(
'posts',
{
pagination: { page: 1, perPage: 10 },
sort: { field: 'published_at', order: 'DESC' },
meta: { nullslast: true }
}
);
```

### Vertical filtering
Postgrest supports a feature of [Vertical Filtering (Columns)](https://postgrest.org/en/stable/api.html#vertical-filtering-columns). Within the react-admin hooks this feature can be used as in the following example:

Expand Down
34 changes: 33 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export const defaultPrimaryKeys = new Map<string, PrimaryKey>();

export const defaultSchema = () => '';

export { PostgRestSortOrder };

export interface IDataProviderConfig {
apiUrl: string;
httpClient: (string, Options) => Promise<any>;
Expand Down Expand Up @@ -127,7 +129,37 @@ export default (config: IDataProviderConfig): DataProvider => ({
};

if (field) {
query.order = getOrderBy(field, order, primaryKey);
query.order = getOrderBy(
field,
order,
primaryKey,
config.sortOrder
);
if (
params.meta?.nullsfirst &&
!query.order.includes('nullsfirst')
) {
query.order = query.order.includes('nullslast')
? query.order.replace('nullslast', 'nullsfirst')
: `${query.order}.nullsfirst`;
}
if (
params.meta?.nullsfirst === false &&
query.order.includes('nullsfirst')
) {
query.order = query.order.replace('.nullsfirst', '');
}
if (params.meta?.nullslast && !query.order.includes('nullslast')) {
query.order = query.order.includes('nullsfirst')
? query.order.replace('nullsfirst', 'nullslast')
: `${query.order}.nullslast`;
}
if (
params.meta?.nullslast === false &&
query.order.includes('nullslast')
) {
query.order = query.order.replace('.nullslast', '');
}
}

if (select) {
Expand Down
2 changes: 1 addition & 1 deletion src/urlBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export const getQuery = (
return result;
};

export enum PostgRestSortOrder {
export const enum PostgRestSortOrder {
AscendingNullsLastDescendingNullsFirst = 'asc,desc',
AscendingNullsLastDescendingNullsLast = 'asc,desc.nullslast',
AscendingNullsFirstDescendingNullsFirst = 'asc.nullsfirst,desc',
Expand Down
249 changes: 249 additions & 0 deletions tests/dataProvider/getList.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { makeTestFromCase, Case } from './helper';
import { PostgRestSortOrder } from '../../src/index';

describe('getList specific', () => {
const method = 'getList';
Expand Down Expand Up @@ -50,4 +51,252 @@ describe('getList specific', () => {
];

cases.forEach(makeTestFromCase);

describe('meta', () => {
describe('nullsfirst', () => {
makeTestFromCase({
test: 'nullsfirst true added in meta changes the sort order',
method,
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'ASC',
},
filter: {},
meta: { nullsfirst: true },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.asc.nullsfirst`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
makeTestFromCase({
test: 'nullsfirst true added in meta is compatible with the default sort order',
method,
config: {
sortOrder:
PostgRestSortOrder.AscendingNullsFirstDescendingNullsFirst,
},
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'ASC',
},
filter: {},
meta: { nullsfirst: true },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.asc.nullsfirst`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
makeTestFromCase({
test: 'nullsfirst true added in meta overrides the default sort order',
method,
config: {
sortOrder:
PostgRestSortOrder.AscendingNullsLastDescendingNullsFirst,
},
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'ASC',
},
filter: {},
meta: { nullsfirst: true },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.asc.nullsfirst`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
makeTestFromCase({
test: 'nullsfirst false added in meta overrides the default sort order',
method,
config: {
sortOrder:
PostgRestSortOrder.AscendingNullsFirstDescendingNullsFirst,
},
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'ASC',
},
filter: {},
meta: { nullsfirst: false },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.asc`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
});
});

describe('nullslast', () => {
makeTestFromCase({
test: 'nullslast true added in meta changes the sort order',
method,
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'DESC',
},
filter: {},
meta: { nullslast: true },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.desc.nullslast`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
makeTestFromCase({
test: 'nullslast true added in meta is compatible with the default sort order',
method,
config: {
sortOrder:
PostgRestSortOrder.AscendingNullsLastDescendingNullsLast,
},
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'DESC',
},
filter: {},
meta: { nullslast: true },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.desc.nullslast`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
makeTestFromCase({
test: 'nullslast true added in meta overrides the default sort order',
method,
config: {
sortOrder:
PostgRestSortOrder.AscendingNullsFirstDescendingNullsFirst,
},
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'DESC',
},
filter: {},
meta: { nullslast: true },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.desc.nullslast`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
makeTestFromCase({
test: 'nullslast false added in meta overrides the default sort order',
method,
config: {
sortOrder:
PostgRestSortOrder.AscendingNullsLastDescendingNullsLast,
},
resource: 'posts',
params: {
pagination: {
page: 1,
perPage: 10,
},
sort: {
field: 'id',
order: 'DESC',
},
filter: {},
meta: { nullslast: false },
},
expectedUrl: `/posts?offset=0&limit=10&order=id.desc`,
expectedOptions: {
headers: {
accept: 'application/json',
prefer: 'count=exact',
},
},
httpClientResponseHeaders: {
'content-range': '0-9/100',
},
});
});
});
Loading
Loading