Repository: getredash/redash

Issue:
Case insensitive parameters in Redash query result cache
### Issue Summary

Redash may display wrong results for query with case sensitive parameter. It looks like results cache is ignoring param letters size.

### Steps to Reproduce

1. Create a query with global text parameter.
2. Create dashboard with visualization for this query.
3. Open dashboard with parameter set to lowercase letter for example 'test'
4. Open dashboard on a new tab with parameter 'TEST'. 

In the last step you should receive visualization for parameter 'test' even when parameter value is set to 'TEST'

I have been able to reproduce this scenario on demo.redash.io. To reproduce do:

1. Open link: http://demo.redash.io/dashboard/ma_dashboard?p_dashboard_name=test
You should see rows from dashboard table with name `test`
2. Open link: http://demo.redash.io/dashboard/ma_dashboard?p_dashboard_name=Test
You should see rows from dashboard table with name `test` even when parameter `dashboard_name` is set to `Test`. After refreshing visualization result changed to proper one.

### Technical details:

* Redash Version: 3.0.0+b3134
* Browser/OS: Chrome/Firefox/Opera
* How did you install Redash: used demo.redash.io



Discussion:
The problem is in the [`redash.utils.gen_query_hash`](https://github.com/getredash/redash/blob/073db37cfda27a33570339b6f4e34d69ff6032bf/redash/utils/__init__.py#L47-L57) method. And it's actually documented there as a TODO item 😳 

I think we can stop lower casing the query in this method. We don't need to dedup that aggressively. 
Hi, I've looked around at the code, reproduced the error and I think this can be my first PR.
@arikfr your suggestion is to remove the `.lower()` when we're creating the hash?
@ClaudioDavi yes, but also need a migration to recalculate the query hash for all existing queries.
Ok, thanks @arikfr ! Do you have any migration examples for me to have a look and follow through?

Resolving patch:
diff --git a/migrations/versions/1038c2174f5d_make_case_insensitive_hash_of_query_text.py b/migrations/versions/1038c2174f5d_make_case_insensitive_hash_of_query_text.py
new file mode 100644
--- /dev/null
+++ b/migrations/versions/1038c2174f5d_make_case_insensitive_hash_of_query_text.py
@@ -0,0 +1,51 @@
+"""Make case insensitive hash of query text
+
+Revision ID: 1038c2174f5d
+Revises: fd4fc850d7ea
+Create Date: 2023-07-16 23:10:12.885949
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.sql import table
+
+from redash.utils import gen_query_hash
+
+# revision identifiers, used by Alembic.
+revision = '1038c2174f5d'
+down_revision = 'fd4fc850d7ea'
+branch_labels = None
+depends_on = None
+
+
+
+def change_query_hash(conn, table, query_text_to):
+    for record in conn.execute(table.select()):
+        query_text = query_text_to(record.query)
+        conn.execute(
+            table
+            .update()
+            .where(table.c.id == record.id)
+            .values(query_hash=gen_query_hash(query_text)))
+
+
+def upgrade():
+    queries = table(
+        'queries',
+        sa.Column('id', sa.Integer, primary_key=True),
+        sa.Column('query', sa.Text),
+        sa.Column('query_hash', sa.String(length=10)))
+
+    conn = op.get_bind()
+    change_query_hash(conn, queries, query_text_to=str)
+
+
+def downgrade():
+    queries = table(
+        'queries',
+        sa.Column('id', sa.Integer, primary_key=True),
+        sa.Column('query', sa.Text),
+        sa.Column('query_hash', sa.String(length=10)))
+
+    conn = op.get_bind()
+    change_query_hash(conn, queries, query_text_to=str.lower)
diff --git a/redash/utils/__init__.py b/redash/utils/__init__.py
--- a/redash/utils/__init__.py
+++ b/redash/utils/__init__.py
@@ -51,14 +51,14 @@ def slugify(s):
 
 def gen_query_hash(sql):
     """Return hash of the given query after stripping all comments, line breaks
-    and multiple spaces, and lower casing all text.
+    and multiple spaces.
 
-    TODO: possible issue - the following queries will get the same id:
+    The following queries will get different ids:
         1. SELECT 1 FROM table WHERE column='Value';
         2. SELECT 1 FROM table where column='value';
     """
     sql = COMMENTS_REGEX.sub("", sql)
-    sql = "".join(sql.split()).lower()
+    sql = "".join(sql.split())
     return hashlib.md5(sql.encode("utf-8")).hexdigest()
 
 


Test patch: