searchcraft 0.4.2 → 0.5.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/lib/ext/scenic/adapters/postgres.rb +27 -0
- data/lib/searchcraft/builder.rb +26 -5
- data/lib/searchcraft/model.rb +49 -2
- data/lib/searchcraft/version.rb +1 -1
- data/lib/tasks/refresh.rake +1 -1
- data/sig/ext/misc.rbs +11 -0
- data/sig/searchcraft/builder.rbs +4 -1
- data/sig/searchcraft/model.rbs +4 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dcdc1a8d1072f3a64089e24aa0c6682876a00e0f0e0300a3b1552f299a5d4208
|
4
|
+
data.tar.gz: a3ced335b1a4042c43936d39af7efc678b3b0f4bfb6e8959260c1ed11da3be66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 387710b8e2980b3f84539feef53ceec31dac0180f28a3bf12f77cbd3e9932d5d1bd971b4994e0f70719684922e011c2d7cce67b38f59667e02a14a778edb3713
|
7
|
+
data.tar.gz: 484faca6fe838694569d563a3df6f5849cf555db4e17191ca3e3d0647dbd9fa09a05cb6fff0231f9dd40a2128c23b52a96ea0c2aef0bddf08e003d55da110d3f
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Features of Scenic not available in v1.7.0
|
2
|
+
class Scenic::Adapters::Postgres
|
3
|
+
# True if supplied relation name is populated. Useful for checking the
|
4
|
+
# state of materialized views which may error if created `WITH NO DATA`
|
5
|
+
# and used before they are refreshed. True for all other relation types.
|
6
|
+
#
|
7
|
+
# @param name The name of the relation
|
8
|
+
#
|
9
|
+
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
|
10
|
+
# in use does not support materialized views.
|
11
|
+
#
|
12
|
+
# @return [boolean]
|
13
|
+
def populated?(name)
|
14
|
+
raise_unless_materialized_views_supported
|
15
|
+
|
16
|
+
schemaless_name = name.split(".").last
|
17
|
+
|
18
|
+
sql = "SELECT relispopulated FROM pg_class WHERE relname = '#{schemaless_name}'"
|
19
|
+
relations = execute(sql)
|
20
|
+
|
21
|
+
if relations.count.positive?
|
22
|
+
relations.first["relispopulated"].in?(["t", true])
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/searchcraft/builder.rb
CHANGED
@@ -24,8 +24,20 @@ class SearchCraft::Builder
|
|
24
24
|
end
|
25
25
|
|
26
26
|
class << self
|
27
|
+
def with_no_data
|
28
|
+
@with_no_data = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_data
|
32
|
+
@with_no_data = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_no_data?
|
36
|
+
@with_no_data
|
37
|
+
end
|
38
|
+
|
27
39
|
# Iterate through subclasses, and invoke recreate_view_if_changed!
|
28
|
-
def rebuild_any_if_changed!
|
40
|
+
def rebuild_any_if_changed!(skip_dump_schema: false)
|
29
41
|
SearchCraft::ViewHashStore.setup_table_if_needed!
|
30
42
|
|
31
43
|
sorted_builders = sort_builders_by_dependency
|
@@ -39,7 +51,10 @@ class SearchCraft::Builder
|
|
39
51
|
|
40
52
|
builders_changed = []
|
41
53
|
sorted_builders.each do |builder|
|
42
|
-
changed = builder.new.recreate_view_if_changed!(
|
54
|
+
changed = builder.new.recreate_view_if_changed!(
|
55
|
+
builders_changed: builders_changed,
|
56
|
+
skip_dump_schema: skip_dump_schema
|
57
|
+
)
|
43
58
|
builders_changed << builder if changed
|
44
59
|
end
|
45
60
|
|
@@ -98,7 +113,9 @@ class SearchCraft::Builder
|
|
98
113
|
def view_sql
|
99
114
|
# remove trailing ; from view_sql
|
100
115
|
inner_sql = view_select_sql.gsub(/;\s*$/, "")
|
101
|
-
|
116
|
+
|
117
|
+
with_data = self.class.with_no_data? ? "WITH NO DATA" : "WITH DATA"
|
118
|
+
"CREATE MATERIALIZED VIEW #{view_name} AS (#{inner_sql}) #{with_data};"
|
102
119
|
end
|
103
120
|
|
104
121
|
# After materialized view created, do you need indexes on its columns?
|
@@ -114,7 +131,7 @@ class SearchCraft::Builder
|
|
114
131
|
|
115
132
|
# If missing or changed, drop and create view
|
116
133
|
# Returns false if no change required
|
117
|
-
def recreate_view_if_changed!(builders_changed: [])
|
134
|
+
def recreate_view_if_changed!(builders_changed: [], skip_dump_schema: false)
|
118
135
|
if SearchCraft.debug?
|
119
136
|
warn "#{self.class.name}#recreate_view_if_changed!"
|
120
137
|
warn " builders_changed: #{builders_changed.map(&:name).join(", ")}" if builders_changed.any?
|
@@ -139,12 +156,13 @@ class SearchCraft::Builder
|
|
139
156
|
drop_view!
|
140
157
|
create_view!
|
141
158
|
update_hash_store!
|
142
|
-
dump_schema!
|
159
|
+
dump_schema! unless skip_dump_schema
|
143
160
|
|
144
161
|
true
|
145
162
|
end
|
146
163
|
|
147
164
|
def create_view!
|
165
|
+
warn "Creating view/sequence/indexes for #{view_name}..." if SearchCraft.debug?
|
148
166
|
create_sequence!
|
149
167
|
sql_execute(view_sql)
|
150
168
|
create_indexes!
|
@@ -152,13 +170,16 @@ class SearchCraft::Builder
|
|
152
170
|
|
153
171
|
# Finds and drops all indexes and sequences on view, and then drops view
|
154
172
|
def drop_view!
|
173
|
+
puts "Dropping view/sequence for #{view_name}..." if SearchCraft.debug?
|
155
174
|
sql_execute("DROP MATERIALIZED VIEW IF EXISTS #{view_name} CASCADE;")
|
156
175
|
|
157
176
|
sql_execute("DROP SEQUENCE IF EXISTS #{view_id_sequence_name};")
|
158
177
|
|
178
|
+
warn "Updating ViewHashStore for #{self.class.name}" if SearchCraft.debug?
|
159
179
|
SearchCraft::ViewHashStore.reset!(builder: self)
|
160
180
|
end
|
161
181
|
|
182
|
+
# TODO: what if indexes didn't change?
|
162
183
|
def recreate_indexes!
|
163
184
|
drop_indexes!
|
164
185
|
create_indexes!
|
data/lib/searchcraft/model.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "scenic"
|
2
|
+
require "ext/scenic/adapters/postgres"
|
2
3
|
|
3
4
|
module SearchCraft::Model
|
4
5
|
# Maintain a list of classes that include this module
|
@@ -21,13 +22,18 @@ module SearchCraft::Model
|
|
21
22
|
# Runs .refresh! on all classes that include SearchCraft::Model
|
22
23
|
def self.refresh_all!
|
23
24
|
included_classes.each do |klass|
|
24
|
-
warn "Refreshing materialized view #{klass.table_name}..." unless Rails.env.test?
|
25
25
|
if klass.is_a?(ClassMethods)
|
26
26
|
klass.refresh!
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def self.refresh_any_unpopulated!
|
32
|
+
included_classes.each do |klass|
|
33
|
+
klass.refresh! unless klass.populated?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
31
37
|
def self.included_classes
|
32
38
|
@included_classes | if SearchCraft.config.explicit_model_class_names
|
33
39
|
SearchCraft.config.explicit_model_class_names.map(&:constantize)
|
@@ -38,12 +44,53 @@ module SearchCraft::Model
|
|
38
44
|
|
39
45
|
module ClassMethods
|
40
46
|
def refresh!
|
41
|
-
|
47
|
+
refresh_concurrently = @refresh_concurrently && populated?
|
48
|
+
unless Rails.env.test?
|
49
|
+
if refresh_concurrently
|
50
|
+
warn "Refreshing materialized view concurrently #{table_name}..."
|
51
|
+
else
|
52
|
+
warn "Refreshing materialized view #{table_name}..."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Scenic.database.refresh_materialized_view(table_name, concurrently: refresh_concurrently, cascade: false)
|
57
|
+
rescue ActiveRecord::StatementInvalid
|
58
|
+
# If populated? lies and returns true; then might get error:
|
59
|
+
# PG::FeatureNotSupported: ERROR: CONCURRENTLY cannot be used when the materialized view is not populated (ActiveRecord::StatementInvalid)
|
60
|
+
Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false)
|
61
|
+
end
|
62
|
+
|
63
|
+
def populated?
|
64
|
+
Scenic.database.populated?(table_name)
|
42
65
|
end
|
43
66
|
|
44
67
|
def refresh_concurrently=(value)
|
45
68
|
@refresh_concurrently = value
|
46
69
|
end
|
70
|
+
|
71
|
+
# Checks the database server to see if the materialized view is currently being refreshed
|
72
|
+
def currently_refreshing?
|
73
|
+
# quoted_table_name is table_name, but with double quotes around each chunk
|
74
|
+
# e.g. "schema"."table" or "table"
|
75
|
+
quoted_table_name = Scenic.database.quote_table_name(table_name)
|
76
|
+
dbname = ActiveRecord::Base.connection_db_config.database
|
77
|
+
sql = <<~SQL
|
78
|
+
SELECT EXISTS (
|
79
|
+
SELECT 1
|
80
|
+
FROM pg_stat_activity
|
81
|
+
WHERE datname = '#{dbname}'
|
82
|
+
AND query LIKE '%REFRESH MATERIALIZED VIEW #{quoted_table_name}%'
|
83
|
+
AND pid <> pg_backend_pid()
|
84
|
+
) AS is_refresh_running;
|
85
|
+
SQL
|
86
|
+
|
87
|
+
warn "Checking if #{table_name} is currently being refreshed..." if SearchCraft.debug?
|
88
|
+
if (result = ActiveRecord::Base.connection.execute(sql))
|
89
|
+
result.first["is_refresh_running"]
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
47
94
|
end
|
48
95
|
|
49
96
|
def read_only?
|
data/lib/searchcraft/version.rb
CHANGED
data/lib/tasks/refresh.rake
CHANGED
@@ -5,7 +5,7 @@ namespace :searchcraft do
|
|
5
5
|
require "benchmark"
|
6
6
|
SearchCraft.config.explicit_model_class_names.each do |model_class_name|
|
7
7
|
klass = model_class_name.constantize
|
8
|
-
|
8
|
+
warn "Refreshing materialized views for #{klass.name}"
|
9
9
|
puts Benchmark.measure { klass.refresh! }
|
10
10
|
end
|
11
11
|
end
|
data/sig/ext/misc.rbs
CHANGED
@@ -10,6 +10,11 @@ module ActiveRecord
|
|
10
10
|
|
11
11
|
class Base
|
12
12
|
def self.configurations: () -> ActiveRecord::DatabaseConfigurations
|
13
|
+
def self.connection_db_config: () -> ActiveRecord::DatabaseConfig
|
14
|
+
end
|
15
|
+
|
16
|
+
class DatabaseConfig
|
17
|
+
def database: () -> String
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
@@ -26,6 +31,12 @@ module Scenic
|
|
26
31
|
module Adapters
|
27
32
|
class Postgres
|
28
33
|
def refresh_materialized_view: (String, ?concurrently: bool, ?cascade: bool) -> nil
|
34
|
+
def populated?: (String) -> bool
|
35
|
+
def quote_table_name: (String) -> String
|
36
|
+
|
37
|
+
# The following are due to temporary ext/scenic/adapters/postgres.rb
|
38
|
+
def raise_unless_materialized_views_supported: () -> nil
|
39
|
+
def execute: (String) -> ActiveRecord::Result
|
29
40
|
end
|
30
41
|
end
|
31
42
|
|
data/sig/searchcraft/builder.rbs
CHANGED
@@ -12,11 +12,14 @@ module SearchCraft
|
|
12
12
|
def self.find_subclasses_via_rails_eager_load_paths: (?known_subclass_names: Array[String]) -> Array[String]
|
13
13
|
|
14
14
|
def self.rebuild_all!: () -> void
|
15
|
-
|
16
15
|
def self.rebuild_any_if_changed!: () -> void
|
17
16
|
|
18
17
|
def self.recreate_indexes!: () -> void
|
19
18
|
|
19
|
+
def self.with_no_data: () -> void
|
20
|
+
def self.with_data: () -> void
|
21
|
+
def self.with_no_data?: () -> bool
|
22
|
+
|
20
23
|
def create_view!: () -> void
|
21
24
|
|
22
25
|
def dependencies_ready?: () -> bool
|
data/sig/searchcraft/model.rbs
CHANGED
@@ -5,6 +5,8 @@ module SearchCraft
|
|
5
5
|
|
6
6
|
def refresh_all!: () -> void
|
7
7
|
|
8
|
+
def currently_refreshing?: () -> bool
|
9
|
+
|
8
10
|
def table_name: () -> String
|
9
11
|
|
10
12
|
def table_name=: (String) -> void
|
@@ -12,6 +14,8 @@ module SearchCraft
|
|
12
14
|
def name: () -> String
|
13
15
|
|
14
16
|
def refresh!: () -> void
|
17
|
+
|
18
|
+
def populated?: () -> bool
|
15
19
|
end
|
16
20
|
|
17
21
|
extend ClassMethods
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: searchcraft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dr Nic Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -168,6 +168,7 @@ files:
|
|
168
168
|
- CODE_OF_CONDUCT.md
|
169
169
|
- LICENSE.txt
|
170
170
|
- README.md
|
171
|
+
- lib/ext/scenic/adapters/postgres.rb
|
171
172
|
- lib/search_craft.rb
|
172
173
|
- lib/searchcraft.rb
|
173
174
|
- lib/searchcraft/annotate.rb
|
@@ -217,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
217
218
|
- !ruby/object:Gem::Version
|
218
219
|
version: '0'
|
219
220
|
requirements: []
|
220
|
-
rubygems_version: 3.
|
221
|
+
rubygems_version: 3.5.5
|
221
222
|
signing_key:
|
222
223
|
specification_version: 4
|
223
224
|
summary: Instant search for Rails and ActiveRecord
|