forked from duffelhq/paginator
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for all asc/desc sorting order combinations (duffel…
…hq#136) * feat: add support for all asc/desc sorting order combinations This patch adds support for all supported sorting orders when the field is nullable. It is now possible to specify the following extra sort orders in the cursor_fields: * asc_nulls_last, asc_nulls_first * desc_nulls_last, desc_nulls_first As the number of different combinations is large, we refactored the code that builds the dynamic filter expressions to make it a bit more manageable. Each sorting order is now a separate module. There is at least one problem with the current implementation though. The `:asc/:desc` default order, w.r.t. to null values may vary from vendor to vendor. For instance, we are following Postgres defaults. It means that `:asc = :asc_nulls_last` and `:desc = :desc_nulls_first`. However, if you are using Mysql instead, you get `:asc = :asc_nulls_first` and `:desc = :desc_nulls_last`. Somehow we need to factor in the adapter. However, I was not sure the best way to achieve that, I opted for lefting it out for now and instead asks for directions on that regard. * chore: remove function.identity and rebase against main * fix: dialyzer
- Loading branch information
Showing
8 changed files
with
456 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
defmodule Paginator.Ecto.Query.AscNullsFirst do | ||
@behaviour Paginator.Ecto.Query.DynamicFilterBuilder | ||
|
||
import Ecto.Query | ||
|
||
@impl Paginator.Ecto.Query.DynamicFilterBuilder | ||
def build_dynamic_filter(%{direction: :after, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(is_nil(field(query, ^args.column)) and ^args.next_filters) or | ||
not is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, next_filters: true}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
field(query, ^args.column) > ^args.value | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) > ^args.value | ||
) | ||
end | ||
|
||
def build_dynamic_filter(%{direction: :before, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
is_nil(field(query, ^args.column)) and ^args.next_filters | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, next_filters: true}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
field(query, ^args.column) < ^args.value or is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) < ^args.value or | ||
is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
defmodule Paginator.Ecto.Query.AscNullsLast do | ||
@behaviour Paginator.Ecto.Query.DynamicFilterBuilder | ||
|
||
import Ecto.Query | ||
|
||
@impl Paginator.Ecto.Query.DynamicFilterBuilder | ||
def build_dynamic_filter(%{direction: :after, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
is_nil(field(query, ^args.column)) and ^args.next_filters | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, next_filters: true}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
field(query, ^args.column) > ^args.value or is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) > ^args.value or | ||
is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(%{direction: :before, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(is_nil(field(query, ^args.column)) and ^args.next_filters) or | ||
not is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, next_filters: true}) do | ||
dynamic([{query, args.entity_position}], field(query, ^args.column) < ^args.value) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) < ^args.value | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
defmodule Paginator.Ecto.Query.DescNullsFirst do | ||
@behaviour Paginator.Ecto.Query.DynamicFilterBuilder | ||
|
||
import Ecto.Query | ||
|
||
@impl Paginator.Ecto.Query.DynamicFilterBuilder | ||
def build_dynamic_filter(%{direction: :before, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
is_nil(field(query, ^args.column)) and ^args.next_filters | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, next_filters: true}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
field(query, ^args.column) > ^args.value or is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) > ^args.value or | ||
is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(%{direction: :after, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(is_nil(field(query, ^args.column)) and ^args.next_filters) or | ||
not is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, next_filters: true}) do | ||
dynamic([{query, args.entity_position}], field(query, ^args.column) < ^args.value) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) < ^args.value | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
defmodule Paginator.Ecto.Query.DescNullsLast do | ||
@behaviour Paginator.Ecto.Query.DynamicFilterBuilder | ||
|
||
import Ecto.Query | ||
|
||
@impl Paginator.Ecto.Query.DynamicFilterBuilder | ||
def build_dynamic_filter(%{direction: :before, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(is_nil(field(query, ^args.column)) and ^args.next_filters) or | ||
not is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before, next_filters: true}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
field(query, ^args.column) > ^args.value | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :before}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) > ^args.value | ||
) | ||
end | ||
|
||
def build_dynamic_filter(%{direction: :after, value: nil, next_filters: true}) do | ||
raise("unstable sort order: nullable columns can't be used as the last term") | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, value: nil}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
is_nil(field(query, ^args.column)) and ^args.next_filters | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after, next_filters: true}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
field(query, ^args.column) < ^args.value or is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
|
||
def build_dynamic_filter(args = %{direction: :after}) do | ||
dynamic( | ||
[{query, args.entity_position}], | ||
(field(query, ^args.column) == ^args.value and ^args.next_filters) or | ||
field(query, ^args.column) < ^args.value or | ||
is_nil(field(query, ^args.column)) | ||
) | ||
end | ||
end |
Oops, something went wrong.