Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot list sessions for a given reviewer #191

Closed
hugocorbucci opened this issue Jun 29, 2015 · 9 comments
Closed

Cannot list sessions for a given reviewer #191

hugocorbucci opened this issue Jun 29, 2015 · 9 comments
Assignees

Comments

@hugocorbucci
Copy link
Member

After fixing #190, I discovered that listing the sessions that a given reviewer can review is no longer working.
This has not been caught by automated tests because sqlite3 doesn't apply the rules regarding group by and having clauses of SQL like MySQL does.
MySQL (https://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html) doesn't allow a non-aggregated column (such as session.final_reviews_count) to be references in either a group_by or a having clause unless aliased somewhere else.

As a result, as soon as the reviewer_sessions page starts loading, the db query is performed and errors out with the following error:

ActionView::Template::Error (Mysql2::Error: Unknown column 'sessions.final_reviews_count' in 'having clause': SELECT COUNT(*) AS count_all, sessions.id AS sessions_id FROM `sessions` LEFT OUTER JOIN reviews ON sessions.id = reviews.session_id AND reviews.type = 'FinalReview' WHERE `sessions`.`conference_id` = 6 AND (`sessions`.`state` NOT IN ('cancelled')) AND (author_id <> 1 AND (second_author_id IS NULL OR second_author_id <> 1)) AND ((track_id = 26 AND audience_level_id <= 16) OR (track_id = 27 AND audience_level_id <= 17) OR (track_id = 28 AND audience_level_id <= 18) OR (track_id = 29 AND audience_level_id <= 19) OR (track_id = 30 AND audience_level_id <= NULL) OR (track_id = 31 AND audience_level_id <= NULL)) AND (reviews.reviewer_id IS NULL OR reviews.reviewer_id <> 1) AND (final_reviews_count < 3) GROUP BY sessions.id HAVING count(reviews.id) = sessions.final_reviews_count):
    1: - title t('actions.reviewer_sessions', count: @sessions.total_entries)

The SQL fix is simple:

SELECT COUNT(*) AS count_all, sessions.id AS sessions_id, sessions.final_reviews_count AS sessions_reviews_count FROM `sessions` LEFT OUTER JOIN reviews ON sessions.id = reviews.session_id AND reviews.type = 'FinalReview' WHERE `sessions`.`conference_id` = 6 AND (`sessions`.`state` NOT IN ('cancelled')) AND (author_id <> 1 AND (second_author_id IS NULL OR second_author_id <> 1)) AND ((track_id = 26 AND audience_level_id <= 16) OR (track_id = 27 AND audience_level_id <= 17) OR (track_id = 28 AND audience_level_id <= 18) OR (track_id = 29 AND audience_level_id <= 19) OR (track_id = 30 AND audience_level_id <= NULL) OR (track_id = 31 AND audience_level_id <= NULL)) AND (reviews.reviewer_id IS NULL OR reviews.reviewer_id <> 1) AND (final_reviews_count < 3) GROUP BY sessions.id HAVING count(reviews.id) = sessions_reviews_count);

(Note the extra AS for reviews count at the select level)

Unfortunately, ActiveRecord builds the count call in https://github.com/rails/rails/blob/b88220732dffe336a0814bb91a4f5acc6e91e508/activerecord/lib/active_record/relation/calculations.rb#L300. When it does so, the only AS fields that are gathered are the ones of the group_by clause.

So we either have to find a way to change that query to remove the group_by and having clauses when counting (unlikely) or add the extra aliasing without changing the results.

This is likely an issue introduced by #114 as Rails 4 did a bunch of optimizing with the Arel queries which likely included simplifying the count queries.

@lcobucci
Copy link

@hugocorbucci I really don't know rails so well like you and others, but if the AR querying API is limiting a little bit we could introduce a view on the database and map it to a new model as a temporary workaround until finding a permanent solution.

It is really not pretty, but it may be a fast way to solve the problem.

@raphaelmolesim
Copy link
Contributor

@lcobucci it would be possible, but there is a lot of logic that build this query, that would has to be moved into a view or procedure and It would consume a lot of effort as well and the final solution would be terrible.

I tried a fix in PR 192 => #192

@hugocorbucci
Copy link
Member Author

#192 makes the count of the number of sessions to review wrong (it combines the groups).
As a result, pagination doesn't work either which serious limits how many can be reviewed.

@lcobucci
Copy link

As I said before, I'm don't know ruby nor rails, but I don't see why something like this would be so terrible (as an attachable and easy to remove workaround):

CREATE VIEW session_for_review AS
SELECT
    sessions.id AS sessions_id,
    COUNT(*) AS count_all,
    sessions.final_reviews_count,
    sessions.conference_id,
    sessions.state,
    sessions.author_id,
    sessions.second_author_id,
    sessions.track_id,
    sessions.audience_level_id,
    sessions.created_at,
    reviews.reviewer_id,
    reviews.type,
    COUNT(reviews.id) AS matching_reviews
FROM sessions
LEFT OUTER JOIN reviews
    ON sessions.id = reviews.session_id
GROUP BY sessions.id
HAVING matching_reviews = final_reviews_count;
class SessionForReview < ActiveRecord::Base
  # Mapping information and other things...

  scope :for_conference, ->(conference) { where(conference_id: conference.id)}
  scope :not_author, ->(u) {
    where('author_id <> ? AND (second_author_id IS NULL OR second_author_id <> ?)', u.to_i, u.to_i)
  }

  scope :with_incomplete_final_reviews, -> { where('final_reviews_count < ?', 3) }

  scope :submitted_before, ->(date) { where('created_at <= ?', date) }

  scope :not_reviewed_by, ->(user, review_type) {
    where('(reviewer_id IS NULL OR reviewer_id <> ?) AND type = ?', user.id, review_type)
  }

  scope :for_preferences, ->(*preferences) {
    return none if preferences.empty?
    clause = preferences.map { |p| "(track_id = ? AND audience_level_id <= ?)" }.join(" OR ")
    args = preferences.map {|p| [p.track_id, p.audience_level_id]}.flatten
    where(clause, *args)
  }

  def self.for_reviewer(user, conference)
    sessions = for_review_in(conference).
      not_author(user.id).
      for_preferences(*user.preferences(conference)).
      not_reviewed_by(user, conference.in_early_review_phase? ? 'EarlyReview' : 'FinalReview')
    if conference.in_final_review_phase?
      sessions.with_incomplete_final_reviews
    else
      sessions
    end
  end

  def self.for_review_in(conference)
    sessions = for_conference(conference).without_state(:cancelled) # I don't if this works =P
    if conference.in_early_review_phase?
      sessions.submitted_before(conference.presubmissions_deadline + 3.hours)
    else
      sessions
    end
  end
end

I'm cloning the repo and will try something like this.

@lcobucci
Copy link

Sorry guys, I won't be able to try that approach. I can't provision the bundled VM =/
It goes well until the provision start: https://gist.github.com/lcobucci/d1623c04bca7f14f1701

Anyway... good luck there 😄

hugocorbucci pushed a commit that referenced this issue Jun 30, 2015
This one handles both the count and the actual usage so that pagination
works. Added tests that break for mysql.
@hugocorbucci
Copy link
Member Author

@lcobucci Mainly this is pretty hard to implement because of the coupling ActiveRecord imposes between your database and your code. To use your view, I'd have to identify anything that uses sessions in those contexts.

@hugocorbucci
Copy link
Member Author

@lcobucci Having said that, I have no idea why your VM is not provisioning correctly. I'd have to find that out.

@hugocorbucci
Copy link
Member Author

Ok! Seems like 337ac17 actually fixes both the pagination as well as the count and the rest of the stuff.
So I'm closing this up for now. We'll reopen if we have new problems.

Regarding the VM, @lcobucci it'd be better to open another issue with details.

@lcobucci
Copy link

lcobucci commented Jul 1, 2015

Perfect @hugocorbucci! I'm glad that the issue is solved.
Sorry for being persistent on the suggestion.

I'll open an issue for the VM with more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants