Skip to content

Commit 6775e4a

Browse files
sashabelozerovctran
authored andcommitted
Indexes: add WHERE and USING (#482)
1 parent 5803fba commit 6775e4a

File tree

2 files changed

+284
-19
lines changed

2 files changed

+284
-19
lines changed

lib/annotate/annotate_models.rb

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ module AnnotateModels
6565
# Don't show default value for these column types
6666
NO_DEFAULT_COL_TYPES = %w(json jsonb hstore).freeze
6767

68+
INDEX_CLAUSES = {
69+
unique: {
70+
default: 'UNIQUE',
71+
markdown: '_unique_'
72+
},
73+
where: {
74+
default: 'WHERE',
75+
markdown: '_where_'
76+
},
77+
using: {
78+
default: 'USING',
79+
markdown: '_using_'
80+
}
81+
}.freeze
82+
6883
class << self
6984
def annotate_pattern(options = {})
7085
if options[:wrapper_open]
@@ -356,12 +371,54 @@ def index_columns_info(index)
356371
end
357372
end
358373

374+
def index_unique_info(index, format = :default)
375+
index.unique ? " #{INDEX_CLAUSES[:unique][format]}" : ''
376+
end
377+
378+
def index_where_info(index, format = :default)
379+
value = index.try(:where).try(:to_s)
380+
if value.blank?
381+
''
382+
else
383+
" #{INDEX_CLAUSES[:where][format]} #{value}"
384+
end
385+
end
386+
387+
def index_using_info(index, format = :default)
388+
value = index.try(:using) && index.using.try(:to_sym)
389+
if !value.blank? && value != :btree
390+
" #{INDEX_CLAUSES[:using][format]} #{value}"
391+
else
392+
''
393+
end
394+
end
395+
359396
def final_index_string_in_markdown(index)
360-
sprintf("# * `%s`%s:\n# * **`%s`**\n", index.name, index.unique ? " (_unique_)" : "", index_columns_info(index).join("`**\n# * **`"))
397+
details = sprintf(
398+
"%s%s%s",
399+
index_unique_info(index, :markdown),
400+
index_where_info(index, :markdown),
401+
index_using_info(index, :markdown)
402+
).strip
403+
details = " (#{details})" unless details.blank?
404+
405+
sprintf(
406+
"# * `%s`%s:\n# * **`%s`**\n",
407+
index.name,
408+
details,
409+
index_columns_info(index).join("`**\n# * **`")
410+
)
361411
end
362412

363413
def final_index_string(index, max_size)
364-
sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{index_columns_info(index).join(',')})", index.unique ? "UNIQUE" : "").rstrip + "\n"
414+
sprintf(
415+
"# %-#{max_size}.#{max_size}s %s%s%s%s",
416+
index.name,
417+
"(#{index_columns_info(index).join(',')})",
418+
index_unique_info(index),
419+
index_where_info(index),
420+
index_using_info(index)
421+
).rstrip + "\n"
365422
end
366423

367424
def hide_limit?(col_type, options)

spec/annotate/annotate_models_spec.rb

Lines changed: 225 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
require 'active_support/core_ext/string'
66

77
describe AnnotateModels do
8-
def mock_index(name, columns = [], orders = {}, unique = false)
8+
def mock_index(name, params = {})
99
double('IndexKeyDefinition',
1010
name: name,
11-
columns: columns,
12-
unique: unique,
13-
orders: orders)
11+
columns: params[:columns] || [],
12+
unique: params[:unique] || false,
13+
orders: params[:orders] || {},
14+
where: params[:where],
15+
using: params[:using])
1416
end
1517

1618
def mock_foreign_key(name, from_column, to_table, to_column = 'id', constraints = {})
@@ -303,8 +305,8 @@ def mock_column(name, type, options = {})
303305
[
304306
mock_column(:id, :integer),
305307
mock_column(:foreign_thing_id, :integer)
306-
], [mock_index('index_rails_02e851e3b7', ['id']),
307-
mock_index('index_rails_02e851e3b8', ['foreign_thing_id'])])
308+
], [mock_index('index_rails_02e851e3b7', columns: ['id']),
309+
mock_index('index_rails_02e851e3b8', columns: ['foreign_thing_id'])])
308310
expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_indexes: true)).to eql(<<-EOS)
309311
# Schema Info
310312
#
@@ -331,10 +333,10 @@ def mock_column(name, type, options = {})
331333
mock_column("value", :string)
332334
],
333335
[
334-
mock_index('index_rails_02e851e3b7', ['id']),
336+
mock_index('index_rails_02e851e3b7', columns: ['id']),
335337
mock_index('index_rails_02e851e3b8',
336-
%w(firstname surname value),
337-
'surname' => :asc, 'value' => :desc)
338+
columns: %w(firstname surname value),
339+
orders: { 'surname' => :asc, 'value' => :desc })
338340
])
339341
expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_indexes: true)).to eql(<<-EOS)
340342
# Schema Info
@@ -354,6 +356,72 @@ def mock_column(name, type, options = {})
354356
EOS
355357
end
356358

359+
it 'should get indexes keys with where clause' do
360+
klass = mock_class(:users,
361+
:id,
362+
[
363+
mock_column("id", :integer),
364+
mock_column("firstname", :string),
365+
mock_column("surname", :string),
366+
mock_column("value", :string)
367+
],
368+
[
369+
mock_index('index_rails_02e851e3b7', columns: ['id']),
370+
mock_index('index_rails_02e851e3b8',
371+
columns: %w(firstname surname),
372+
where: 'value IS NOT NULL')
373+
])
374+
expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_indexes: true)).to eql(<<-EOS)
375+
# Schema Info
376+
#
377+
# Table name: users
378+
#
379+
# id :integer not null, primary key
380+
# firstname :string not null
381+
# surname :string not null
382+
# value :string not null
383+
#
384+
# Indexes
385+
#
386+
# index_rails_02e851e3b7 (id)
387+
# index_rails_02e851e3b8 (firstname,surname) WHERE value IS NOT NULL
388+
#
389+
EOS
390+
end
391+
392+
it 'should get indexes keys with using clause other than btree' do
393+
klass = mock_class(:users,
394+
:id,
395+
[
396+
mock_column("id", :integer),
397+
mock_column("firstname", :string),
398+
mock_column("surname", :string),
399+
mock_column("value", :string)
400+
],
401+
[
402+
mock_index('index_rails_02e851e3b7', columns: ['id']),
403+
mock_index('index_rails_02e851e3b8',
404+
columns: %w(firstname surname),
405+
using: 'hash')
406+
])
407+
expect(AnnotateModels.get_schema_info(klass, 'Schema Info', show_indexes: true)).to eql(<<-EOS)
408+
# Schema Info
409+
#
410+
# Table name: users
411+
#
412+
# id :integer not null, primary key
413+
# firstname :string not null
414+
# surname :string not null
415+
# value :string not null
416+
#
417+
# Indexes
418+
#
419+
# index_rails_02e851e3b7 (id)
420+
# index_rails_02e851e3b8 (firstname,surname) USING hash
421+
#
422+
EOS
423+
end
424+
357425
it 'should get simple indexes keys' do
358426
klass = mock_class(:users,
359427
:id,
@@ -362,10 +430,10 @@ def mock_column(name, type, options = {})
362430
mock_column(:foreign_thing_id, :integer)
363431
],
364432
[
365-
mock_index('index_rails_02e851e3b7', ['id']),
433+
mock_index('index_rails_02e851e3b7', columns: ['id']),
366434
mock_index('index_rails_02e851e3b8',
367-
['foreign_thing_id'],
368-
'foreign_thing_id' => :desc)
435+
columns: ['foreign_thing_id'],
436+
orders: { 'foreign_thing_id' => :desc })
369437
])
370438
expect(AnnotateModels.get_schema_info(klass, 'Schema Info', simple_indexes: true)).to eql(<<-EOS)
371439
# Schema Info
@@ -384,8 +452,8 @@ def mock_column(name, type, options = {})
384452
[
385453
mock_column("id", :integer),
386454
mock_column("name", :string)
387-
], [mock_index('index_rails_02e851e3b7', ['id']),
388-
mock_index('index_rails_02e851e3b8', 'LOWER(name)')])
455+
], [mock_index('index_rails_02e851e3b7', columns: ['id']),
456+
mock_index('index_rails_02e851e3b8', columns: 'LOWER(name)')])
389457
expect(AnnotateModels.get_schema_info(klass, 'Schema Info', simple_indexes: true)).to eql(<<-EOS)
390458
# Schema Info
391459
#
@@ -501,10 +569,79 @@ def mock_column(name, type, options = {})
501569
mock_column(:name, :string, limit: 50)
502570
],
503571
[
504-
mock_index('index_rails_02e851e3b7', ['id']),
572+
mock_index('index_rails_02e851e3b7', columns: ['id']),
505573
mock_index('index_rails_02e851e3b8',
506-
['foreign_thing_id'],
507-
'foreign_thing_id' => :desc)
574+
columns: ['foreign_thing_id'])
575+
])
576+
expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, show_indexes: true)).to eql(<<-EOS)
577+
# #{AnnotateModels::PREFIX}
578+
#
579+
# Table name: `users`
580+
#
581+
# ### Columns
582+
#
583+
# Name | Type | Attributes
584+
# ----------- | ------------------ | ---------------------------
585+
# **`id`** | `integer` | `not null, primary key`
586+
# **`name`** | `string(50)` | `not null`
587+
#
588+
# ### Indexes
589+
#
590+
# * `index_rails_02e851e3b7`:
591+
# * **`id`**
592+
# * `index_rails_02e851e3b8`:
593+
# * **`foreign_thing_id`**
594+
#
595+
EOS
596+
end
597+
598+
it 'should get schema info as Markdown with unique indexes' do
599+
klass = mock_class(:users,
600+
:id,
601+
[
602+
mock_column(:id, :integer),
603+
mock_column(:name, :string, limit: 50)
604+
],
605+
[
606+
mock_index('index_rails_02e851e3b7', columns: ['id']),
607+
mock_index('index_rails_02e851e3b8',
608+
columns: ['foreign_thing_id'],
609+
unique: true)
610+
])
611+
expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, show_indexes: true)).to eql(<<-EOS)
612+
# #{AnnotateModels::PREFIX}
613+
#
614+
# Table name: `users`
615+
#
616+
# ### Columns
617+
#
618+
# Name | Type | Attributes
619+
# ----------- | ------------------ | ---------------------------
620+
# **`id`** | `integer` | `not null, primary key`
621+
# **`name`** | `string(50)` | `not null`
622+
#
623+
# ### Indexes
624+
#
625+
# * `index_rails_02e851e3b7`:
626+
# * **`id`**
627+
# * `index_rails_02e851e3b8` (_unique_):
628+
# * **`foreign_thing_id`**
629+
#
630+
EOS
631+
end
632+
633+
it 'should get schema info as Markdown with ordered indexes' do
634+
klass = mock_class(:users,
635+
:id,
636+
[
637+
mock_column(:id, :integer),
638+
mock_column(:name, :string, limit: 50)
639+
],
640+
[
641+
mock_index('index_rails_02e851e3b7', columns: ['id']),
642+
mock_index('index_rails_02e851e3b8',
643+
columns: ['foreign_thing_id'],
644+
orders: { 'foreign_thing_id' => :desc })
508645
])
509646
expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, show_indexes: true)).to eql(<<-EOS)
510647
# #{AnnotateModels::PREFIX}
@@ -528,6 +665,77 @@ def mock_column(name, type, options = {})
528665
EOS
529666
end
530667

668+
it 'should get schema info as Markdown with indexes with WHERE clause' do
669+
klass = mock_class(:users,
670+
:id,
671+
[
672+
mock_column(:id, :integer),
673+
mock_column(:name, :string, limit: 50)
674+
],
675+
[
676+
mock_index('index_rails_02e851e3b7', columns: ['id']),
677+
mock_index('index_rails_02e851e3b8',
678+
columns: ['foreign_thing_id'],
679+
unique: true,
680+
where: 'name IS NOT NULL')
681+
])
682+
expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, show_indexes: true)).to eql(<<-EOS)
683+
# #{AnnotateModels::PREFIX}
684+
#
685+
# Table name: `users`
686+
#
687+
# ### Columns
688+
#
689+
# Name | Type | Attributes
690+
# ----------- | ------------------ | ---------------------------
691+
# **`id`** | `integer` | `not null, primary key`
692+
# **`name`** | `string(50)` | `not null`
693+
#
694+
# ### Indexes
695+
#
696+
# * `index_rails_02e851e3b7`:
697+
# * **`id`**
698+
# * `index_rails_02e851e3b8` (_unique_ _where_ name IS NOT NULL):
699+
# * **`foreign_thing_id`**
700+
#
701+
EOS
702+
end
703+
704+
it 'should get schema info as Markdown with indexes with using clause other than btree' do
705+
klass = mock_class(:users,
706+
:id,
707+
[
708+
mock_column(:id, :integer),
709+
mock_column(:name, :string, limit: 50)
710+
],
711+
[
712+
mock_index('index_rails_02e851e3b7', columns: ['id']),
713+
mock_index('index_rails_02e851e3b8',
714+
columns: ['foreign_thing_id'],
715+
using: 'hash')
716+
])
717+
expect(AnnotateModels.get_schema_info(klass, AnnotateModels::PREFIX, format_markdown: true, show_indexes: true)).to eql(<<-EOS)
718+
# #{AnnotateModels::PREFIX}
719+
#
720+
# Table name: `users`
721+
#
722+
# ### Columns
723+
#
724+
# Name | Type | Attributes
725+
# ----------- | ------------------ | ---------------------------
726+
# **`id`** | `integer` | `not null, primary key`
727+
# **`name`** | `string(50)` | `not null`
728+
#
729+
# ### Indexes
730+
#
731+
# * `index_rails_02e851e3b7`:
732+
# * **`id`**
733+
# * `index_rails_02e851e3b8` (_using_ hash):
734+
# * **`foreign_thing_id`**
735+
#
736+
EOS
737+
end
738+
531739
describe '#set_defaults' do
532740
it 'should default show_complete_foreign_keys to false' do
533741
expect(Annotate.true?(ENV['show_complete_foreign_keys'])).to be(false)

0 commit comments

Comments
 (0)