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

feat(pg): support ordering with NULLS FIRST / NULLS LAST #332

Merged
merged 5 commits into from
Nov 21, 2018
Merged

feat(pg): support ordering with NULLS FIRST / NULLS LAST #332

merged 5 commits into from
Nov 21, 2018

Conversation

mattbretl
Copy link
Contributor

No description provided.

@mattbretl
Copy link
Contributor Author

This allows plugins to solve #331

Copy link
Member

@benjie benjie left a comment

Choose a reason for hiding this comment

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

You're on fire, @mattbretl! This looks good to me; we need a test for it before I can merge it though.

? sql.fragment` NULLS FIRST`
: nullsFirst === false
? sql.fragment` NULLS LAST`
: null
Copy link
Member

Choose a reason for hiding this comment

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

I had to actually read the source to make sure null was okay here! It is ✅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I find that really handy.. don't change it! 😄

@mattbretl
Copy link
Contributor Author

Given how the tests are structured, I can add a new integration test in postgraphile-core/__tests__/integration/schema, appending a small plugin to expose additional orderBy enums that use NULLS LAST. Is that what you had in mind?

@mattbretl
Copy link
Contributor Author

Or.. I could add an orderByNullsLast option to PgConnectionArgOrderBy.js, and use that (if provided) when calling queryBuilder.orderBy(). Then the test would be as simple as passing orderByNullsLast: true through graphileBuildOptions. That's probably all that most people (including myself) will want to accomplish with this.. overriding the PG default of NULLS FIRST for DESC.

@benjie
Copy link
Member

benjie commented Nov 2, 2018

I think the latter makes sense and requires minimal effort. I don't really care so long as it is tested and can be show to work both ways. Basically what you're saying is "treat nulls as if they are smaller than everything" - i.e. they're first when ascending and last when descending?

Can you flag that this behaviour will become the default in v5? I always want it so that when you reverse the order (ASC -> DESC) the rows return in the exact opposite order (which is not the case currently, if there are nulls).

Thanks Matt!

@benjie
Copy link
Member

benjie commented Nov 2, 2018

I wonder if there's a performance impact of adding the NULLS FIRST/LAST clause...

@mattbretl
Copy link
Contributor Author

Basically what you're saying is "treat nulls as if they are smaller than everything" - i.e. they're first when ascending and last when descending?

Actually, no. This is about using NULLS LAST for both ASC and DESC. As a simple example, consider a tasks table with a nullable integer priority field. (Tasks can optionally be assigned a priority.) On the UI side, if you're sorting by high or low priority, you probably want to show prioritized tasks before unprioritized tasks, and that requires specifying NULLS LAST for DESC.

If 5 is high-priority and 1 is low-priority:

  • High-priority tasks: ORDER BY priority DESC NULLS LAST (without NULLS LAST, you get all of the unprioritized tasks showing up first.. definitely not what you want)
  • Low-priority tasks: ORDER BY priority ASC

If 1 is high-priority and 5 is low-priority:

  • High-priority tasks: ORDER BY priority ASC
  • Low-priority tasks: ORDER BY priority DESC NULLS LAST (without NULLS LAST, you get all of the unprioritized tasks showing up first.. probably not what you want)

Can you flag that this behaviour will become the default in v5? I always want it so that when you reverse the order (ASC -> DESC) the rows return in the exact opposite order (which is not the case currently, if there are nulls).

It works that way currently. Records are returned in the exact opposite order, with nulls treated as greater than all non-nulls. So there should be no need to change the default in v5.

@benjie
Copy link
Member

benjie commented Nov 3, 2018

Hmmm. Add --order-nulls-last flag then I guess.

@mattbretl
Copy link
Contributor Author

I wasn't sure if you wanted to add yet-another global config option, so I left it at the graphileBuildOptions level for now. Let me know what you think.

@mattbretl
Copy link
Contributor Author

I wonder if there's a performance impact of adding the NULLS FIRST/LAST clause...

From the PG docs..

By default, B-tree indexes store their entries in ascending order with nulls last. This means that a forward scan of an index on column x produces output satisfying ORDER BY x (or more verbosely, ORDER BY x ASC NULLS LAST). The index can also be scanned backward, producing output satisfying ORDER BY x DESC (or more verbosely, ORDER BY x DESC NULLS FIRST, since NULLS FIRST is the default for ORDER BY DESC).

So unless you explicitly create a NULLS LAST index, using DESC NULLS LAST could have a performance impact.

@benjie
Copy link
Member

benjie commented Nov 21, 2018

Ohhhh. Thanks for sharing. Reviewing now 👍

Copy link
Member

@benjie benjie 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 going to merge this. A small follow-up PR that adds | null to the relevant places would be welcome but TBH I'm happy to cross this bridge when we get to it.

@@ -25,7 +25,7 @@ export default class QueryBuilder {
public where(exprGen: SQLGen): void;
public whereBound(exprGen: SQLGen, isLower: boolean): void;
public setOrderIsUnique(): void;
public orderBy(exprGen: SQLGen, ascending: boolean): void;
public orderBy(exprGen: SQLGen, ascending: boolean, nullsFirst: boolean): void;
Copy link
Member

Choose a reason for hiding this comment

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

Technically this should be nullsFirst?: boolean because it doesn't have to be specified

Copy link
Member

Choose a reason for hiding this comment

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

You know what... Lets have it required.

@@ -236,9 +236,9 @@ class QueryBuilder {
setOrderIsUnique() {
this.data.orderIsUnique = true;
}
orderBy(exprGen: SQLGen, ascending: boolean = true) {
orderBy(exprGen: SQLGen, ascending: boolean = true, nullsFirst: boolean) {
Copy link
Member

Choose a reason for hiding this comment

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

Technically this should also be nullsFirst?: boolean because it doesn't have to be specified. Huh, TypeScript and Flow use the same syntax for this 👍

Copy link
Member

Choose a reason for hiding this comment

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

Actually it should be nullsFirst: boolean | null = null I think.

Copy link
Member

Choose a reason for hiding this comment

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

You know what... Lets have it required. It should still be | null technically...

@benjie benjie merged commit 545d082 into graphile:master Nov 21, 2018
@mattbretl mattbretl deleted the order-by-nulls-first-or-last branch November 21, 2018 18:36
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.

None yet

2 participants