README.md in pg_ha_migrations-1.7.0 vs README.md in pg_ha_migrations-1.8.0
- old
+ new
@@ -66,18 +66,23 @@
When `unsafe_*` migration methods support checks of this type you can bypass the checks by passing an `:allow_dependent_objects` key in the method's `options` hash containing an array of dependent object types you'd like to allow. Until 2.0 none of these checks will run by default, but you can opt-in by setting `config.check_for_dependent_objects = true` [in your configuration initializer](#configuration).
Similarly we believe the `force: true` option to ActiveRecord's `create_table` method is always unsafe, and therefore we disallow it even when calling `unsafe_create_table`. This option won't be enabled by default until 2.0, but you can opt-in by setting `config.allow_force_create_table = false` [in your configuration initializer](#configuration).
-[Running multiple DDL statements inside a transaction acquires exclusive locks on all of the modified objects](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680#cc22). For that reason, this gem [disables DDL transactions](./lib/pg_ha_migrations.rb:8) by default. You can change this by resetting `ActiveRecord::Migration.disable_ddl_transaction` in your application.
+[Running multiple DDL statements inside a transaction acquires exclusive locks on all of the modified objects](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680#cc22). For that reason, this gem [disables DDL transactions](./lib/pg_ha_migrations/hacks/disable_ddl_transaction.rb) by default. You can change this by resetting `ActiveRecord::Migration.disable_ddl_transaction` in your application.
The following functionality is currently unsupported:
- Rollbacks
- Generators
- schema.rb
+Compatibility notes:
+
+- While some features may work with other versions, this gem is currently tested against PostgreSQL 11+ and Partman 4.x
+- There is a [bug](https://github.com/rails/rails/pull/41490) in early versions of Rails 6.1 when using `algorithm: :concurrently`. To add / remove indexes concurrently, please upgrade to at least Rails 6.1.4.
+
#### safe\_create\_table
Safely creates a new table.
```ruby
@@ -164,10 +169,18 @@
```ruby
unsafe_make_column_not_nullable :table, :column
```
+#### safe\_add\_index\_on\_empty\_table
+
+Safely add an index on a table with zero rows. This will raise an error if the table contains data.
+
+```ruby
+safe_add_index_on_empty_table :table, :column
+```
+
#### safe\_add\_concurrent\_index
Add an index concurrently.
```ruby
@@ -186,10 +199,45 @@
```ruby
safe_remove_concurrent_index :table, :name => :index_name
```
+#### safe\_add\_concurrent\_partitioned\_index
+
+Add an index to a natively partitioned table concurrently, as described in the [table partitioning docs](https://www.postgresql.org/docs/current/ddl-partitioning.html):
+
+> To avoid long lock times, it is possible to use `CREATE INDEX ON ONLY` the partitioned table; such an index is marked invalid, and the partitions do not get the index applied automatically.
+> The indexes on partitions can be created individually using `CONCURRENTLY`, and then attached to the index on the parent using `ALTER INDEX .. ATTACH PARTITION`.
+> Once indexes for all partitions are attached to the parent index, the parent index is marked valid automatically.
+
+```ruby
+# Assuming this table has partitions child1 and child2, the following indexes will be created:
+# - index_partitioned_table_on_column
+# - index_child1_on_column (attached to index_partitioned_table_on_column)
+# - index_child2_on_column (attached to index_partitioned_table_on_column)
+safe_add_concurrent_partitioned_index :partitioned_table, :column
+```
+
+Add a composite index using the `hash` index type with custom name for the parent index when the parent table contains sub-partitions.
+
+```ruby
+# Assuming this table has partitions child1 and child2, and child1 has sub-partitions sub1 and sub2,
+# the following indexes will be created:
+# - custom_name_idx
+# - index_child1_on_column1_column2 (attached to custom_name_idx)
+# - index_sub1_on_column1_column2 (attached to index_child1_on_column1_column2)
+# - index_sub2_on_column1_column2 (attached to index_child1_on_column1_column2)
+# - index_child2_on_column1_column2 (attached to custom_name_idx)
+safe_add_concurrent_partitioned_index :partitioned_table, [:column1, :column2], name: "custom_name_idx", using: :hash
+```
+
+Note:
+
+This method runs multiple DDL statements non-transactionally.
+Creating or attaching an index on a child table could fail.
+In such cases an exception will be raised, and an `INVALID` index will be left on the parent table.
+
#### safe\_add\_unvalidated\_check\_constraint
Safely add a `CHECK` constraint. The constraint will not be immediately validated on existing rows to avoid a full table scan while holding an exclusive lock. After adding the constraint, you'll need to use `safe_validate_check_constraint` to validate existing rows.
```ruby
@@ -385,18 +433,31 @@
### Utilities
#### safely\_acquire\_lock\_for\_table
-Safely acquire a lock for a table.
+Safely acquire an access exclusive lock for a table.
```ruby
safely_acquire_lock_for_table(:table) do
...
end
```
+Safely acquire a lock for a table in a different mode.
+
+```ruby
+safely_acquire_lock_for_table(:table, mode: :share) do
+ ...
+end
+```
+
+Note:
+
+We enforce that only one table (or a table and its partitions) can be locked at a time.
+Attempting to acquire a nested lock on a different table will result in an error.
+
#### adjust\_lock\_timeout
Adjust lock timeout.
```ruby
@@ -419,9 +480,25 @@
Set maintenance work mem.
```ruby
safe_set_maintenance_work_mem_gb 1
+```
+
+#### ensure\_small\_table!
+
+Ensure a table on disk is below the default threshold (10 megabytes).
+This will raise an error if the table is too large.
+
+```ruby
+ensure_small_table! :table
+```
+
+Ensure a table on disk is below a custom threshold and is empty.
+This will raise an error if the table is too large and/or contains data.
+
+```ruby
+ensure_small_table! :table, empty: true, threshold: 100.megabytes
```
### Configuration
The gem can be configured in an initializer.