occams-record 1.7.0 → 1.8.0
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/README.md +11 -99
- data/lib/occams-record/pluck.rb +45 -4
- data/lib/occams-record/query.rb +5 -1
- data/lib/occams-record/raw_query.rb +1 -1
- data/lib/occams-record/results/results.rb +27 -2
- data/lib/occams-record/results/row.rb +1 -1
- data/lib/occams-record/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 846af974c8bc267fe8e901995d0aa3341b018d135eeb267c11d9add447cd0155
|
4
|
+
data.tar.gz: 763f8a038908cb9a990bcf305644661736dd9133e51a9d8fb8ece2b8ff018fd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24e1c5dfb09cb35b2b7116cb40aafcb1eb215ac96e3c8bab7ff6558598b31e8abb77b49c2b711392567862b182e1aed705888e939f8b66f15285f2ca352dbc89
|
7
|
+
data.tar.gz: 04f78d14765b6ae4d9ccee5f01633132ed1e39662071dfc39f6e33583e9cd3e6214841225eb35ce2f678472ce34bf45e27e23660114f28cede6a27cdb38613b0
|
data/README.md
CHANGED
@@ -11,120 +11,33 @@ OccamsRecord is a high-efficiency, advanced query library for use alongside Acti
|
|
11
11
|
* 3x-5x faster than ActiveRecord queries, *minimum*.
|
12
12
|
* Uses 1/3 the memory of ActiveRecord query results.
|
13
13
|
* Eliminates the N+1 query problem. (This often exceeds the baseline 3x-5x gain.)
|
14
|
-
* Support for cursors (Postgres only, new in v1.4.0)
|
15
14
|
|
16
15
|
### 2) Supercharged querying & eager loading
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
.query(User.active)
|
25
|
-
.eager_load(:orders) { |l|
|
26
|
-
l.scope { |q| q.where("created_at >= ?", date).order("created_at DESC") }
|
27
|
-
}
|
28
|
-
```
|
29
|
-
|
30
|
-
**Use `ORDER BY` with `find_each`/`find_in_batches`**
|
31
|
-
|
32
|
-
```ruby
|
33
|
-
OccamsRecord
|
34
|
-
.query(Order.order("created_at DESC"))
|
35
|
-
.find_each { |order|
|
36
|
-
...
|
37
|
-
}
|
38
|
-
```
|
39
|
-
|
40
|
-
**Use cursors**
|
41
|
-
|
42
|
-
```ruby
|
43
|
-
OccamsRecord
|
44
|
-
.query(Order.order("created_at DESC"))
|
45
|
-
.find_each_with_cursor { |order|
|
46
|
-
...
|
47
|
-
}
|
48
|
-
```
|
49
|
-
|
50
|
-
**Use `find_each`/`find_in_batches` with raw SQL**
|
51
|
-
|
52
|
-
```ruby
|
53
|
-
OccamsRecord
|
54
|
-
.sql("
|
55
|
-
SELECT * FROM orders
|
56
|
-
WHERE created_at >= %{date}
|
57
|
-
LIMIT %{batch_limit}
|
58
|
-
OFFSET %{batch_offset}",
|
59
|
-
{date: 10.years.ago}
|
60
|
-
)
|
61
|
-
.find_each { |order|
|
62
|
-
...
|
63
|
-
}
|
64
|
-
```
|
65
|
-
|
66
|
-
**Eager load associations when you're writing raw SQL**
|
67
|
-
|
68
|
-
```ruby
|
69
|
-
OccamsRecord
|
70
|
-
.sql("
|
71
|
-
SELECT * FROM users
|
72
|
-
LEFT OUTER JOIN ...
|
73
|
-
")
|
74
|
-
.model(User)
|
75
|
-
.eager_load(:orders)
|
76
|
-
```
|
77
|
-
|
78
|
-
**Use `pluck` with raw SQL**
|
79
|
-
|
80
|
-
```ruby
|
81
|
-
OccamsRecord
|
82
|
-
.sql("
|
83
|
-
SELECT some_col FROM users
|
84
|
-
INNER JOIN ...
|
85
|
-
")
|
86
|
-
.pluck(:some_col)
|
87
|
-
```
|
88
|
-
|
89
|
-
**Eager load "ad hoc associations" using raw SQL**
|
90
|
-
|
91
|
-
Relationships are complicated, and sometimes they can't be expressed in ActiveRecord models. Define your relationship on the fly!
|
92
|
-
(Don't worry, there's a full explanation later on.)
|
93
|
-
|
94
|
-
```ruby
|
95
|
-
OccamsRecord
|
96
|
-
.query(User.all)
|
97
|
-
.eager_load_many(:orders, {:id => :user_id}, "
|
98
|
-
SELECT user_id, orders.*
|
99
|
-
FROM orders INNER JOIN ...
|
100
|
-
WHERE user_id IN (%{ids})
|
101
|
-
")
|
102
|
-
```
|
17
|
+
* Customize the SQL used to eager load associations (order them, apply filters, etc)
|
18
|
+
* Use cursors (Postgres only)
|
19
|
+
* Use `ORDER BY` with `find_each`/`find_in_batches`
|
20
|
+
* Use `find_each`/`find_in_batches` with raw SQL
|
21
|
+
* Eager load associations off of raw SQL queries
|
22
|
+
* Use `pluck` with raw SQL queries
|
103
23
|
|
24
|
+
### How does OccamsRecord do all this?
|
104
25
|
[Look over the speed and memory measurements yourself!](https://github.com/jhollinger/occams-record/wiki/Measurements) OccamsRecord achieves all of this by making some **very specific trade-offs:**
|
105
26
|
|
106
27
|
* OccamsRecord results are *read-only*.
|
107
28
|
* OccamsRecord results are *purely database rows* - they don't have any instance methods from your Rails models.
|
108
|
-
* You *must eager load* each assocation you intend to use. If you
|
29
|
+
* You *must eager load* each assocation you intend to use. If you try to use one you didn't eager load, an exception will be raised.
|
109
30
|
|
110
|
-
|
31
|
+
# Overview
|
111
32
|
|
112
|
-
|
33
|
+
Full documentation is available at [rubydoc.info/gems/occams-record](http://www.rubydoc.info/gems/occams-record). Code lives at at [github.com/jhollinger/occams-record](https://github.com/jhollinger/occams-record). Contributions welcome!
|
113
34
|
|
114
|
-
Simply add
|
35
|
+
Simply add `occams-record` to your `Gemfile`:
|
115
36
|
|
116
37
|
```ruby
|
117
38
|
gem 'occams-record'
|
118
39
|
```
|
119
40
|
|
120
|
-
---
|
121
|
-
|
122
|
-
# Overview
|
123
|
-
|
124
|
-
Full documentation is available at [rubydoc.info/gems/occams-record](http://www.rubydoc.info/gems/occams-record).
|
125
|
-
|
126
|
-
Code lives at at [github.com/jhollinger/occams-record](https://github.com/jhollinger/occams-record). Contributions welcome!
|
127
|
-
|
128
41
|
Build your queries like normal, using ActiveRecord's excellent query builder. Then pass them off to Occams Record.
|
129
42
|
|
130
43
|
```ruby
|
@@ -371,7 +284,6 @@ The following ActiveRecord features are under consideration, but not high priori
|
|
371
284
|
The following ActiveRecord features are not supported, and likely never will be. Pull requests are still welcome, though.
|
372
285
|
|
373
286
|
* Eager loading `through` associations that involve a polymorphic association.
|
374
|
-
* ActiveRecord enum types
|
375
287
|
* ActiveRecord serialized types
|
376
288
|
|
377
289
|
---
|
data/lib/occams-record/pluck.rb
CHANGED
@@ -2,13 +2,54 @@ module OccamsRecord
|
|
2
2
|
module Pluck
|
3
3
|
private
|
4
4
|
|
5
|
-
def pluck_results(results, cols)
|
5
|
+
def pluck_results(results, cols, model: nil)
|
6
6
|
if cols.size == 1
|
7
|
-
|
7
|
+
pluck_results_single(results, cols[0].to_s, model: model)
|
8
|
+
else
|
9
|
+
pluck_results_multi(results, cols.map(&:to_s), model: model)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# returns an array of values
|
14
|
+
def pluck_results_single(results, col, model: nil)
|
15
|
+
enum = model&.defined_enums&.[](col)
|
16
|
+
inv_enum = enum&.invert
|
17
|
+
if enum
|
18
|
+
results.map { |r|
|
19
|
+
val = r[col]
|
20
|
+
enum.has_key?(val) ? val : inv_enum[val]
|
21
|
+
}
|
22
|
+
else
|
23
|
+
# micro-optimization for when there are no enums
|
8
24
|
results.map { |r| r[col] }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns an array of arrays
|
29
|
+
def pluck_results_multi(results, cols, model: nil)
|
30
|
+
any_enums = false
|
31
|
+
cols_with_enums = cols.map { |col|
|
32
|
+
enum = model&.defined_enums&.[](col)
|
33
|
+
any_enums ||= !!enum
|
34
|
+
[col, enum, enum&.invert]
|
35
|
+
}
|
36
|
+
|
37
|
+
if any_enums
|
38
|
+
results.map { |row|
|
39
|
+
cols_with_enums.map { |(col, enum, inv_enum)|
|
40
|
+
if enum
|
41
|
+
val = row[col]
|
42
|
+
enum.has_key?(val) ? val : inv_enum[val]
|
43
|
+
else
|
44
|
+
row[col]
|
45
|
+
end
|
46
|
+
}
|
47
|
+
}
|
9
48
|
else
|
10
|
-
|
11
|
-
results.map { |
|
49
|
+
# micro-optimization for when there are no enums
|
50
|
+
results.map { |row|
|
51
|
+
cols.map { |col| row[col] }
|
52
|
+
}
|
12
53
|
end
|
13
54
|
end
|
14
55
|
end
|
data/lib/occams-record/query.rb
CHANGED
@@ -96,6 +96,8 @@ module OccamsRecord
|
|
96
96
|
#
|
97
97
|
def run
|
98
98
|
sql = block_given? ? yield(scope).to_sql : scope.to_sql
|
99
|
+
return [] if sql.blank? # return early in case ActiveRecord::QueryMethods#none was used
|
100
|
+
|
99
101
|
@query_logger << "#{@eager_loaders.tracer}: #{sql}" if @query_logger
|
100
102
|
result = if measure?
|
101
103
|
record_start_time!
|
@@ -237,6 +239,8 @@ module OccamsRecord
|
|
237
239
|
#
|
238
240
|
def pluck(*cols)
|
239
241
|
sql = (block_given? ? yield(scope).to_sql : scope).select(*cols).to_sql
|
242
|
+
return [] if sql.blank? # return early in case ActiveRecord::QueryMethods#none was used
|
243
|
+
|
240
244
|
@query_logger << "#{@eager_loaders.tracer}: #{sql}" if @query_logger
|
241
245
|
result = if measure?
|
242
246
|
record_start_time!
|
@@ -246,7 +250,7 @@ module OccamsRecord
|
|
246
250
|
else
|
247
251
|
model.connection.exec_query sql
|
248
252
|
end
|
249
|
-
pluck_results result, cols
|
253
|
+
pluck_results result, cols, model: @model
|
250
254
|
end
|
251
255
|
end
|
252
256
|
end
|
@@ -54,16 +54,41 @@ module OccamsRecord
|
|
54
54
|
# * MySQL ?
|
55
55
|
#
|
56
56
|
type = column_types[col] || model_column_types&.[](col)
|
57
|
+
|
58
|
+
#
|
59
|
+
# NOTE is also some variation in when enum values are mapped in different AR versions.
|
60
|
+
# In >=5.0, <=7.0, ActiveRecord::Result objects contain the human-readable values. In 4.2 and
|
61
|
+
# pre-release versions of 7.1, they instead have the RAW values (e.g. integers) which we must map ourselves.
|
62
|
+
#
|
63
|
+
enum = model&.defined_enums&.[](col)
|
64
|
+
inv_enum = enum&.invert
|
65
|
+
|
57
66
|
case type&.type
|
58
67
|
when nil
|
59
|
-
|
68
|
+
if enum
|
69
|
+
define_method(col) {
|
70
|
+
val = @raw_values[idx]
|
71
|
+
enum.has_key?(val) ? val : inv_enum[val]
|
72
|
+
}
|
73
|
+
else
|
74
|
+
define_method(col) { @raw_values[idx] }
|
75
|
+
end
|
60
76
|
when :datetime
|
61
77
|
define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx])&.in_time_zone }
|
62
78
|
when :boolean
|
63
79
|
define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
|
64
80
|
define_method("#{col}?") { !!send(col) }
|
65
81
|
else
|
66
|
-
|
82
|
+
if enum
|
83
|
+
define_method(col) {
|
84
|
+
@cast_values[idx] ||= (
|
85
|
+
val = type.send(CASTER, @raw_values[idx])
|
86
|
+
enum.has_key?(val) ? val : inv_enum[val]
|
87
|
+
)
|
88
|
+
}
|
89
|
+
else
|
90
|
+
define_method(col) { @cast_values[idx] ||= type.send(CASTER, @raw_values[idx]) }
|
91
|
+
end
|
67
92
|
end
|
68
93
|
end
|
69
94
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: occams-record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Hollinger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|