mat_views 0.1.2 → 0.2.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 +4 -4
- data/README.md +6 -6
- data/app/jobs/mat_views/application_job.rb +105 -0
- data/app/jobs/mat_views/create_view_job.rb +16 -118
- data/app/jobs/mat_views/delete_view_job.rb +18 -125
- data/app/jobs/mat_views/refresh_view_job.rb +7 -127
- data/app/models/mat_views/mat_view_run.rb +23 -3
- data/lib/ext/exception.rb +20 -0
- data/lib/generators/mat_views/install/templates/create_mat_view_runs.rb +2 -2
- data/lib/mat_views/jobs/adapter.rb +8 -5
- data/lib/mat_views/service_response.rb +30 -15
- data/lib/mat_views/services/base_service.rb +179 -24
- data/lib/mat_views/services/concurrent_refresh.rb +35 -121
- data/lib/mat_views/services/create_view.rb +64 -47
- data/lib/mat_views/services/delete_view.rb +41 -87
- data/lib/mat_views/services/regular_refresh.rb +35 -92
- data/lib/mat_views/services/swap_refresh.rb +75 -117
- data/lib/mat_views/version.rb +1 -1
- data/lib/mat_views.rb +3 -2
- data/lib/tasks/helpers.rb +19 -19
- data/lib/tasks/mat_views_tasks.rake +35 -29
- metadata +3 -2
@@ -14,20 +14,27 @@ module MatViews
|
|
14
14
|
# executes `CREATE MATERIALIZED VIEW ... WITH DATA`, and, when the
|
15
15
|
# refresh strategy is `:concurrent`, ensures a supporting UNIQUE index.
|
16
16
|
#
|
17
|
-
#
|
17
|
+
# Options:
|
18
|
+
# - `force:` (Boolean, default: false) → drop and recreate if the view already exists
|
19
|
+
# - `row_count_strategy:` (Symbol, default: :none) → one of `:estimated`, `:exact`, or `:none or nil` to control row count reporting
|
20
|
+
#
|
21
|
+
# Returns a {MatViews::ServiceResponse}
|
18
22
|
#
|
19
23
|
# @see MatViews::Services::RegularRefresh
|
20
24
|
# @see MatViews::Services::ConcurrentRefresh
|
21
25
|
#
|
22
26
|
# @example Create a new matview (no force)
|
23
|
-
# svc = MatViews::Services::CreateView.new(defn)
|
27
|
+
# svc = MatViews::Services::CreateView.new(defn, **options)
|
24
28
|
# response = svc.run
|
25
|
-
# response.status # => :created or :
|
29
|
+
# response.status # => :created or :skipped
|
26
30
|
#
|
27
31
|
# @example Force recreate an existing matview
|
28
32
|
# svc = MatViews::Services::CreateView.new(defn, force: true)
|
29
33
|
# svc.run
|
30
34
|
#
|
35
|
+
# @example via job, this is the typical usage and will create a run record in the DB
|
36
|
+
# MatViews::Jobs::Adapter.enqueue(MatViews::Services::CreateViewJob, definition.id, **options)
|
37
|
+
#
|
31
38
|
class CreateView < BaseService
|
32
39
|
##
|
33
40
|
# Whether to force recreation (drop+create if exists).
|
@@ -38,53 +45,53 @@ module MatViews
|
|
38
45
|
##
|
39
46
|
# @param definition [MatViews::MatViewDefinition]
|
40
47
|
# @param force [Boolean] Whether to drop+recreate an existing matview.
|
41
|
-
|
42
|
-
|
48
|
+
# @param row_count_strategy [Symbol, nil] one of `:estimated`, `:exact`, or `nil` (default: `:estimated`)
|
49
|
+
#
|
50
|
+
# Supports optional row count strategies:
|
51
|
+
# - `:estimated` → approximate, using `pg_class.reltuples`
|
52
|
+
# - `:exact` → accurate, using `COUNT(*)`
|
53
|
+
# - `nil` → skip row count
|
54
|
+
def initialize(definition, force: false, row_count_strategy: :estimated)
|
55
|
+
super(definition, row_count_strategy: row_count_strategy)
|
43
56
|
@force = !!force
|
44
57
|
end
|
45
58
|
|
59
|
+
private
|
60
|
+
|
46
61
|
##
|
47
62
|
# Execute the create operation.
|
48
63
|
#
|
49
64
|
# - Validates name, SQL, and concurrent-index requirements.
|
50
|
-
# - Handles existing view:
|
65
|
+
# - Handles existing view: skipped (default) or drop+recreate (`force: true`).
|
51
66
|
# - Creates the materialized view WITH DATA.
|
52
67
|
# - Creates a UNIQUE index if refresh strategy is concurrent.
|
53
68
|
#
|
54
|
-
# @
|
55
|
-
# - `:created` on success (payload includes `view` and `created_indexes`)
|
56
|
-
# - `:noop` if the view already exists and `force: false`
|
57
|
-
# - `:error` if validation or execution fails
|
69
|
+
# @api private
|
58
70
|
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
71
|
+
# @return [MatViews::ServiceResponse]
|
72
|
+
# - `status: :created or :skipped` on success, with `response` containing:
|
73
|
+
# - `view` - the qualified view name
|
74
|
+
# - `row_count_before` - if requested and available
|
75
|
+
# - `row_count_after` - if requested and available
|
76
|
+
# - `status: :error` with `error` on failure, with `error` containing:
|
77
|
+
# - serlialized exception class, message, and backtrace in a hash
|
78
|
+
def _run
|
79
|
+
sql = create_with_data_sql
|
80
|
+
self.response = { view: "#{schema}.#{rel}", sql: [sql] }
|
81
|
+
# If exists, either skipped or drop+recreate
|
82
|
+
existed = handle_existing
|
65
83
|
return existed if existed.is_a?(MatViews::ServiceResponse)
|
66
84
|
|
67
|
-
|
68
|
-
|
85
|
+
response[:row_count_before] = UNKNOWN_ROW_COUNT
|
86
|
+
conn.execute(sql)
|
87
|
+
response[:row_count_after] = fetch_rows_count
|
69
88
|
|
70
89
|
# For concurrent strategy, ensure the unique index so future
|
71
90
|
# REFRESH MATERIALIZED VIEW CONCURRENTLY is allowed.
|
72
|
-
|
73
|
-
|
74
|
-
ok(:created, payload: { view: qualified_rel, **index_info })
|
75
|
-
rescue StandardError => e
|
76
|
-
error_response(
|
77
|
-
e,
|
78
|
-
payload: { view: qualified_rel },
|
79
|
-
meta: { sql: sql, force: force }
|
80
|
-
)
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
91
|
+
response.merge!(ensure_unique_index_if_needed)
|
84
92
|
|
85
|
-
|
86
|
-
|
87
|
-
# ────────────────────────────────────────────────────────────────
|
93
|
+
ok(:created)
|
94
|
+
end
|
88
95
|
|
89
96
|
##
|
90
97
|
# Validate name, SQL, and concurrent strategy requirements.
|
@@ -92,37 +99,47 @@ module MatViews
|
|
92
99
|
# @api private
|
93
100
|
# @return [MatViews::ServiceResponse, nil] error response or nil if OK
|
94
101
|
#
|
95
|
-
def prepare
|
96
|
-
|
97
|
-
|
98
|
-
|
102
|
+
def prepare
|
103
|
+
raise_err("Invalid view name format: #{definition.name.inspect}") unless valid_name?
|
104
|
+
raise_err('SQL must start with SELECT') unless valid_sql?
|
105
|
+
raise_err('refresh_strategy=concurrent requires unique_index_columns (non-empty)') if strategy == 'concurrent' && cols.empty?
|
99
106
|
|
100
107
|
nil
|
101
108
|
end
|
102
109
|
|
103
110
|
##
|
104
|
-
#
|
111
|
+
# Assign the request parameters.
|
112
|
+
# Called by {#run} before {#prepare}.
|
113
|
+
#
|
114
|
+
# @api private
|
115
|
+
# @return [void]
|
116
|
+
#
|
117
|
+
def assign_request
|
118
|
+
self.request = { row_count_strategy: row_count_strategy, force: }
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Handle existing matview: return skipped if not forcing, or drop if forcing.
|
105
123
|
#
|
106
124
|
# @api private
|
107
125
|
# @return [MatViews::ServiceResponse, nil]
|
108
126
|
#
|
109
|
-
def handle_existing
|
127
|
+
def handle_existing
|
110
128
|
return nil unless view_exists?
|
111
129
|
|
112
|
-
return
|
130
|
+
return ok(:skipped) unless force
|
113
131
|
|
114
132
|
drop_view
|
115
133
|
nil
|
116
134
|
end
|
117
135
|
|
118
136
|
##
|
119
|
-
#
|
120
|
-
#
|
137
|
+
# SQL for `CREATE MATERIALIZED VIEW ... WITH DATA`.
|
121
138
|
# @api private
|
122
|
-
# @return [
|
139
|
+
# @return [String]
|
123
140
|
#
|
124
|
-
def
|
125
|
-
|
141
|
+
def create_with_data_sql
|
142
|
+
<<~SQL
|
126
143
|
CREATE MATERIALIZED VIEW #{qualified_rel} AS
|
127
144
|
#{sql}
|
128
145
|
WITH DATA
|
@@ -147,9 +164,9 @@ module MatViews
|
|
147
164
|
concurrently = pg_idle?
|
148
165
|
conn.execute(<<~SQL)
|
149
166
|
CREATE UNIQUE INDEX #{'CONCURRENTLY ' if concurrently}#{quote_table_name(idx_name)}
|
150
|
-
ON #{qualified_rel} (#{cols.map { |
|
167
|
+
ON #{qualified_rel} (#{cols.map { |col| quote_column_name(col) }.join(', ')})
|
151
168
|
SQL
|
152
|
-
{ created_indexes: [idx_name] }
|
169
|
+
{ created_indexes: [idx_name], row_count_before: UNKNOWN_ROW_COUNT, row_count_after: fetch_rows_count }
|
153
170
|
end
|
154
171
|
end
|
155
172
|
end
|
@@ -11,24 +11,24 @@ module MatViews
|
|
11
11
|
# Service that safely drops a PostgreSQL materialized view.
|
12
12
|
#
|
13
13
|
# Options:
|
14
|
-
# - `
|
15
|
-
# - `
|
14
|
+
# - `cascade:` (Boolean, default: false) → drop with CASCADE instead of RESTRICT
|
15
|
+
# - `row_count_strategy:` (Symbol, default: :none) → one of `:estimated`, `:exact`, or `:none or nil` to control row count reporting
|
16
16
|
#
|
17
|
-
# Returns a {MatViews::ServiceResponse}
|
18
|
-
# - `ok(:deleted, ...)` when dropped successfully
|
19
|
-
# - `ok(:skipped, ...)` when absent and `if_exists: true`
|
20
|
-
# - `err("...")` or `error_response(...)` on validation or execution error
|
17
|
+
# Returns a {MatViews::ServiceResponse}
|
21
18
|
#
|
22
19
|
# @see MatViews::DeleteViewJob
|
23
20
|
# @see MatViews::MatViewRun
|
24
21
|
#
|
25
22
|
# @example Drop a view if it exists
|
26
|
-
# svc = MatViews::Services::DeleteView.new(defn)
|
23
|
+
# svc = MatViews::Services::DeleteView.new(defn, **options)
|
27
24
|
# svc.run
|
28
25
|
#
|
29
26
|
# @example Force drop with CASCADE
|
30
27
|
# MatViews::Services::DeleteView.new(defn, cascade: true).run
|
31
28
|
#
|
29
|
+
# @example via job, this is the typical usage and will create a run record in the DB
|
30
|
+
# MatViews::Jobs::Adapter.enqueue(MatViews::Services::DeleteViewJob, definition.id, **options)
|
31
|
+
#
|
32
32
|
class DeleteView < BaseService
|
33
33
|
##
|
34
34
|
# Whether to cascade the drop (default: false).
|
@@ -36,98 +36,55 @@ module MatViews
|
|
36
36
|
# @return [Boolean]
|
37
37
|
attr_reader :cascade
|
38
38
|
|
39
|
-
##
|
40
|
-
# Whether to allow idempotent skipping if view is absent (default: true).
|
41
|
-
#
|
42
|
-
# @return [Boolean]
|
43
|
-
attr_reader :if_exists
|
44
|
-
|
45
39
|
##
|
46
40
|
# @param definition [MatViews::MatViewDefinition]
|
47
41
|
# @param cascade [Boolean] drop with CASCADE instead of RESTRICT
|
48
|
-
# @param
|
49
|
-
def initialize(definition, cascade: false,
|
50
|
-
super(definition)
|
51
|
-
@cascade
|
52
|
-
@if_exists = if_exists ? true : false
|
42
|
+
# @param row_count_strategy [Symbol, nil] one of `:estimated`, `:exact`, or `nil` (default: `:estimated`)
|
43
|
+
def initialize(definition, cascade: false, row_count_strategy: :estimated)
|
44
|
+
super(definition, row_count_strategy: row_count_strategy)
|
45
|
+
@cascade = cascade ? true : false
|
53
46
|
end
|
54
47
|
|
48
|
+
private
|
49
|
+
|
55
50
|
##
|
56
51
|
# Run the drop operation.
|
57
52
|
#
|
58
53
|
# Steps:
|
59
|
-
# - Validate name format
|
60
|
-
# -
|
54
|
+
# - Validate name format
|
55
|
+
# - return :skipped if absent
|
61
56
|
# - Execute DROP MATERIALIZED VIEW.
|
62
57
|
#
|
63
|
-
# @return [MatViews::ServiceResponse]
|
64
|
-
#
|
65
|
-
def run
|
66
|
-
prep = prepare!
|
67
|
-
return prep if prep
|
68
|
-
|
69
|
-
res = skip_early_if_absent
|
70
|
-
return res if res
|
71
|
-
|
72
|
-
perform_drop
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
# ────────────────────────────────────────────────────────────────
|
78
|
-
# internal
|
79
|
-
# ────────────────────────────────────────────────────────────────
|
80
|
-
|
81
|
-
##
|
82
|
-
# Execute the DROP MATERIALIZED VIEW statement.
|
83
|
-
#
|
84
|
-
# @api private
|
85
|
-
# @return [MatViews::ServiceResponse]
|
86
|
-
#
|
87
|
-
def perform_drop
|
88
|
-
conn.execute(sql)
|
89
|
-
|
90
|
-
ok(:deleted,
|
91
|
-
payload: { view: "#{schema}.#{rel}" },
|
92
|
-
meta: { sql: sql, cascade: cascade, if_exists: if_exists })
|
93
|
-
rescue ActiveRecord::StatementInvalid => e
|
94
|
-
msg = "#{e.message} — dependencies exist. Use cascade: true to force drop."
|
95
|
-
error_response(
|
96
|
-
e.class.new(msg),
|
97
|
-
meta: { sql: sql, cascade: cascade, if_exists: if_exists },
|
98
|
-
payload: { view: "#{schema}.#{rel}" }
|
99
|
-
)
|
100
|
-
rescue StandardError => e
|
101
|
-
error_response(
|
102
|
-
e,
|
103
|
-
meta: { sql: sql, cascade: cascade, if_exists: if_exists },
|
104
|
-
payload: { view: "#{schema}.#{rel}" }
|
105
|
-
)
|
106
|
-
end
|
107
|
-
|
108
|
-
##
|
109
|
-
# Skip early if view is absent and `if_exists` is true.
|
110
|
-
#
|
111
58
|
# @api private
|
112
|
-
# @return [MatViews::ServiceResponse, nil]
|
113
59
|
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
60
|
+
# @return [MatViews::ServiceResponse]
|
61
|
+
# - `status: :deleted or :skipped` on success, with `response` containing:
|
62
|
+
# - `view` - the qualified view name
|
63
|
+
# - `row_count_before` - if requested and available
|
64
|
+
# - `row_count_after` - if requested and available
|
65
|
+
# - `status: :error` with `error` on failure, with `error` containing:
|
66
|
+
# - serlialized exception class, message, and backtrace in a hash
|
67
|
+
def _run
|
68
|
+
self.response = { view: "#{schema}.#{rel}", sql: [drop_sql] }
|
69
|
+
|
70
|
+
return ok(:skipped) unless view_exists?
|
71
|
+
|
72
|
+
response[:row_count_before] = fetch_rows_count
|
73
|
+
conn.execute(drop_sql)
|
74
|
+
response[:row_count_after] = UNKNOWN_ROW_COUNT # view is gone
|
75
|
+
ok(:deleted)
|
121
76
|
end
|
122
77
|
|
123
78
|
##
|
124
|
-
#
|
79
|
+
# Assign the request parameters.
|
80
|
+
# Called by {#run} before {#prepare}.
|
81
|
+
# Sets `concurrent: true` in the request hash.
|
125
82
|
#
|
126
83
|
# @api private
|
127
|
-
# @return [
|
84
|
+
# @return [void]
|
128
85
|
#
|
129
|
-
def
|
130
|
-
|
86
|
+
def assign_request
|
87
|
+
self.request = { row_count_strategy: row_count_strategy, cascade: cascade }
|
131
88
|
end
|
132
89
|
|
133
90
|
##
|
@@ -136,22 +93,19 @@ module MatViews
|
|
136
93
|
# @api private
|
137
94
|
# @return [MatViews::ServiceResponse, nil]
|
138
95
|
#
|
139
|
-
def prepare
|
140
|
-
|
141
|
-
return nil if if_exists # skip hard existence check
|
142
|
-
|
143
|
-
return err("Materialized view #{schema}.#{rel} does not exist") unless view_exists?
|
96
|
+
def prepare
|
97
|
+
raise_err "Invalid view name format: #{definition.name.inspect}" unless valid_name?
|
144
98
|
|
145
99
|
nil
|
146
100
|
end
|
147
101
|
|
148
102
|
##
|
149
|
-
#
|
103
|
+
# Build the SQL DROP statement.
|
150
104
|
#
|
151
105
|
# @api private
|
152
106
|
# @return [String]
|
153
107
|
#
|
154
|
-
def
|
108
|
+
def drop_sql
|
155
109
|
drop_mode = cascade ? ' CASCADE' : ' RESTRICT'
|
156
110
|
%(DROP MATERIALIZED VIEW IF EXISTS #{qualified_rel}#{drop_mode})
|
157
111
|
end
|
@@ -13,32 +13,25 @@ module MatViews
|
|
13
13
|
# This is the safest option for simple or low-frequency updates where
|
14
14
|
# blocking reads during refresh is acceptable.
|
15
15
|
#
|
16
|
-
#
|
17
|
-
# -
|
18
|
-
# - `:exact` → runs `COUNT(*)` (accurate, but potentially slow)
|
19
|
-
# - `nil` → no row count included in payload
|
16
|
+
# Options:
|
17
|
+
# - `row_count_strategy:` (Symbol, default: :none) → one of `:estimated`, `:exact`, or `:none or nil` to control row count reporting
|
20
18
|
#
|
21
|
-
#
|
19
|
+
# Returns a {MatViews::ServiceResponse}
|
22
20
|
#
|
23
|
-
# @
|
24
|
-
#
|
25
|
-
#
|
21
|
+
# @see MatViews::Services::ConcurrentRefresh
|
22
|
+
# @see MatViews::Services::SwapRefresh
|
23
|
+
#
|
24
|
+
# @example Direct usage
|
25
|
+
# svc = MatViews::Services::RegularRefresh.new(definition, **options)
|
26
|
+
# response = svc.run
|
27
|
+
# response.success? # => true/false
|
28
|
+
#
|
29
|
+
# @example via job, this is the typical usage and will create a run record in the DB
|
30
|
+
# When definition.refresh_strategy == "concurrent"
|
31
|
+
# MatViews::Jobs::Adapter.enqueue(MatViews::Services::RegularRefresh, definition.id, **options)
|
26
32
|
#
|
27
33
|
class RegularRefresh < BaseService
|
28
|
-
|
29
|
-
# The row count strategy requested.
|
30
|
-
# One of `:estimated`, `:exact`, `nil`, or unrecognized symbol.
|
31
|
-
#
|
32
|
-
# @return [Symbol, nil]
|
33
|
-
attr_reader :row_count_strategy
|
34
|
-
|
35
|
-
##
|
36
|
-
# @param definition [MatViews::MatViewDefinition]
|
37
|
-
# @param row_count_strategy [Symbol, nil] row counting mode
|
38
|
-
def initialize(definition, row_count_strategy: :estimated)
|
39
|
-
super(definition)
|
40
|
-
@row_count_strategy = row_count_strategy
|
41
|
-
end
|
34
|
+
private
|
42
35
|
|
43
36
|
##
|
44
37
|
# Perform the refresh.
|
@@ -49,97 +42,47 @@ module MatViews
|
|
49
42
|
# - Optionally compute row count.
|
50
43
|
#
|
51
44
|
# @return [MatViews::ServiceResponse]
|
45
|
+
# - `status: :updated` on success, with `response` containing:
|
46
|
+
# - `view` - the qualified view name
|
47
|
+
# - `row_count_before` - if requested and available
|
48
|
+
# - `row_count_after` - if requested and available
|
49
|
+
# - `status: :error` with `error` on failure, with `error` containing:
|
50
|
+
# - serlialized exception class, message, and backtrace in a hash
|
52
51
|
#
|
53
|
-
def
|
54
|
-
prep = prepare!
|
55
|
-
return prep if prep
|
56
|
-
|
52
|
+
def _run
|
57
53
|
sql = "REFRESH MATERIALIZED VIEW #{qualified_rel}"
|
58
54
|
|
59
|
-
|
55
|
+
self.response = { view: "#{schema}.#{rel}", sql: [sql] }
|
60
56
|
|
61
|
-
|
62
|
-
|
57
|
+
response[:row_count_before] = fetch_rows_count
|
58
|
+
conn.execute(sql)
|
59
|
+
response[:row_count_after] = fetch_rows_count
|
63
60
|
|
64
|
-
ok(:updated
|
65
|
-
payload: payload,
|
66
|
-
meta: { sql: sql, row_count_strategy: row_count_strategy })
|
67
|
-
rescue StandardError => e
|
68
|
-
error_response(
|
69
|
-
e,
|
70
|
-
meta: {
|
71
|
-
sql: sql,
|
72
|
-
backtrace: Array(e.backtrace),
|
73
|
-
row_count_strategy: row_count_strategy
|
74
|
-
},
|
75
|
-
payload: { view: "#{schema}.#{rel}" }
|
76
|
-
)
|
61
|
+
ok(:updated)
|
77
62
|
end
|
78
63
|
|
79
|
-
private
|
80
|
-
|
81
|
-
# ────────────────────────────────────────────────────────────────
|
82
|
-
# internal
|
83
|
-
# ────────────────────────────────────────────────────────────────
|
84
|
-
|
85
64
|
##
|
86
65
|
# Validate name and existence of the materialized view.
|
87
66
|
#
|
88
67
|
# @api private
|
89
68
|
# @return [MatViews::ServiceResponse, nil]
|
90
69
|
#
|
91
|
-
def prepare
|
92
|
-
|
93
|
-
|
70
|
+
def prepare
|
71
|
+
raise_err "Invalid view name format: #{definition.name.inspect}" unless valid_name?
|
72
|
+
raise_err "Materialized view #{schema}.#{rel} does not exist" unless view_exists?
|
94
73
|
|
95
74
|
nil
|
96
75
|
end
|
97
76
|
|
98
|
-
# ────────────────────────────────────────────────────────────────
|
99
|
-
# rows counting
|
100
|
-
# ────────────────────────────────────────────────────────────────
|
101
|
-
|
102
|
-
##
|
103
|
-
# Pick the appropriate row count method.
|
104
|
-
#
|
105
|
-
# @api private
|
106
|
-
# @return [Integer, nil]
|
107
|
-
#
|
108
|
-
def fetch_rows_count
|
109
|
-
case row_count_strategy
|
110
|
-
when :estimated then estimated_rows_count
|
111
|
-
when :exact then exact_rows_count
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
##
|
116
|
-
# Fast/approx via `pg_class.reltuples`.
|
117
|
-
# Updated by `ANALYZE` and autovacuum.
|
118
|
-
#
|
119
|
-
# @api private
|
120
|
-
# @return [Integer]
|
121
|
-
#
|
122
|
-
def estimated_rows_count
|
123
|
-
conn.select_value(<<~SQL).to_i
|
124
|
-
SELECT COALESCE(c.reltuples::bigint, 0)
|
125
|
-
FROM pg_class c
|
126
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
127
|
-
WHERE c.relkind IN ('m','r','p')
|
128
|
-
AND n.nspname = #{conn.quote(schema)}
|
129
|
-
AND c.relname = #{conn.quote(rel)}
|
130
|
-
LIMIT 1
|
131
|
-
SQL
|
132
|
-
end
|
133
|
-
|
134
77
|
##
|
135
|
-
#
|
136
|
-
#
|
78
|
+
# Assign the request parameters.
|
79
|
+
# Called by {#run} before {#prepare}.
|
137
80
|
#
|
138
81
|
# @api private
|
139
|
-
# @return [
|
82
|
+
# @return [void]
|
140
83
|
#
|
141
|
-
def
|
142
|
-
|
84
|
+
def assign_request
|
85
|
+
self.request = { row_count_strategy: row_count_strategy }
|
143
86
|
end
|
144
87
|
end
|
145
88
|
end
|