Index: app/models/query.rb IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- app/models/query.rb (revision e20adb0532f07510320b118df2c2e53efafd294e) +++ app/models/query.rb (revision c0ef6de058bb128515d1a4085c592da75e82188b) @@ -716,7 +716,7 @@ arg = arg.keys.sort.collect {|k| arg[k]} end if arg - c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']} + c = arg.select { |k, o| !k.to_s.blank? }.slice(0, 3).collect { |k, o| [k.to_s, o.to_s] } end write_attribute(:sort_criteria, c) end @@ -984,7 +984,7 @@ " SELECT customized_id FROM #{CustomValue.table_name}" + " WHERE customized_type='#{target_class}' AND custom_field_id=#{chained_custom_field_id}" + " AND #{sql_for_field(field, operator, value, CustomValue.table_name, 'value')}))" - + end def sql_for_custom_field_attribute(field, operator, value, custom_field_id, attribute) Index: config/locales/en.yml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- config/locales/en.yml (revision e20adb0532f07510320b118df2c2e53efafd294e) +++ config/locales/en.yml (revision c0ef6de058bb128515d1a4085c592da75e82188b) @@ -898,6 +898,8 @@ label_sort: Sort label_ascending: Ascending label_descending: Descending + label_ascending_nulls_last: Ascending with null values at the end + label_descending_nulls_first: Descending with null values at the top label_date_from_to: From %{start} to %{end} label_wiki_content_added: Wiki page added label_wiki_content_updated: Wiki page updated Index: config/locales/ru.yml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- config/locales/ru.yml (revision e20adb0532f07510320b118df2c2e53efafd294e) +++ config/locales/ru.yml (revision c0ef6de058bb128515d1a4085c592da75e82188b) @@ -1331,3 +1331,5 @@ field_updated_by: Updated by field_last_updated_by: Last updated by field_full_width_layout: Full width layout + label_ascending_nulls_last: По возрастанию с пустыми значениями в конце + label_descending_nulls_first: По убыванию с пустыми значениями в начале \ No newline at end of file Index: lib/redmine/database.rb IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lib/redmine/database.rb (revision e20adb0532f07510320b118df2c2e53efafd294e) +++ lib/redmine/database.rb (revision c0ef6de058bb128515d1a4085c592da75e82188b) @@ -49,6 +49,14 @@ (ActiveRecord::Base.connection.adapter_name =~ /mysql/i).present? end + def sqlite3? + (ActiveRecord::Base.connection.adapter_name =~ /sqlite/i).present? + end + + def sqlserver? + (ActiveRecord::Base.connection.adapter_name =~ /sqlserver/i).present? + end + # Returns a SQL statement for case/accent (if possible) insensitive match def like(left, right, options={}) neg = (options[:match] == false ? 'NOT ' : '') @@ -64,6 +72,10 @@ end end + def firebird? + (ActiveRecord::Base.connection.adapter_name =~ /firebird/i).present? + end + # Resets database information def reset @postgresql_unaccent = nil Index: app/helpers/sort_helper.rb IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- app/helpers/sort_helper.rb (revision e20adb0532f07510320b118df2c2e53efafd294e) +++ app/helpers/sort_helper.rb (revision c0ef6de058bb128515d1a4085c592da75e82188b) @@ -61,13 +61,13 @@ def available_criteria=(criteria) unless criteria.is_a?(Hash) - criteria = criteria.inject({}) {|h,k| h[k] = k; h} + criteria = criteria.inject({}) { |h, k| h[k] = k; h } end @available_criteria = criteria end def from_param(param) - @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]} + @criteria = param.to_s.split(',').collect { |s| s.split(':')[0..1] } normalize! end @@ -77,15 +77,15 @@ end def to_param - @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',') + @criteria.collect { |k, o| k + ":#{o.to_s}" }.join(',') end # Returns an array of SQL fragments used to sort the list def to_sql - sql = @criteria.collect do |k,o| + sql = @criteria.collect do |k, o| if s = @available_criteria[k] s = [s] unless s.is_a?(Array) - s.collect {|c| append_order(c, o ? "ASC" : "DESC")} + s.collect { |c| append_order(c, o.to_s) } end end.flatten.compact sql.blank? ? nil : sql @@ -96,7 +96,7 @@ end def add!(key, asc) - @criteria.delete_if {|k,o| k == key} + @criteria.delete_if { |k, o| k == key } @criteria = [[key, asc]] + @criteria normalize! end @@ -112,7 +112,7 @@ end def first_asc? - @criteria.first && @criteria.first.last + @criteria.first && @criteria.first.last.to_s.downcase.start_with?("asc") end def empty? @@ -123,8 +123,8 @@ def normalize! @criteria ||= [] - @criteria = @criteria.collect {|s| s = Array(s); [s.first, (s.last == false || s.last == 'desc') ? false : true]} - @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria + @criteria = @criteria.collect { |s| s = Array(s); [s.first, s[1].nil? ? "asc" : s.last] } + @criteria = @criteria.select { |k, o| @available_criteria.has_key?(k) } if @available_criteria @criteria.slice!(3) self end @@ -134,7 +134,20 @@ if criterion =~ / (asc|desc)$/i criterion else - "#{criterion} #{order}" + if (order =~ /nulls/i).present? + t = order.to_s.split(' ') + sort_order = t.first + nulls_last = t.last.eql?("last") + if Redmine::Database.mysql? + "ISNULL(#{criterion}) #{nulls_last ? "ASC" : "DESC"}, #{criterion} #{sort_order}" + elsif Redmine::Database.sqlserver? || Redmine::Database.sqlite3? + "#{criterion} IS NULL #{nulls_last ? "ASC" : "DESC"}, #{criterion} #{sort_order}" + else + "#{criterion} #{order}" + end + else + "#{criterion} #{order}" + end end end @@ -158,12 +171,12 @@ # def sort_init(*args) case args.size - when 1 - @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]] - when 2 - @sort_default = [[args.first, args.last]] - else - raise ArgumentError + when 1 + @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]] + when 2 + @sort_default = [[args.first, args.last]] + else + raise ArgumentError end end @@ -217,7 +230,7 @@ end caption = column.to_s.humanize unless caption - sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param } + sort_options = {:sort => @sort_criteria.add(column.to_s, order).to_param} link_to(caption, {:params => request.query_parameters.merge(sort_options)}, :class => css) end Index: lib/redmine/field_format.rb IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lib/redmine/field_format.rb (revision e20adb0532f07510320b118df2c2e53efafd294e) +++ lib/redmine/field_format.rb (revision c0ef6de058bb128515d1a4085c592da75e82188b) @@ -293,8 +293,7 @@ # objects by their value of the custom field. # Returns nil if the custom field can not be used for sorting. def order_statement(custom_field) - # COALESCE is here to make sure that blank and NULL values are sorted equally - "COALESCE(#{join_alias custom_field}.value, '')" + "#{join_alias custom_field}.value" #COALESCE removed to allow null placement on order end # Returns a GROUP BY clause that can used to group by custom value Index: app/views/queries/_form.html.erb IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- app/views/queries/_form.html.erb (revision e20adb0532f07510320b118df2c2e53efafd294e) +++ app/views/queries/_form.html.erb (revision c0ef6de058bb128515d1a4085c592da75e82188b) @@ -63,7 +63,7 @@ <%= label_tag "query_sort_criteria_direction_" + i.to_s, l(:description_query_sort_criteria_direction), :class => "hidden-for-sighted" %> <%= select_tag("query[sort_criteria][#{i}][]", - options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i)), + options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc'], [l(:label_ascending_nulls_last), 'asc nulls last'], [l(:label_descending_nulls_first), 'desc nulls first']], @query.sort_criteria_order(i)), :id => "query_sort_criteria_direction_" + i.to_s) %>
<% end %>