searchcraft 0.4.2 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 913320513d07b3447532e62d060b3ebc118dce10531d2f1d62587bdccc5c8c33
4
- data.tar.gz: f8b7d111101ece94baeffd41d5d4f42350f47a9b7db0d96e589ee2297e4ae196
3
+ metadata.gz: baee24eaa1a2af160c50f79554ac35f3944380a161d0d37d78c3aa3c06a2f9c9
4
+ data.tar.gz: 5fc8e38ccb127a419fe5b75b8bc2332dba4d1b5ae1c951d0148ad85b72b180cb
5
5
  SHA512:
6
- metadata.gz: baab9337ba89647bd3cf7819570d0de0ae32a066c07d33587ac33c157f8bd3014c20516c6a3d5a2bc77ed37fb9e53fa73eabacef8fbf784b240cc5046da036a0
7
- data.tar.gz: 4daaa7dcd5d3312740a83eb8f08efb6f86bc20179a0081e092207f7788673e9bb6ecc0b88ca47beeb33278406f96d4775653b24ccd4255282e9fd8eba16ba3b1
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
@@ -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
- "CREATE MATERIALIZED VIEW #{view_name} AS (#{inner_sql}) WITH DATA;"
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
 
@@ -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
- Scenic.database.refresh_materialized_view(table_name, concurrently: @refresh_concurrently, cascade: false)
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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SearchCraft
4
- VERSION = "0.4.2"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -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
- puts "Refreshing materialized views for #{klass.name}"
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
 
@@ -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
@@ -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.2
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: 2023-11-06 00:00:00.000000000 Z
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.4.20
221
+ rubygems_version: 3.5.5
221
222
  signing_key:
222
223
  specification_version: 4
223
224
  summary: Instant search for Rails and ActiveRecord