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 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