sql-data-guard 0.0.51__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {sql_data_guard-0.0.51/src/sql_data_guard.egg-info → sql_data_guard-0.1.3}/PKG-INFO +8 -6
  2. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/README.md +7 -5
  3. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/pyproject.toml +1 -1
  4. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/restriction_validation.py +0 -1
  5. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/restriction_verification.py +1 -1
  6. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/sql_data_guard.py +11 -3
  7. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3/src/sql_data_guard.egg-info}/PKG-INFO +8 -6
  8. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_sql_guard_curr_unit.py +19 -9
  9. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_sql_guard_unit.py +9 -3
  10. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_sql_guard_updates_unit.py +9 -7
  11. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/LICENSE.md +0 -0
  12. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/setup.cfg +0 -0
  13. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/__init__.py +0 -0
  14. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/rest/__init__.py +0 -0
  15. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/rest/sql_data_guard_rest.py +0 -0
  16. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/verification_context.py +0 -0
  17. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard/verification_utils.py +0 -0
  18. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard.egg-info/SOURCES.txt +0 -0
  19. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard.egg-info/dependency_links.txt +0 -0
  20. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard.egg-info/requires.txt +0 -0
  21. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/src/sql_data_guard.egg-info/top_level.txt +0 -0
  22. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_duckdb_unit.py +0 -0
  23. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_rest_api_unit.py +0 -0
  24. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_sql_guard_joins_unit.py +0 -0
  25. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_sql_guard_llm.py +0 -0
  26. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_sql_guard_validation_unit.py +0 -0
  27. {sql_data_guard-0.0.51 → sql_data_guard-0.1.3}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-data-guard
3
- Version: 0.0.51
3
+ Version: 0.1.3
4
4
  Summary: Safety Layer for LLM Database Interactions
5
5
  Author-email: Imperva - Threat Reseach Infra <ww.dis.imperva.threat-research-infra@thalesgroup.com>
6
6
  License: MIT License
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
  # sql-data-guard: Safety Layer for LLM Database Interactions
27
27
 
28
28
  <div style="text-align: center;">
29
- <img alt="SQL Data Guard logo" src="https://github.com/ThalesGroup/sql-data-guard/blob/main/sql-data-guard-logo.png" width="300"/>
29
+ <img alt="SQL Data Guard logo" src="https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/sql-data-guard-logo.png" width="300"/>
30
30
  </div>
31
31
 
32
32
  SQL is the go-to language for performing queries on databases, and for a good reason - it’s well known, easy to use and pretty simple. However, it seems that it’s as easy to use as it is to exploit, and SQL injection is still one of the most targeted vulnerabilities - especially nowadays with the proliferation of “natural language queries” harnessing Large Language Models (LLMs) power to generate and run SQL queries.
@@ -93,6 +93,9 @@ Output:
93
93
  "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"
94
94
  }
95
95
  ```
96
+ For more details on restriction rules and validation, see the [manual.](docs/manual.md)
97
+
98
+
96
99
  Here is a table with more examples of SQL queries and their corresponding JSON outputs:
97
100
 
98
101
  | SQL Query | JSON Output |
@@ -102,8 +105,6 @@ Here is a table with more examples of SQL queries and their corresponding JSON o
102
105
  | SELECT id, col FROM orders WHERE account_id = 123 | { "allowed": false, "errors": ["Column col is not allowed. Column removed from SELECT clause"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } ``` |
103
106
  | SELECT id FROM orders WHERE account_id = 123 OR 1 = 1 | { "allowed": false, "errors": ["Always-True expression is not allowed"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } |
104
107
  |SELECT * FROM orders WHERE account_id = 123| {"allowed": false, "errors": ["SELECT * is not allowed"], "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"} |
105
- ```
106
-
107
108
 
108
109
  This table provides a variety of SQL queries and their corresponding JSON outputs, demonstrating how `sql-data-guard` handles different scenarios.
109
110
 
@@ -124,6 +125,7 @@ To run the sql-data-guard Docker container, use the following command:
124
125
 
125
126
  ```bash
126
127
  docker run -d --name sql-data-guard -p 5000:5000 ghcr.io/thalesgroup/sql-data-guard
128
+ ```
127
129
 
128
130
  ### Calling the Docker Container Using REST API
129
131
 
@@ -147,7 +149,7 @@ curl -X POST http://localhost:5000/verify-sql \
147
149
  ```
148
150
 
149
151
  ## Contributing
150
- We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
152
+ We welcome contributions! Please see our [CONTRIBUTING.md](https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/CONTRIBUTING.md) for more details.
151
153
 
152
154
  ## License
153
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
155
+ This project is licensed under the MIT License. See the [LICENSE.md](https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/LICENSE.md) file for details.
@@ -2,7 +2,7 @@
2
2
  # sql-data-guard: Safety Layer for LLM Database Interactions
3
3
 
4
4
  <div style="text-align: center;">
5
- <img alt="SQL Data Guard logo" src="https://github.com/ThalesGroup/sql-data-guard/blob/main/sql-data-guard-logo.png" width="300"/>
5
+ <img alt="SQL Data Guard logo" src="https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/sql-data-guard-logo.png" width="300"/>
6
6
  </div>
7
7
 
8
8
  SQL is the go-to language for performing queries on databases, and for a good reason - it’s well known, easy to use and pretty simple. However, it seems that it’s as easy to use as it is to exploit, and SQL injection is still one of the most targeted vulnerabilities - especially nowadays with the proliferation of “natural language queries” harnessing Large Language Models (LLMs) power to generate and run SQL queries.
@@ -69,6 +69,9 @@ Output:
69
69
  "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"
70
70
  }
71
71
  ```
72
+ For more details on restriction rules and validation, see the [manual.](docs/manual.md)
73
+
74
+
72
75
  Here is a table with more examples of SQL queries and their corresponding JSON outputs:
73
76
 
74
77
  | SQL Query | JSON Output |
@@ -78,8 +81,6 @@ Here is a table with more examples of SQL queries and their corresponding JSON o
78
81
  | SELECT id, col FROM orders WHERE account_id = 123 | { "allowed": false, "errors": ["Column col is not allowed. Column removed from SELECT clause"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } ``` |
79
82
  | SELECT id FROM orders WHERE account_id = 123 OR 1 = 1 | { "allowed": false, "errors": ["Always-True expression is not allowed"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } |
80
83
  |SELECT * FROM orders WHERE account_id = 123| {"allowed": false, "errors": ["SELECT * is not allowed"], "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"} |
81
- ```
82
-
83
84
 
84
85
  This table provides a variety of SQL queries and their corresponding JSON outputs, demonstrating how `sql-data-guard` handles different scenarios.
85
86
 
@@ -100,6 +101,7 @@ To run the sql-data-guard Docker container, use the following command:
100
101
 
101
102
  ```bash
102
103
  docker run -d --name sql-data-guard -p 5000:5000 ghcr.io/thalesgroup/sql-data-guard
104
+ ```
103
105
 
104
106
  ### Calling the Docker Container Using REST API
105
107
 
@@ -123,7 +125,7 @@ curl -X POST http://localhost:5000/verify-sql \
123
125
  ```
124
126
 
125
127
  ## Contributing
126
- We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
128
+ We welcome contributions! Please see our [CONTRIBUTING.md](https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/CONTRIBUTING.md) for more details.
127
129
 
128
130
  ## License
129
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
131
+ This project is licensed under the MIT License. See the [LICENSE.md](https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/LICENSE.md) file for details.
@@ -9,7 +9,7 @@ package-dir = {"" = "src"}
9
9
  extra-index-url = ["https://pypi.org/simple"]
10
10
  [project]
11
11
  name = "sql-data-guard"
12
- version = "0.0.51"
12
+ version = "0.1.3"
13
13
  dependencies = [
14
14
  "sqlglot"
15
15
  ]
@@ -19,7 +19,6 @@ def validate_restrictions(config: dict):
19
19
  "<",
20
20
  ">=",
21
21
  "<=",
22
- "!=",
23
22
  "BETWEEN",
24
23
  "IN",
25
24
  ] # Allowed operations
@@ -70,7 +70,7 @@ def _create_new_condition(
70
70
  """
71
71
  if restriction.get("operation") == "BETWEEN":
72
72
  operator = "BETWEEN"
73
- operand = f"{_format_value(restriction["values"][0])} AND {_format_value(restriction["values"][1])}"
73
+ operand = f"{_format_value(restriction['values'][0])} AND {_format_value(restriction['values'][1])}"
74
74
  elif restriction.get("operation") == "IN":
75
75
  operator = "IN"
76
76
  values = restriction.get("values", [restriction.get("value")])
@@ -124,6 +124,10 @@ def _has_static_expression(context: VerificationContext, exp: expr.Expression) -
124
124
 
125
125
 
126
126
  def _verify_query_statement(query_statement: expr.Query, context: VerificationContext):
127
+ for select in query_statement.selects:
128
+ for sub in select.find_all(expr.Subquery):
129
+ _add_table_alias(sub, context)
130
+ _verify_query_statement(sub.this, context)
127
131
  if isinstance(query_statement, expr.Union):
128
132
  _verify_query_statement(query_statement.left, context)
129
133
  _verify_query_statement(query_statement.right, context)
@@ -131,6 +135,10 @@ def _verify_query_statement(query_statement: expr.Query, context: VerificationCo
131
135
  for cte in query_statement.ctes:
132
136
  _add_table_alias(cte, context)
133
137
  _verify_query_statement(cte.this, context)
138
+ where_clause = query_statement.find(expr.Where)
139
+ if where_clause:
140
+ for sub in where_clause.find_all(expr.Subquery):
141
+ _verify_query_statement(sub.this, context)
134
142
  from_tables = _get_from_clause_tables(query_statement, context)
135
143
  for t in from_tables:
136
144
  found = False
@@ -247,8 +255,8 @@ def _get_from_clause_tables(
247
255
  """
248
256
  result = []
249
257
  from_clause = select_clause.find(expr.From)
250
- join_clause = select_clause.find(expr.Join)
251
- for clause in [from_clause, join_clause]:
258
+ join_clauses = select_clause.args.get("joins", [])
259
+ for clause in [from_clause] + join_clauses:
252
260
  if clause:
253
261
  for t in find_direct(clause, expr.Table):
254
262
  if isinstance(t, expr.Table):
@@ -256,7 +264,7 @@ def _get_from_clause_tables(
256
264
  for l in find_direct(clause, expr.Subquery):
257
265
  _add_table_alias(l, context)
258
266
  _verify_query_statement(l.this, context)
259
- if join_clause:
267
+ for join_clause in join_clauses:
260
268
  for l in find_direct(join_clause, expr.Lateral):
261
269
  _add_table_alias(l, context)
262
270
  _verify_query_statement(l.this.find(expr.Select), context)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sql-data-guard
3
- Version: 0.0.51
3
+ Version: 0.1.3
4
4
  Summary: Safety Layer for LLM Database Interactions
5
5
  Author-email: Imperva - Threat Reseach Infra <ww.dis.imperva.threat-research-infra@thalesgroup.com>
6
6
  License: MIT License
@@ -26,7 +26,7 @@ Dynamic: license-file
26
26
  # sql-data-guard: Safety Layer for LLM Database Interactions
27
27
 
28
28
  <div style="text-align: center;">
29
- <img alt="SQL Data Guard logo" src="https://github.com/ThalesGroup/sql-data-guard/blob/main/sql-data-guard-logo.png" width="300"/>
29
+ <img alt="SQL Data Guard logo" src="https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/sql-data-guard-logo.png" width="300"/>
30
30
  </div>
31
31
 
32
32
  SQL is the go-to language for performing queries on databases, and for a good reason - it’s well known, easy to use and pretty simple. However, it seems that it’s as easy to use as it is to exploit, and SQL injection is still one of the most targeted vulnerabilities - especially nowadays with the proliferation of “natural language queries” harnessing Large Language Models (LLMs) power to generate and run SQL queries.
@@ -93,6 +93,9 @@ Output:
93
93
  "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"
94
94
  }
95
95
  ```
96
+ For more details on restriction rules and validation, see the [manual.](docs/manual.md)
97
+
98
+
96
99
  Here is a table with more examples of SQL queries and their corresponding JSON outputs:
97
100
 
98
101
  | SQL Query | JSON Output |
@@ -102,8 +105,6 @@ Here is a table with more examples of SQL queries and their corresponding JSON o
102
105
  | SELECT id, col FROM orders WHERE account_id = 123 | { "allowed": false, "errors": ["Column col is not allowed. Column removed from SELECT clause"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } ``` |
103
106
  | SELECT id FROM orders WHERE account_id = 123 OR 1 = 1 | { "allowed": false, "errors": ["Always-True expression is not allowed"], "fixed": "SELECT id FROM orders WHERE account_id = 123" } |
104
107
  |SELECT * FROM orders WHERE account_id = 123| {"allowed": false, "errors": ["SELECT * is not allowed"], "fixed": "SELECT id, product_name, account_id FROM orders WHERE account_id = 123"} |
105
- ```
106
-
107
108
 
108
109
  This table provides a variety of SQL queries and their corresponding JSON outputs, demonstrating how `sql-data-guard` handles different scenarios.
109
110
 
@@ -124,6 +125,7 @@ To run the sql-data-guard Docker container, use the following command:
124
125
 
125
126
  ```bash
126
127
  docker run -d --name sql-data-guard -p 5000:5000 ghcr.io/thalesgroup/sql-data-guard
128
+ ```
127
129
 
128
130
  ### Calling the Docker Container Using REST API
129
131
 
@@ -147,7 +149,7 @@ curl -X POST http://localhost:5000/verify-sql \
147
149
  ```
148
150
 
149
151
  ## Contributing
150
- We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
152
+ We welcome contributions! Please see our [CONTRIBUTING.md](https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/CONTRIBUTING.md) for more details.
151
153
 
152
154
  ## License
153
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
155
+ This project is licensed under the MIT License. See the [LICENSE.md](https://raw.githubusercontent.com/ThalesGroup/sql-data-guard/main/LICENSE.md) file for details.
@@ -534,11 +534,19 @@ class TestSQLOrderDateBetweenRestrictions:
534
534
 
535
535
  def test_price_between_valid(self, cnn, config):
536
536
  verify_sql_test(
537
- "SELECT prod_id, prod_name, price"
538
- "FROM products WHERE price BETWEEN 80 AND 150",
537
+ "SELECT prod_id, prod_name, price FROM products WHERE price BETWEEN 80 AND 150",
539
538
  config,
540
539
  cnn=cnn,
541
- data=[],
540
+ errors={
541
+ "Missing restriction for table: products column: price value: [80, 150]"
542
+ },
543
+ fix="SELECT prod_id, prod_name, price FROM products WHERE (price BETWEEN 80 AND "
544
+ "150) AND price BETWEEN 80 AND 150",
545
+ data=[
546
+ (1, "Product A", 120.0),
547
+ (2, "Product B", 80.0),
548
+ (3, "Product C", 150.0),
549
+ ],
542
550
  )
543
551
 
544
552
  def test_price_not_between_restriction(self, cnn, config):
@@ -572,17 +580,19 @@ class TestSQLOrderDateBetweenRestrictions:
572
580
 
573
581
  def test_left_join_products_with_orders(self, cnn, config):
574
582
  verify_sql_test(
575
- "SELECT p.prod_name, o.order_id, COALESCE(o.quantity, 0) AS quantity "
576
- "FROM products p "
577
- "LEFT JOIN orders o ON prod_id = prod_id "
578
- "WHERE p.price BETWEEN 80 AND 150",
583
+ """SELECT p.prod_name, o.order_id, COALESCE(o.quantity, 0) AS quantity
584
+ FROM products p
585
+ LEFT JOIN orders o ON p.prod_id = o.prod_id
586
+ WHERE p.price BETWEEN 80 AND 150""",
579
587
  config,
580
588
  errors={
581
589
  "Missing restriction for table: products column: p.price value: [80, 150]"
582
590
  },
583
- fix="SELECT p.prod_name, o.order_id, COALESCE(o.quantity, 0) AS quantity FROM products AS p LEFT JOIN orders AS o ON prod_id = prod_id WHERE (p.price BETWEEN 80 AND 150) AND p.price BETWEEN 80 AND 150",
591
+ fix="SELECT p.prod_name, o.order_id, COALESCE(o.quantity, 0) AS quantity FROM "
592
+ "products AS p LEFT JOIN orders AS o ON p.prod_id = o.prod_id WHERE (p.price "
593
+ "BETWEEN 80 AND 150) AND p.price BETWEEN 80 AND 150",
584
594
  cnn=cnn,
585
- data=[],
595
+ data=[("Product A", 1, 10), ("Product B", 2, 5), ("Product C", 3, 7)],
586
596
  )
587
597
 
588
598
  def test_select_products_below_price_restriction(self, cnn, config):
@@ -252,6 +252,7 @@ INSERT INTO orders (order_id, account_id, product_id) VALUES
252
252
  COUNT(o.order_id) AS order_count
253
253
  FROM orders o
254
254
  JOIN products p ON o.product_id = p.product_id
255
+ WHERE o.account_id = 123
255
256
  GROUP BY o.account_id, p.product_name
256
257
  ),
257
258
  RankedProducts AS (
@@ -273,11 +274,16 @@ FROM OrderCounts oc
273
274
  JOIN RankedProducts rp ON oc.product_name = rp.product_name
274
275
  WHERE oc.account_id IN (
275
276
  -- Filter accounts with at least 2 orders
276
- SELECT account_id FROM orders GROUP BY account_id HAVING COUNT(order_id) >= 2
277
+ SELECT account_id FROM orders
278
+ WHERE account_id = 123
279
+ GROUP BY account_id HAVING COUNT(order_id) >= 2
277
280
  )
278
281
  ORDER BY oc.account_id, rp.product_rank;"""
279
- verify_sql_test_data(
280
- sql, config, cnn, [(123, "Laptop", 1, 1), (123, "Smartphone", 1, 1)]
282
+ verify_sql_test(
283
+ sql,
284
+ config,
285
+ cnn=cnn,
286
+ data=[(123, "Laptop", 1, 1), (123, "Smartphone", 1, 1)],
281
287
  )
282
288
 
283
289
 
@@ -647,7 +647,9 @@ class TestMultipleRestriction:
647
647
  "prod_name",
648
648
  "category",
649
649
  ], # category stored as JSON
650
- "restrictions": [{"column": "id", "values": [324, 224]}],
650
+ "restrictions": [
651
+ {"column": "id", "values": [324, 224], "operation": "IN"}
652
+ ],
651
653
  },
652
654
  {
653
655
  "table_name": "customers",
@@ -704,7 +706,7 @@ class TestMultipleRestriction:
704
706
  },
705
707
  fix="SELECT id FROM products1 WHERE (id IN (324, 224)) AND id IN (324, 224)",
706
708
  cnn=cnn,
707
- data=[],
709
+ data=[["324"]],
708
710
  )
709
711
 
710
712
  def test_with_in_operator2(self, config, cnn):
@@ -716,7 +718,7 @@ class TestMultipleRestriction:
716
718
  },
717
719
  fix="SELECT id FROM products1 WHERE (id IN (324, 233)) AND id IN (324, 224)",
718
720
  cnn=cnn,
719
- data=[],
721
+ data=[["324"]],
720
722
  )
721
723
 
722
724
  def test_in_operator_with_or(self, config, cnn):
@@ -728,7 +730,7 @@ class TestMultipleRestriction:
728
730
  "Missing restriction for table: products1 column: id value: [324, 224]"
729
731
  },
730
732
  fix="""SELECT id FROM products1 WHERE ((id IN (324, 224) OR prod_name = 'prod3')) AND id IN (324, 224)""",
731
- data=[],
733
+ data=[["324"]],
732
734
  )
733
735
 
734
736
  def test_not_in_operator(self, config, cnn):
@@ -745,13 +747,13 @@ class TestMultipleRestriction:
745
747
 
746
748
  def test_in_operator_with_numeric_values(self, config, cnn):
747
749
  verify_sql_test(
748
- """SELECT id FROM products1 WHERE (id IN (324, 224) AND category IN ('electronics', 'furniture'))""",
750
+ """SELECT id FROM products2 WHERE (id IN (324, 224) AND category IN ('electronics', 'furniture'))""",
749
751
  config,
750
752
  cnn=cnn,
751
753
  errors={
752
- "Missing restriction for table: products1 column: id value: [324, 224]"
754
+ "Missing restriction for table: products2 column: id value: [324, 224]"
753
755
  },
754
- fix="SELECT id FROM products1 WHERE ((id IN (324, 224) AND category IN "
756
+ fix="SELECT id FROM products2 WHERE ((id IN (324, 224) AND category IN "
755
757
  "('electronics', 'furniture'))) AND id IN (324, 224)",
756
758
  data=[],
757
759
  )