You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add ConvertOrderBy method for MongoDB-style sorting (#40)
Add `ORDER BY` support with `ConvertOrderBy` method.
- Converts MongoDB-style sort objects to PostgreSQL `ORDER BY` clauses.
- Supports both regular columns and JSONB fields with dual sorting.
- Includes integration tests and fuzz tests.
(given "customdata" is configured with `filter.WithNestedJSONB("customdata", "password", "playerCount")`)
98
98
99
99
100
+
## Order By Support
101
+
102
+
In addition to filtering, this package also supports converting MongoDB-style sort objects into PostgreSQL ORDER BY clauses using the `ConvertOrderBy` method:
db.Query("SELECT * FROM games ORDER BY " + orderBy)
114
+
```
115
+
116
+
### Sort Direction Values:
117
+
-`1`: Ascending (ASC)
118
+
-`-1`: Descending (DESC)
119
+
120
+
### Return value
121
+
The `ConvertOrderBy` method returns a string that can be directly used in an SQL ORDER BY clause. When the input is an empty object or `nil`, it returns an empty string. Keep in mind that the method does not add the `ORDER BY` keyword itself; you need to include it in your SQL query.
122
+
123
+
### JSONB Field Sorting:
124
+
For JSONB fields, the package generates sophisticated ORDER BY clauses that handle both numeric and text sorting:
125
+
126
+
```go
127
+
// With WithNestedJSONB("metadata", "created_at"):
// Generates: (CASE WHEN jsonb_typeof(metadata->'score') = 'number' THEN (metadata->>'score')::numeric END) DESC NULLS LAST, metadata->>'score' DESC NULLS LAST
131
+
```
132
+
133
+
This ensures proper sorting whether the JSONB field contains numeric or text values.
134
+
135
+
> [!TIP]
136
+
> Always add an `, id ASC` to your ORDER BY clause to ensure a consistent order (where `id` is your primary key).
137
+
> ```go
138
+
> if orderBy != "" {
139
+
> orderBy += ", "
140
+
> }
141
+
> orderBy += "id ASC"
142
+
> ```
143
+
100
144
## Difference with MongoDB
101
145
102
146
- The MongoDB query filters don't have the option to compare fields with each other. This package adds the `$field` operator to compare fields with each other.
`(CASE WHEN jsonb_typeof("customdata"->'map') = 'number' THEN ("customdata"->>'map')::numeric END) ASC NULLS LAST, "customdata"->>'map' ASC NULLS LAST`,
`(CASE WHEN jsonb_typeof("customdata"->'map') = 'number' THEN ("customdata"->>'map')::numeric END) DESC NULLS LAST, "customdata"->>'map' DESC NULLS LAST`,
`(CASE WHEN jsonb_typeof("customdata"->'map') = 'number' THEN ("customdata"->>'map')::numeric END) ASC NULLS LAST, "customdata"->>'map' ASC NULLS LAST, (CASE WHEN jsonb_typeof("customdata"->'bar') = 'number' THEN ("customdata"->>'bar')::numeric END) DESC NULLS LAST, "customdata"->>'bar' DESC NULLS LAST`,
`"created_at" ASC NULLS LAST, (CASE WHEN jsonb_typeof("customdata"->'map') = 'number' THEN ("customdata"->>'map')::numeric END) DESC NULLS LAST, "customdata"->>'map' DESC NULLS LAST`,
700
+
nil,
701
+
},
702
+
{
703
+
"field name with spaces",
704
+
[]filter.Option{filter.WithAllowAllColumns()},
705
+
`{"my_field": 1}`,
706
+
`"my_field" ASC NULLS LAST`,
707
+
nil,
708
+
},
709
+
{
710
+
"empty object",
711
+
[]filter.Option{filter.WithAllowAllColumns()},
712
+
`{}`,
713
+
``,
714
+
nil,
715
+
},
716
+
{
717
+
"invalid field name for SQL injection",
718
+
[]filter.Option{filter.WithAllowAllColumns()},
719
+
`{"my field": 1}`,
720
+
``,
721
+
fmt.Errorf("invalid column name: my field"),
722
+
},
723
+
{
724
+
"invalid direction value",
725
+
[]filter.Option{filter.WithAllowAllColumns()},
726
+
`{"playerCount": 2}`,
727
+
``,
728
+
fmt.Errorf("invalid order direction for field playerCount: 2 (must be 1 or -1)"),
729
+
},
730
+
{
731
+
"invalid direction string",
732
+
[]filter.Option{filter.WithAllowAllColumns()},
733
+
`{"playerCount": "asc"}`,
734
+
``,
735
+
fmt.Errorf("invalid order direction for field playerCount: asc (must be 1 or -1)"),
0 commit comments