occams-record 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|