Repository: adamchainz/django-mysql

Issue:
Query hint methods error if DJANGO_MYSQL_REWRITE_QUERIES isn't set
Summary: As seen in #305 it's not obvious why `force_index` etc. aren't working when you try to use them without the setting on. They should error if it's not set rather than lead to confusion.



Discussion:

Resolving patch:
diff --git a/django_mysql/models/query.py b/django_mysql/models/query.py
--- a/django_mysql/models/query.py
+++ b/django_mysql/models/query.py
@@ -5,8 +5,10 @@
 import sys
 import time
 from copy import copy
+from functools import wraps
 from subprocess import PIPE, Popen
 
+from django.conf import settings
 from django.db import connections, models
 from django.db.models.sql.where import ExtraWhere
 from django.db.transaction import atomic
@@ -24,6 +26,18 @@
 )
 
 
+def requires_query_rewrite(func):
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        if not settings.DJANGO_MYSQL_REWRITE_QUERIES:
+            raise RuntimeError(
+                "You need to set DJANGO_MYSQL_REWRITE_QUERIES = True in your "
+                "settings to use query hints."
+            )
+        return func(*args, **kwargs)
+    return wrapper
+
+
 class QuerySetMixin(object):
 
     def __init__(self, *args, **kwargs):
@@ -88,6 +102,7 @@ def approx_count(self, fall_back=True, return_approx_int=True,
     # These specially constructed comments will be rewritten by rewrite_query
     # into actual hints
 
+    @requires_query_rewrite
     def label(self, string):
         """
         Adds an arbitrary user-defined comment that will appear after
@@ -98,24 +113,31 @@ def label(self, string):
             raise ValueError("Bad label - cannot be embedded in SQL comment")
         return self.extra(where=["/*QueryRewrite':label={}*/1".format(string)])
 
+    @requires_query_rewrite
     def straight_join(self):
         return self.extra(where=["/*QueryRewrite':STRAIGHT_JOIN*/1"])
 
+    @requires_query_rewrite
     def sql_small_result(self):
         return self.extra(where=["/*QueryRewrite':SQL_SMALL_RESULT*/1"])
 
+    @requires_query_rewrite
     def sql_big_result(self):
         return self.extra(where=["/*QueryRewrite':SQL_BIG_RESULT*/1"])
 
+    @requires_query_rewrite
     def sql_buffer_result(self):
         return self.extra(where=["/*QueryRewrite':SQL_BUFFER_RESULT*/1"])
 
+    @requires_query_rewrite
     def sql_cache(self):
         return self.extra(where=["/*QueryRewrite':SQL_CACHE*/1"])
 
+    @requires_query_rewrite
     def sql_no_cache(self):
         return self.extra(where=["/*QueryRewrite':SQL_NO_CACHE*/1"])
 
+    @requires_query_rewrite
     def sql_calc_found_rows(self):
         qs = self.extra(where=["/*QueryRewrite':SQL_CALC_FOUND_ROWS*/1"])
         qs._found_rows = None
@@ -154,6 +176,7 @@ def ignore_index(self, *index_names, **kwargs):
         kwargs['hint'] = 'IGNORE'
         return self._index_hint(*index_names, **kwargs)
 
+    @requires_query_rewrite
     def _index_hint(self, *index_names, **kwargs):
         hint = kwargs.pop('hint')
         table_name = kwargs.pop('table_name', None)


Test patch:
diff --git a/tests/testapp/test_models.py b/tests/testapp/test_models.py
--- a/tests/testapp/test_models.py
+++ b/tests/testapp/test_models.py
@@ -5,6 +5,7 @@
 from unittest import skipUnless
 
 import pytest
+import six
 from django.contrib.auth.models import User
 from django.db.models.query import QuerySet
 from django.template import Context, Template
@@ -169,11 +170,11 @@ def test_straight_join_with_distinct(self):
         assert cap.query.startswith("SELECT DISTINCT STRAIGHT_JOIN ")
 
     @override_settings(DJANGO_MYSQL_REWRITE_QUERIES=False)
-    def test_can_disable_setting(self):
-        with CaptureLastQuery() as cap:
-            list(Author.objects.all().straight_join())
+    def test_disabled_setting_errors_on_usage(self):
+        with pytest.raises(RuntimeError) as excinfo:
+            Author.objects.all().straight_join()
 
-        assert not cap.query.startswith("SELECT STRAIGHT_JOIN ")
+        assert 'DJANGO_MYSQL_REWRITE_QUERIES' in six.text_type(excinfo.value)
 
     def test_sql_cache(self):
         with CaptureLastQuery() as cap: