searchcraft 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ext/scenic/adapters/postgres.rb +27 -0
- data/lib/searchcraft/builder.rb +18 -1
- data/lib/searchcraft/model.rb +39 -1
- 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: baee24eaa1a2af160c50f79554ac35f3944380a161d0d37d78c3aa3c06a2f9c9
|
4
|
+
data.tar.gz: 5fc8e38ccb127a419fe5b75b8bc2332dba4d1b5ae1c951d0148ad85b72b180cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c54aa0216ed4de138df713cdf8ecd9d717591823ac9125f5b1fed43742bfbeae7e6b640ef97fca9854cfc1a45dee79fef4ffca270399e4cc235950e26a71096
|
7
|
+
data.tar.gz: 19a9f0f01a8676de06524e0f4c87e10fe509bf52c7766dec07f027645fbb85611d1315006664aee5a9d85d27b8fe348d7f04cb79f30b54dfce156182b49789d8
|
@@ -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,6 +24,18 @@ 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
40
|
def rebuild_any_if_changed!
|
29
41
|
SearchCraft::ViewHashStore.setup_table_if_needed!
|
@@ -98,7 +110,9 @@ class SearchCraft::Builder
|
|
98
110
|
def view_sql
|
99
111
|
# remove trailing ; from view_sql
|
100
112
|
inner_sql = view_select_sql.gsub(/;\s*$/, "")
|
101
|
-
|
113
|
+
|
114
|
+
with_data = self.class.with_no_data? ? "WITH NO DATA" : "WITH DATA"
|
115
|
+
"CREATE MATERIALIZED VIEW #{view_name} AS (#{inner_sql}) #{with_data};"
|
102
116
|
end
|
103
117
|
|
104
118
|
# After materialized view created, do you need indexes on its columns?
|
@@ -145,6 +159,7 @@ class SearchCraft::Builder
|
|
145
159
|
end
|
146
160
|
|
147
161
|
def create_view!
|
162
|
+
warn "Creating view/sequence/indexes for #{view_name}..." if SearchCraft.debug?
|
148
163
|
create_sequence!
|
149
164
|
sql_execute(view_sql)
|
150
165
|
create_indexes!
|
@@ -152,10 +167,12 @@ class SearchCraft::Builder
|
|
152
167
|
|
153
168
|
# Finds and drops all indexes and sequences on view, and then drops view
|
154
169
|
def drop_view!
|
170
|
+
puts "Dropping view/sequence for #{view_name}..." if SearchCraft.debug?
|
155
171
|
sql_execute("DROP MATERIALIZED VIEW IF EXISTS #{view_name} CASCADE;")
|
156
172
|
|
157
173
|
sql_execute("DROP SEQUENCE IF EXISTS #{view_id_sequence_name};")
|
158
174
|
|
175
|
+
warn "Updating ViewHashStore for #{self.class.name}" if SearchCraft.debug?
|
159
176
|
SearchCraft::ViewHashStore.reset!(builder: self)
|
160
177
|
end
|
161
178
|
|
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
|
@@ -28,6 +29,12 @@ module SearchCraft::Model
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
def self.refresh_any_unpopulated!
|
33
|
+
included_classes.each do |klass|
|
34
|
+
klass.refresh! unless klass.populated?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
31
38
|
def self.included_classes
|
32
39
|
@included_classes | if SearchCraft.config.explicit_model_class_names
|
33
40
|
SearchCraft.config.explicit_model_class_names.map(&:constantize)
|
@@ -38,12 +45,43 @@ module SearchCraft::Model
|
|
38
45
|
|
39
46
|
module ClassMethods
|
40
47
|
def refresh!
|
41
|
-
|
48
|
+
refresh_concurrently = @refresh_concurrently && populated?
|
49
|
+
puts "Refreshing materialized view #{table_name}..." if SearchCraft.debug?
|
50
|
+
|
51
|
+
Scenic.database.refresh_materialized_view(table_name, concurrently: refresh_concurrently, cascade: false)
|
52
|
+
end
|
53
|
+
|
54
|
+
def populated?
|
55
|
+
Scenic.database.populated?(table_name)
|
42
56
|
end
|
43
57
|
|
44
58
|
def refresh_concurrently=(value)
|
45
59
|
@refresh_concurrently = value
|
46
60
|
end
|
61
|
+
|
62
|
+
# Checks the database server to see if the materialized view is currently being refreshed
|
63
|
+
def currently_refreshing?
|
64
|
+
# quoted_table_name is table_name, but with double quotes around each chunk
|
65
|
+
# e.g. "schema"."table" or "table"
|
66
|
+
quoted_table_name = Scenic.database.quote_table_name(table_name)
|
67
|
+
dbname = ActiveRecord::Base.connection_db_config.database
|
68
|
+
sql = <<~SQL
|
69
|
+
SELECT EXISTS (
|
70
|
+
SELECT 1
|
71
|
+
FROM pg_stat_activity
|
72
|
+
WHERE datname = '#{dbname}'
|
73
|
+
AND query LIKE '%REFRESH MATERIALIZED VIEW #{quoted_table_name}%'
|
74
|
+
AND pid <> pg_backend_pid()
|
75
|
+
) AS is_refresh_running;
|
76
|
+
SQL
|
77
|
+
|
78
|
+
warn "Checking if #{table_name} is currently being refreshed..." if SearchCraft.debug?
|
79
|
+
if (result = ActiveRecord::Base.connection.execute(sql))
|
80
|
+
result.first["is_refresh_running"]
|
81
|
+
else
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
47
85
|
end
|
48
86
|
|
49
87
|
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.0
|
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-02-29 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
|