Module: ActiveRecord::PGExtensions::PostgreSQLAdapter
- Defined in:
- lib/active_record/pg_extensions/extension.rb,
lib/active_record/pg_extensions/postgresql_adapter.rb
Overview
Contains general additions to the PostgreSQLAdapter
Defined Under Namespace
Classes: Extension
Constant Summary collapse
- TIMEOUTS =
%i[lock_timeout statement_timeout idle_in_transaction_session_timeout].freeze
Instance Method Summary collapse
-
#add_schema_to_search_path(schema) ⇒ Object
temporarily adds schema to the search_path (i.e. so you can use an extension that won't work without being on the search path, such as postgis).
- #alter_extension(extension, schema: nil, version: nil) ⇒ Object
-
#change_constraint(table, constraint, deferrable: nil, enforced: nil, initially: nil, inherit: nil) ⇒ Object
alters the attributes of an existing constraint on a table see https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-ALTER-CONSTRAINT.
- #create_extension(extension, if_not_exists: false, schema: nil, version: nil, cascade: false) ⇒ Object
- #current_wal_flush_lsn ⇒ Object
- #current_wal_insert_lsn ⇒ Object
- #current_wal_lsn ⇒ Object
-
#defer_constraints(*constraints) ⇒ Object
defers constraints, yields to the caller, and then resets back to immediate note that the reset back to immediate is not in an ensure block, since any error thrown would likely mean the transaction is rolled back, and setting constraint checking back to immediate would also fail.
- #drop_extension(*extensions, if_exists: false, cascade: false) ⇒ Object
-
#extension(extension) ⇒ Object
returns an Extension object for a particular extension.
-
#extension_available?(extension, version = nil) ⇒ Boolean
check if a particular extension can be installed.
- #in_recovery? ⇒ Boolean
- #last_wal_receive_lsn ⇒ Object
- #last_wal_replay_lsn ⇒ Object
-
#rename_constraint(table_name, old_name, new_name, if_exists: false) ⇒ Object
renames a constraint on a table see https://www.postgresql.org/docs/current/sql-altertable.html.
- #reset(configuration_parameter) ⇒ Object
- #set(configuration_parameter, value, local: false) ⇒ Object
-
#set_constraints(deferred, *constraints) ⇒ Object
set constraint check timing for the current transaction see https://www.postgresql.org/docs/current/sql-set-constraints.html.
- #set_replica_identity(table, identity = :default) ⇒ Object
- #vacuum(*table_and_columns, full: false, freeze: false, verbose: false, analyze: false, disable_page_skipping: false, skip_locked: false, index_cleanup: false, truncate: false, parallel: nil) ⇒ Object
-
#wal? ⇒ Boolean
Amazon Aurora doesn't have a WAL.
-
#wal_lsn_diff(lsn1 = :current, lsn2 = :last_replay) ⇒ Object
see https://www.postgresql.org/docs/current/functions-admin.html#id-1.5.8.33.5.5.2.2.4.1.1.1 lsns can be literals, or :current, :current_flush, :current_insert, :last_receive, or :last_replay.
-
#with_statement_timeout(timeout = nil) ⇒ Object
@deprecated: manage the transaction yourself and set statement_timeout directly.
Instance Method Details
#add_schema_to_search_path(schema) ⇒ Object
temporarily adds schema to the search_path (i.e. so you can use an extension that won't work without being on the search path, such as postgis)
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 144 def add_schema_to_search_path(schema) if schema_search_path.split(",").include?(schema) yield else old_search_path = schema_search_path manual_rollback = false result = nil transaction(requires_new: true) do self.schema_search_path += ",#{schema}" result = yield self.schema_search_path = old_search_path rescue ActiveRecord::StatementInvalid, ActiveRecord::Rollback => e # the transaction rolling back will revert the search path change; # we don't need to do another query to set it @schema_search_path = old_search_path manual_rollback = e if e.is_a?(ActiveRecord::Rollback) raise end # the transaction call will swallow ActiveRecord::Rollback, # but we want it this method to be transparent raise manual_rollback if manual_rollback result end end |
#alter_extension(extension, schema: nil, version: nil) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 90 def alter_extension(extension, schema: nil, version: nil) if schema && version raise ArgumentError, "Cannot change schema and upgrade to a particular version in a single statement" end sql = "ALTER EXTENSION #{extension}" sql << " UPDATE" if version sql << " TO #{quote(version)}" if version && version != true sql << " SET SCHEMA #{schema}" if schema execute(sql) reload_type_map @extensions&.delete(extension.to_s) end |
#change_constraint(table, constraint, deferrable: nil, enforced: nil, initially: nil, inherit: nil) ⇒ Object
alters the attributes of an existing constraint on a table see https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-ALTER-CONSTRAINT
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 43 def change_constraint(table, constraint, deferrable: nil, enforced: nil, initially: nil, inherit: nil) if deferrable.nil? && initially.nil? && enforced.nil? && inherit.nil? raise ArgumentError, "at least one of :deferrable, :initially, :enforced, or :inherit must be specified" end = [] << (deferrable ? "DEFERRABLE" : "NOT DEFERRABLE") unless deferrable.nil? unless initially.nil? case initially when :deferred then << "INITIALLY DEFERRED" when :immediate then << "INITIALLY IMMEDIATE" else raise ArgumentError, "initially must be :deferred or :immediate" end end << (enforced ? "ENFORCED" : "NOT ENFORCED") unless enforced.nil? alter_table = "ALTER TABLE #{quote_table_name(table)} ALTER CONSTRAINT #{quote_column_name(constraint)}" execute("#{alter_table} #{.join(" ")}") unless .empty? # INHERIT/NO INHERIT cannot be combined with other options execute("#{alter_table} #{inherit ? "INHERIT" : "NO INHERIT"}") unless inherit.nil? end |
#create_extension(extension, if_not_exists: false, schema: nil, version: nil, cascade: false) ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 77 def create_extension(extension, if_not_exists: false, schema: nil, version: nil, cascade: false) sql = +"CREATE EXTENSION " sql <<= "IF NOT EXISTS " if if_not_exists sql << extension.to_s sql << " SCHEMA #{schema}" if schema sql << " VERSION #{quote(version)}" if version sql << " CASCADE" if cascade execute(sql) reload_type_map @extensions&.delete(extension.to_s) end |
#current_wal_flush_lsn ⇒ Object
227 228 229 230 231 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 227 def current_wal_flush_lsn return nil unless wal? select_value("SELECT #{pre_pg10_wal_function_name("pg_current_wal_flush_lsn")}()") end |
#current_wal_insert_lsn ⇒ Object
234 235 236 237 238 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 234 def current_wal_insert_lsn return nil unless wal? select_value("SELECT #{pre_pg10_wal_function_name("pg_current_wal_insert_lsn")}()") end |
#current_wal_lsn ⇒ Object
220 221 222 223 224 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 220 def current_wal_lsn return nil unless wal? select_value("SELECT #{pre_pg10_wal_function_name("pg_current_wal_lsn")}()") end |
#defer_constraints(*constraints) ⇒ Object
defers constraints, yields to the caller, and then resets back to immediate note that the reset back to immediate is not in an ensure block, since any error thrown would likely mean the transaction is rolled back, and setting constraint checking back to immediate would also fail
24 25 26 27 28 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 24 def defer_constraints(*constraints) set_constraints(:deferred, *constraints) yield set_constraints(:immediate, *constraints) end |
#drop_extension(*extensions, if_exists: false, cascade: false) ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 105 def drop_extension(*extensions, if_exists: false, cascade: false) raise ArgumentError, "wrong number of arguments (given 0, expected 1+)" if extensions.empty? sql = +"DROP EXTENSION " sql << "IF EXISTS " if if_exists sql << extensions.join(", ") sql << " CASCADE" if cascade execute(sql) reload_type_map @extensions&.except!(*extensions.map(&:to_s)) end |
#extension(extension) ⇒ Object
returns an Extension object for a particular extension
127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 127 def extension(extension) @extensions ||= {} @extensions.fetch(extension.to_s) do rows = select_rows(<<~SQL, "SCHEMA") SELECT nspname, extversion FROM pg_extension INNER JOIN pg_namespace ON extnamespace=pg_namespace.oid WHERE extname=#{quote(extension)} SQL next nil if rows.empty? Extension.new(extension.to_s, rows[0][0], rows[0][1]) end end |
#extension_available?(extension, version = nil) ⇒ Boolean
check if a particular extension can be installed
118 119 120 121 122 123 124 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 118 def extension_available?(extension, version = nil) sql = +"SELECT 1 FROM " sql << (version ? "pg_available_extension_versions" : "pg_available_extensions") sql << " WHERE name=#{quote(extension)}" sql << " AND version=#{quote(version)}" if version select_value(sql).to_i == 1 end |
#in_recovery? ⇒ Boolean
273 274 275 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 273 def in_recovery? select_value("SELECT pg_is_in_recovery()") end |
#last_wal_receive_lsn ⇒ Object
241 242 243 244 245 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 241 def last_wal_receive_lsn return nil unless wal? select_value("SELECT #{pre_pg10_wal_function_name("pg_last_wal_receive_lsn")}()") end |
#last_wal_replay_lsn ⇒ Object
248 249 250 251 252 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 248 def last_wal_replay_lsn return nil unless wal? select_value("SELECT #{pre_pg10_wal_function_name("pg_last_wal_replay_lsn")}()") end |
#rename_constraint(table_name, old_name, new_name, if_exists: false) ⇒ Object
renames a constraint on a table see https://www.postgresql.org/docs/current/sql-altertable.html
32 33 34 35 36 37 38 39 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 32 def rename_constraint(table_name, old_name, new_name, if_exists: false) return if if_exists && !constraint_exists?(table_name, name: old_name) execute(<<~SQL.squish) ALTER TABLE #{quote_table_name(table_name)} RENAME CONSTRAINT #{quote_column_name(old_name)} TO #{quote_column_name(new_name)} SQL end |
#reset(configuration_parameter) ⇒ Object
282 283 284 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 282 def reset(configuration_parameter) execute("RESET #{configuration_parameter}") end |
#set(configuration_parameter, value, local: false) ⇒ Object
277 278 279 280 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 277 def set(configuration_parameter, value, local: false) value = value.nil? ? "DEFAULT" : quote(value) execute("SET#{" LOCAL" if local} #{configuration_parameter} TO #{value}") end |
#set_constraints(deferred, *constraints) ⇒ Object
set constraint check timing for the current transaction see https://www.postgresql.org/docs/current/sql-set-constraints.html
11 12 13 14 15 16 17 18 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 11 def set_constraints(deferred, *constraints) raise ArgumentError, "deferred must be :deferred or :immediate" unless %i[deferred immediate].include?(deferred.to_sym) constraints = constraints.map { |c| quote_table_name(c) }.join(", ") constraints = "ALL" if constraints.empty? execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}") end |
#set_replica_identity(table, identity = :default) ⇒ Object
66 67 68 69 70 71 72 73 74 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 66 def set_replica_identity(table, identity = :default) identity_clause = case identity when :default, :full, :nothing identity.to_s.upcase else "USING INDEX #{quote_column_name(identity)}" end execute("ALTER TABLE #{quote_table_name(table)} REPLICA IDENTITY #{identity_clause}") end |
#vacuum(*table_and_columns, full: false, freeze: false, verbose: false, analyze: false, disable_page_skipping: false, skip_locked: false, index_cleanup: false, truncate: false, parallel: nil) ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 171 def vacuum(*table_and_columns, full: false, freeze: false, verbose: false, analyze: false, disable_page_skipping: false, skip_locked: false, index_cleanup: false, truncate: false, parallel: nil) if parallel && !(parallel.is_a?(Integer) && parallel.positive?) raise ArgumentError, "parallel must be a positive integer" end sql = +"VACUUM" sql << " FULL" if full sql << " FREEZE" if freeze sql << " VERBOSE" if verbose sql << " ANALYZE" if analyze sql << " DISABLE_PAGE_SKIPPING" if disable_page_skipping sql << " SKIP_LOCKED" if skip_locked sql << " INDEX_CLEANUP" if index_cleanup sql << " TRUNCATE" if truncate sql << " PARALLEL #{parallel}" if parallel sql << " " unless table_and_columns.empty? sql << table_and_columns.map do |table| if table.is_a?(Hash) raise ArgumentError, "columns may only be specified if a analyze is specified" unless analyze table.map do |table_name, columns| "#{quote_table_name(table_name)} (#{Array.wrap(columns).map { |c| quote_column_name(c) }.join(", ")})" end.join(", ") else quote_table_name(table) end end.join(", ") execute(sql) end |
#wal? ⇒ Boolean
Amazon Aurora doesn't have a WAL
211 212 213 214 215 216 217 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 211 def wal? unless instance_variable_defined?(:@has_wal) function_name = pre_pg10_wal_function_name("pg_current_wal_lsn") @has_wal = select_value("SELECT true FROM pg_proc WHERE proname='#{function_name}' LIMIT 1") end @has_wal end |
#wal_lsn_diff(lsn1 = :current, lsn2 = :last_replay) ⇒ Object
see https://www.postgresql.org/docs/current/functions-admin.html#id-1.5.8.33.5.5.2.2.4.1.1.1 lsns can be literals, or :current, :current_flush, :current_insert, :last_receive, or :last_replay
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 256 def wal_lsn_diff(lsn1 = :current, lsn2 = :last_replay) return nil unless wal? lsns = [lsn1, lsn2].map do |lsn| case lsn when :current then pre_pg10_wal_function_name("pg_current_wal_lsn()") when :current_flush then pre_pg10_wal_function_name("pg_current_flush_wal_lsn()") when :current_insert then pre_pg10_wal_function_name("pg_current_insert_wal_lsn()") when :last_receive then pre_pg10_wal_function_name("pg_last_wal_receive_lsn()") when :last_replay then pre_pg10_wal_function_name("pg_last_wal_replay_lsn()") else; quote(lsn) end end select_value("SELECT #{pre_pg10_wal_function_name("pg_wal_lsn_diff")}(#{lsns[0]}, #{lsns[1]})") end |
#with_statement_timeout(timeout = nil) ⇒ Object
@deprecated: manage the transaction yourself and set statement_timeout directly
otherwise, if you're already in a transaction, or you nest with_statement_timeout, the value will unexpectedly "stick" even after the block returns
306 307 308 309 310 311 312 313 |
# File 'lib/active_record/pg_extensions/postgresql_adapter.rb', line 306 def with_statement_timeout(timeout = nil) timeout = 30 if timeout.nil? || timeout == true transaction do self.statement_timeout = timeout yield end end |