Skip to content

Commit

Permalink
Add validity for PostgreSQL indexes
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed May 31, 2022
1 parent a5b2b61 commit d152a11
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 15 deletions.
9 changes: 9 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
* Add validity for PostgreSQL indexes.

```ruby
connection.index_exists?(:users, :email, valid: true)
connection.indexes(:users).select(&:valid?)
```

*fatkodima*

* Fix eager loading for models without primary keys.

*Anmol Chopra*, *Matt Lawrence*, and *Jonathan Hefner*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module ConnectionAdapters # :nodoc:
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
class IndexDefinition # :nodoc:
attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment
attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment, :valid

def initialize(
table, name,
Expand All @@ -18,7 +18,8 @@ def initialize(
where: nil,
type: nil,
using: nil,
comment: nil
comment: nil,
valid: true
)
@table = table
@name = name
Expand All @@ -31,6 +32,11 @@ def initialize(
@type = type
@using = using
@comment = comment
@valid = valid
end

def valid?
@valid
end

def column_options
Expand All @@ -41,6 +47,13 @@ def column_options
}
end

def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, **options)
(columns.nil? || Array(self.columns) == Array(columns).map(&:to_s)) &&
(name.nil? || self.name == name.to_s) &&
(unique.nil? || self.unique == unique) &&
(valid.nil? || self.valid == valid)
end

private
def concise_options(options)
if columns.size == options.size && options.values.uniq.size == 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,11 @@ def indexes(table_name)
# # Check an index with a custom name exists
# index_exists?(:suppliers, :company_id, name: "idx_company_id")
#
# # Check a valid index exists (PostgreSQL only)
# index_exists?(:suppliers, :company_id, valid: true)
#
def index_exists?(table_name, column_name, **options)
checks = []

if column_name.present?
column_names = Array(column_name).map(&:to_s)
checks << lambda { |i| Array(i.columns) == column_names }
end

checks << lambda { |i| i.unique } if options[:unique]
checks << lambda { |i| i.name == options[:name].to_s } if options[:name]

indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
end

# Returns an array of +Column+ objects for the table specified by +table_name+.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def indexes(table_name) # :nodoc:

result = query(<<~SQL, "SCHEMA")
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
Expand All @@ -107,6 +107,7 @@ def indexes(table_name) # :nodoc:
inddef = row[3]
oid = row[4]
comment = row[5]
valid = row[6]

using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten

Expand Down Expand Up @@ -144,7 +145,8 @@ def indexes(table_name) # :nodoc:
opclasses: opclasses,
where: where,
using: using.to_sym,
comment: comment.presence
comment: comment.presence,
valid: valid
)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,20 @@ def test_index_with_opclass
end
end

def test_invalid_index
with_example_table do
@connection.exec_query("INSERT INTO ex (number) VALUES (1), (1)")
error = assert_raises(ActiveRecord::RecordNotUnique) do
@connection.add_index(:ex, :number, unique: true, algorithm: :concurrently, name: :invalid_index)
end
assert_match(/could not create unique index/, error.message)

assert @connection.index_exists?(:ex, :number, name: :invalid_index)
assert_not @connection.index_exists?(:ex, :number, name: :invalid_index, valid: true)
assert @connection.index_exists?(:ex, :number, name: :invalid_index, valid: false)
end
end

def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@connection.columns_for_distinct("posts.id", [])
Expand Down

0 comments on commit d152a11

Please sign in to comment.