From 69dcd45aea770ca90bb3e1f8ce4e944dfb37e766 Mon Sep 17 00:00:00 2001 From: Vlad Jebelev Date: Fri, 4 Nov 2011 15:45:24 -0500 Subject: AR changes to support creating ordered (asc, desc) indexes --- .../abstract/schema_definitions.rb | 2 +- .../abstract/schema_statements.rb | 30 +++++++++++++++++- .../connection_adapters/abstract_adapter.rb | 5 +++ .../connection_adapters/abstract_mysql_adapter.rb | 36 ++++++++++++++++------ .../connection_adapters/postgresql_adapter.rb | 16 ++++++++-- .../connection_adapters/sqlite_adapter.rb | 4 +++ activerecord/lib/active_record/migration.rb | 5 +-- activerecord/lib/active_record/schema_dumper.rb | 3 ++ 8 files changed, 85 insertions(+), 16 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 989a4fcbca..3f69f75565 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -6,7 +6,7 @@ require 'bigdecimal/util' module ActiveRecord module ConnectionAdapters #:nodoc: - class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc: + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders) #:nodoc: end # Abstract representation of a column definition. Instances of this type diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 7226069ebf..0e5e33fa02 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -339,6 +339,14 @@ module ActiveRecord # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) # # Note: SQLite doesn't support index length + # + # ====== Creating an index with a sort order (desc or asc, asc is the default) + # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :part_id => :asc}) + # generates + # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) + # + # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it) + # def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})" @@ -520,9 +528,29 @@ module ActiveRecord end protected + def add_index_sort_order(option_strings, column_names, options = {}) + if options.is_a?(Hash) && order = options[:order] + case order + when Hash + column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)} + when String + column_names.each {|name| option_strings[name] += " #{order.upcase}"} + end + end + + return option_strings + end + # Overridden by the mysql adapter for supporting index lengths def quoted_columns_for_index(column_names, options = {}) - column_names.map {|name| quote_column_name(name) } + option_strings = Hash[column_names.map {|name| [name, '']}] + + # add index sort order if supported + if supports_index_sort_order? + option_strings = add_index_sort_order(option_strings, column_names, options) + end + + column_names.map {|name| quote_column_name(name) + option_strings[name]} end def options_include_default?(options) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 4c3a8f7233..c47bcfc406 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -130,6 +130,11 @@ module ActiveRecord false end + # Does this adapter support index sort order? + def supports_index_sort_order? + false + end + # QUOTING ================================================== # Override to return the quoted table name. Defaults to column quoting. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index dd573ba569..35c2118190 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -155,6 +155,12 @@ module ActiveRecord true end + # Technically MySQL allows to create indexes with the sort order syntax + # but at the moment (5.5) it doesn't yet implement them + def supports_index_sort_order? + true + end + def native_database_types NATIVE_DATABASE_TYPES end @@ -526,17 +532,29 @@ module ActiveRecord protected + def add_index_length(option_strings, column_names, options = {}) + if options.is_a?(Hash) && length = options[:length] + case length + when Hash + column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)} + when Fixnum + column_names.each {|name| option_strings[name] += "(#{length})"} + end + end + + return option_strings + end + def quoted_columns_for_index(column_names, options = {}) - length = options[:length] if options.is_a?(Hash) + option_strings = Hash[column_names.map {|name| [name, '']}] - case length - when Hash - column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } - when Fixnum - column_names.map {|name| "#{quote_column_name(name)}(#{length})"} - else - column_names.map {|name| quote_column_name(name) } - end + # add index length + option_strings = add_index_length(option_strings, column_names, options) + + # add index sort order + option_strings = add_index_sort_order(option_strings, column_names, options) + + column_names.map {|name| quote_column_name(name) + option_strings[name]} end def translate_exception(exception, message) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e8a43e7bce..15e329a1c8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -247,6 +247,10 @@ module ActiveRecord true end + def supports_index_sort_order? + true + end + class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max) super @@ -756,7 +760,7 @@ module ActiveRecord def indexes(table_name, name = nil) schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') result = query(<<-SQL, name) - SELECT distinct i.relname, d.indisunique, d.indkey, t.oid + SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid @@ -772,7 +776,8 @@ module ActiveRecord index_name = row[0] unique = row[1] == 't' indkey = row[2].split(" ") - oid = row[3] + inddef = row[3] + oid = row[4] columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")] SELECT a.attnum, a.attname @@ -782,7 +787,12 @@ module ActiveRecord SQL column_names = columns.values_at(*indkey).compact - column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names) + + # add info on sort order for columns (only desc order is explicitly specified, asc is the default) + desc_order_columns = inddef.scan(/(\w+) DESC/).flatten + orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} + + column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders) end.compact end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index f74f3e6ec8..caecbc9b3a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -157,6 +157,10 @@ module ActiveRecord sqlite_version >= '3.1.0' end + def supports_index_sort_order? + sqlite_version >= '3.3.0' + end + def native_database_types #:nodoc: { :primary_key => default_primary_key_type, diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 7166f1b82a..2fb1b8f7a3 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -116,8 +116,9 @@ module ActiveRecord # +column_name+ from the table called +table_name+. # * add_index(table_name, column_names, options): Adds a new index # with the name of the column. Other options include - # :name and :unique (e.g. - # { :name => "users_name_index", :unique => true }). + # :name, :unique (e.g. + # { :name => "users_name_index", :unique => true }) and :order + # (e.g. { :order => {:name => :desc} }). # * remove_index(table_name, :column => column_name): Removes the index # specified by +column_name+. # * remove_index(table_name, :name => index_name): Removes the index diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 6fe305f843..cdde5cf3b9 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -190,6 +190,9 @@ HEADER index_lengths = (index.lengths || []).compact statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty? + index_orders = (index.orders || {}) + statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty? + ' ' + statement_parts.join(', ') end -- cgit v1.2.3