db_leftovers 1.3.3 → 1.4.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.
data/Changelog ADDED
@@ -0,0 +1,6 @@
1
+ 1.4.0
2
+ -----
3
+
4
+ - Added support for functional indexes.
5
+
6
+
data/README.md CHANGED
@@ -44,7 +44,7 @@ Within the DSL file, the following methods are supported:
44
44
 
45
45
  ### index(table\_name, columns, [opts])
46
46
 
47
- This ensures that you have an index on the given table and column(s). The `columns` parameter can be either a string/symbol or a list of strings/symbols. Opts is a hash with the following possible keys:
47
+ This ensures that you have an index on the given table and column(s). The `columns` parameter can be either a symbol or a list of strings/symbols. (If you pass a single string for the `columns` parameter, it will be treated as the expression for a functional index rather than a column name.) Opts is a hash with the following possible keys:
48
48
 
49
49
  * `:name` The name of the index. Defaults to `index_`*table\_name*`_on_`*column\_names*, like the `add_index` method from Rails migrations.
50
50
 
@@ -54,11 +54,15 @@ This ensures that you have an index on the given table and column(s). The `colum
54
54
 
55
55
  * `:using` Lets you specify what kind of index to create. Default is `btree`, but if you're on Postgres you might also want `gist`, `gin`, or `hash`.
56
56
 
57
+ * `:function` Lets you specify an expression rather than a list of columns. If you give this option, you should pass an empty list of column names. Alternately, you can pass a string as the column name (rather than a symbol), and db\_leftovers will interpret it as a function.
58
+
57
59
  #### Examples
58
60
 
59
61
  index :books, :author_id
60
62
  index :books, [:publisher_id, :published_at]
61
63
  index :books, :isbn, :unique => true
64
+ index :authors, [], function: 'lower(name)'
65
+ index :authors, 'lower(name)'
62
66
 
63
67
  ### foreign\_key(from\_table, [from\_column], to\_table, [to\_column], [opts])
64
68
 
@@ -23,7 +23,7 @@ module DBLeftovers
23
23
  CREATE #{unique} INDEX #{idx.index_name}
24
24
  ON #{idx.table_name}
25
25
  #{using}
26
- (#{idx.column_names.join(', ')})
26
+ (#{idx.index_function || idx.column_names.join(', ')})
27
27
  #{where}
28
28
  EOQ
29
29
  execute_sql(sql)
@@ -3,23 +3,33 @@ module DBLeftovers
3
3
  # Just a struct to hold all the info for one index:
4
4
  class Index
5
5
  attr_accessor :table_name, :column_names, :index_name,
6
- :where_clause, :using_clause, :unique
6
+ :where_clause, :using_clause, :unique, :index_function
7
7
 
8
8
  def initialize(table_name, column_names, opts={})
9
9
  opts = {
10
10
  :where => nil,
11
+ :function => nil,
11
12
  :unique => false,
12
13
  :using => nil
13
14
  }.merge(opts)
14
15
  opts.keys.each do |k|
15
- raise "Unknown option: #{k}" unless [:where, :unique, :using, :name].include?(k)
16
+ raise "Unknown option: #{k}" unless [:where, :function, :unique, :using, :name].include?(k)
17
+ end
18
+ if column_names.is_a?(String) and opts[:function].nil?
19
+ opts[:function] = column_names
20
+ column_names = []
16
21
  end
17
22
  @table_name = table_name.to_s
18
23
  @column_names = [column_names].flatten.map{|x| x.to_s}
19
24
  @where_clause = opts[:where]
25
+ @index_function = opts[:function]
20
26
  @using_clause = opts[:using]
21
27
  @unique = !!opts[:unique]
22
28
  @index_name = (opts[:name] || choose_name(@table_name, @column_names)).to_s
29
+
30
+ raise "Indexes need a table!" unless @table_name
31
+ raise "Indexes need at least column or an expression!" unless (@column_names.any? or @index_function)
32
+ raise "Can't have both columns and an expression!" if (@column_names.size > 0 and @index_function)
23
33
  end
24
34
 
25
35
  def unique?
@@ -31,19 +41,30 @@ module DBLeftovers
31
41
  other.column_names == column_names and
32
42
  other.index_name == index_name and
33
43
  other.where_clause == where_clause and
44
+ other.index_function == index_function and
34
45
  other.using_clause == using_clause and
35
46
  other.unique == unique
36
47
  end
37
48
 
38
49
  def to_s
39
- "<#{@index_name}: #{@table_name}.[#{column_names.join(",")}] unique=#{@unique}, where=#{@where_clause}, using=#{@using_clause}>"
50
+ "<#{@index_name}: #{@table_name}.[#{column_names.join(",")}] unique=#{@unique}, where=#{@where_clause}, function=#{@index_function}, using=#{@using_clause}>"
40
51
  end
41
52
 
42
53
  private
43
54
 
44
55
  def choose_name(table_name, column_names)
56
+ topic = if column_names.any?
57
+ column_names.join("_and_")
58
+ else
59
+ index_function
60
+ end
61
+ ret = "index_#{table_name}_on_#{topic}"
62
+ ret = ret.gsub(/[^a-zA-Z0-9]/, '_').
63
+ gsub(/__+/, '_').
64
+ gsub(/^_/, '').
65
+ gsub(/_$/, '')
45
66
  # Max length in Postgres is 63; in MySQL 64:
46
- "index_#{table_name}_on_#{column_names.join('_and_')}"[0,63]
67
+ ret[0,63]
47
68
  end
48
69
 
49
70
  end
@@ -16,7 +16,8 @@ module DBLeftovers
16
16
  ix.indisunique AS is_unique,
17
17
  array_to_string(ix.indkey, ',') AS column_numbers,
18
18
  am.amname AS index_type,
19
- pg_get_expr(ix.indpred, ix.indrelid) AS where_clause
19
+ pg_get_expr(ix.indpred, ix.indrelid) AS where_clause,
20
+ pg_get_expr(ix.indexprs, ix.indrelid) AS index_function
20
21
  FROM pg_class t,
21
22
  pg_class i,
22
23
  pg_index ix,
@@ -37,10 +38,11 @@ module DBLeftovers
37
38
  ix.indrelid,
38
39
  ix.indkey,
39
40
  am.amname,
40
- ix.indpred
41
+ ix.indpred,
42
+ ix.indexprs
41
43
  ORDER BY t.relname, i.relname
42
44
  EOQ
43
- @conn.select_rows(sql).each do |indexrelid, indrelid, table_name, index_name, is_unique, column_numbers, index_method, where_clause|
45
+ @conn.select_rows(sql).each do |indexrelid, indrelid, table_name, index_name, is_unique, column_numbers, index_method, where_clause, index_function|
44
46
  where_clause = remove_outer_parens(where_clause) if where_clause
45
47
  index_method = nil if index_method == 'btree'
46
48
  ret[index_name] = Index.new(
@@ -48,6 +50,7 @@ module DBLeftovers
48
50
  column_names_for_index(indrelid, column_numbers.split(",")),
49
51
  unique: is_unique == 't',
50
52
  where: where_clause,
53
+ function: index_function,
51
54
  using: index_method,
52
55
  name: index_name
53
56
  )
@@ -128,6 +131,7 @@ module DBLeftovers
128
131
  private
129
132
 
130
133
  def column_names_for_index(table_id, column_numbers)
134
+ return [] if column_numbers == ['0']
131
135
  column_numbers.map do |c|
132
136
  sql = <<-EOQ
133
137
  SELECT attname
@@ -1,3 +1,3 @@
1
1
  module DbLeftovers
2
- VERSION = '1.3.3'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -102,6 +102,22 @@ describe DBLeftovers::PostgresDatabaseInterface do
102
102
  @db.lookup_all_constraints['books_have_positive_pages'].check.should == 'pages_count > 12'
103
103
  end
104
104
 
105
+ it "should allow functional indexes, specified as a string" do
106
+ DBLeftovers::Definition.define :db_interface => @db do
107
+ index :authors, 'lower(name)'
108
+ end
109
+ @db.lookup_all_indexes.size.should == 1
110
+ @db.lookup_all_indexes.keys.sort.should == ['index_authors_on_lower_name']
111
+ end
112
+
113
+ it "should allow functional indexes, specified with an option" do
114
+ DBLeftovers::Definition.define :db_interface => @db do
115
+ index :authors, [], function: 'lower(name)'
116
+ end
117
+ @db.lookup_all_indexes.size.should == 1
118
+ @db.lookup_all_indexes.keys.sort.should == ['index_authors_on_lower_name']
119
+ end
120
+
105
121
  end
106
122
  end
107
123
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: db_leftovers
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -69,6 +69,7 @@ extra_rdoc_files: []
69
69
  files:
70
70
  - .document
71
71
  - .gitignore
72
+ - Changelog
72
73
  - Gemfile
73
74
  - Gemfile.lock
74
75
  - LICENSE.txt
@@ -111,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
111
112
  version: '0'
112
113
  segments:
113
114
  - 0
114
- hash: 3023613074233627262
115
+ hash: 1649859164079023412
115
116
  required_rubygems_version: !ruby/object:Gem::Requirement
116
117
  none: false
117
118
  requirements: