arel-extensions 9.0.0 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -1
- data/README.md +33 -3
- data/lib/arel/extensions/version.rb +1 -1
- data/lib/arel/visitors/postgresql_extensions.rb +30 -16
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99efadd34c12e304c5204c9dc2717bad533bfb20c9c280a036a8abc3110ab34a
|
|
4
|
+
data.tar.gz: 66c064d703b2531c7676e3a91bef9f0dd813355e24108c7966770ea1fba298ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 043be028082fea8d31c8b427db8a703b7f4a2f1916879e77455f05847086964840b4cae0886e73930ace056dc9e86bcb7c375c4794a64f5ac5a85c19d5f9b61b
|
|
7
|
+
data.tar.gz: 89d8e35f5503774cc499c93fa0cc9ea427dec2cecc27cd6ee01f5d52f9f66f9c278078c80c865d7fc4bac8362e9c860f7fe33b1a90b572c4823621cfe6be9ba3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
|
-
## [9.0.
|
|
1
|
+
## [9.0.1] - 2026-08-30
|
|
2
|
+
|
|
3
|
+
### Security
|
|
4
|
+
- Fixed a SQL injection in the PostgreSQL visitor's JSON path handling
|
|
5
|
+
([GHSA-75hc-9q9v-9cv2], CWE-89, high). `key`/`dig` path segments were
|
|
6
|
+
interpolated straight into a `#>'{...}'` array literal, so a segment
|
|
7
|
+
containing `}'` could close the literal and have the rest of it executed as
|
|
8
|
+
SQL. Most reachable through activerecord-filter, where a filter key like
|
|
9
|
+
`"metadata.<payload>"` on a json/jsonb column puts request input into `dig`.
|
|
10
|
+
Segments are now emitted as a quoted `#> array[...]`, which PostgreSQL folds
|
|
11
|
+
back to the same `text[]` constant (existing expression indexes still match).
|
|
12
|
+
Reported by [@saidM](https://github.com/saidM).
|
|
13
|
+
- `cast_as` now validates the type name and raises `ArgumentError` unless it
|
|
14
|
+
looks like a type identifier. Not reachable from activerecord-filter, but it
|
|
15
|
+
was the same class of raw interpolation.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- A path segment is now always a single segment: `key('a,b')` emits
|
|
19
|
+
`array['a,b']`, where the old raw `'{a,b}'` literal let PostgreSQL split it
|
|
20
|
+
on the comma into two segments. Use `dig('a', 'b')` for multi-segment paths.
|
|
21
|
+
|
|
22
|
+
[GHSA-75hc-9q9v-9cv2]: https://github.com/malomalo/arel-extensions/security/advisories/GHSA-75hc-9q9v-9cv2
|
|
23
|
+
|
|
24
|
+
## [9.0.0] - 2026-08-27
|
|
2
25
|
|
|
3
26
|
### Changed
|
|
4
27
|
- Switched to independent Semantic Versioning. Prior releases tracked the Rails
|
data/README.md
CHANGED
|
@@ -63,14 +63,44 @@ tags.excludes(other) # NOT (tags @> other)
|
|
|
63
63
|
```ruby
|
|
64
64
|
data = User.arel_table[:data]
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
data
|
|
66
|
+
# Keys in a Object
|
|
67
|
+
data['name'] # data #> array['name']
|
|
68
|
+
data.key('name') # data #> array['name']
|
|
69
|
+
data.index('name') # data #> array['name']
|
|
70
|
+
|
|
71
|
+
# Integer index in a Array
|
|
72
|
+
data[0] # data #> array['0']
|
|
73
|
+
data.key(0) # data #> array['0']
|
|
74
|
+
data.index(0) # data #> array['0']
|
|
75
|
+
|
|
76
|
+
# Dig and other operators
|
|
77
|
+
data.dig('address', 'zip') # data #> array['address','zip']
|
|
78
|
+
data.dig('tags', -1) # data #> array['tags','-1']
|
|
68
79
|
data.has_key('name') # data ? 'name'
|
|
69
80
|
data.has_keys('a', 'b') # data ?& array['a','b']
|
|
70
81
|
data.has_any_key('a', 'b') # data ?| array['a','b']
|
|
71
|
-
data.cast_as('int') # (data)::int
|
|
72
82
|
```
|
|
73
83
|
|
|
84
|
+
A segment may be an integer, which PostgreSQL reads as an array index (negative
|
|
85
|
+
counts from the end); out of range yields `NULL`.
|
|
86
|
+
|
|
87
|
+
Path segments are quoted, so they are safe to build from untrusted input.
|
|
88
|
+
PostgreSQL folds the array back to `'{address,zip}'::text[]`, so expression
|
|
89
|
+
indexes written against the literal form still match.
|
|
90
|
+
|
|
91
|
+
### Casting
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
User.arel_table[:id].cast_as('text') # (users.id)::text
|
|
95
|
+
User.arel_table[:created_at].cast_as('date') # (users.created_at)::date
|
|
96
|
+
data.dig('age').cast_as('int') # (data #> array['age'])::int
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A type name can't be quoted or bound, so `cast_as` accepts only something that
|
|
100
|
+
looks like one; optionally schema qualified, with a modifier. For example:
|
|
101
|
+
`text`, `varchar(255)`, `numeric(10,2)`, `timestamp(6) with time zone`,
|
|
102
|
+
`int[]`, `public.geometry`. An `ArgumentError` will be raised otherwise.
|
|
103
|
+
|
|
74
104
|
### Full-text search
|
|
75
105
|
|
|
76
106
|
```ruby
|
|
@@ -39,24 +39,24 @@ module Arel
|
|
|
39
39
|
collector
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
visit(o.relation, collector)
|
|
54
|
-
collector << "\#>'{" << o.name.to_s
|
|
55
|
-
collector << (last_key ? "}'" : ",")
|
|
42
|
+
# Path segments are emitted as a quoted `array[...]` rather than
|
|
43
|
+
# interpolated into a `'{...}'` array literal, so a segment can never
|
|
44
|
+
# break out of the path and inject SQL (GHSA-75hc-9q9v-9cv2). PostgreSQL
|
|
45
|
+
# const-folds the array back to `'{a,b}'::text[]`, so expression indexes
|
|
46
|
+
# on the literal form still match.
|
|
47
|
+
def visit_Arel_Attributes_Key(o, collector)
|
|
48
|
+
keys = []
|
|
49
|
+
node = o
|
|
50
|
+
while node.is_a?(Arel::Attributes::Key)
|
|
51
|
+
keys.unshift(quote(node.name.to_s))
|
|
52
|
+
node = node.relation
|
|
56
53
|
end
|
|
54
|
+
|
|
55
|
+
visit(node, collector)
|
|
56
|
+
collector << " #> array[" << keys.join(',') << "]"
|
|
57
57
|
collector
|
|
58
58
|
end
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
def visit_Arel_Nodes_HasKey(o, collector)
|
|
61
61
|
right = o.right
|
|
62
62
|
|
|
@@ -84,10 +84,24 @@ module Arel
|
|
|
84
84
|
collector
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
# A type name can't be bound or quoted, so only allow something that
|
|
88
|
+
# actually looks like one -- optionally schema qualified, with a modifier
|
|
89
|
+
# and/or array suffix. Keeps user input from reaching the SQL as-is.
|
|
90
|
+
CAST_TYPE = /\A
|
|
91
|
+
[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)? # type, optionally schema qualified
|
|
92
|
+
(\(\d+(\s*,\s*\d+)?\))? # "varchar(255)", "numeric(10,2)"
|
|
93
|
+
(\ [a-z]+)* # "timestamp with time zone"
|
|
94
|
+
(\(\d+(\s*,\s*\d+)?\))? # "character varying(255)"
|
|
95
|
+
(\[\])* # "int[]"
|
|
96
|
+
\z/xi
|
|
97
|
+
|
|
87
98
|
def visit_Arel_Attributes_Cast(o, collector)
|
|
99
|
+
type = o.name.to_s
|
|
100
|
+
raise ArgumentError, "invalid cast type: #{type.inspect}" unless CAST_TYPE.match?(type)
|
|
101
|
+
|
|
88
102
|
collector << "("
|
|
89
103
|
visit(o.relation, collector)
|
|
90
|
-
collector << ")::#{
|
|
104
|
+
collector << ")::#{type}"
|
|
91
105
|
collector
|
|
92
106
|
end
|
|
93
107
|
|