Skip to content

FEAT:Support 5.1-Modified JSON_Haskey_Lookup #459

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from

Conversation

NandanaRaol
Copy link
Contributor

@NandanaRaol NandanaRaol commented Jun 5, 2025

This pull request refactors and enhances JSON handling for SQL Server in mssql/functions.py, focusing on improving readability, modularity, and support for logical operators in JSON conditions. The most important changes include introducing a helper function for condition building, updating the json_HasKeyLookup method for better SQL generation, and improving compatibility with different SQL Server versions.

Refactoring and Modularity:

  • Added the _build_json_conditions helper function to centralize and simplify the logic for combining SQL conditions with logical operators (e.g., AND/OR). This reduces code duplication and improves maintainability.

Enhancements to json_HasKeyLookup:

  • Simplified JSON path handling by introducing consistent preprocessing for left-hand and right-hand sides, improving clarity and reducing redundant code. Logical operator handling was streamlined to ensure consistent condition generation.
  • Improved SQL generation for SQL Server 2022+ by using JSON_PATH_EXISTS with better escaping and condition building. For older SQL Server versions, fallback logic now uses JSON_VALUE with enhanced parameter handling.

These changes make the codebase cleaner, easier to extend, and more robust in handling various SQL Server versions and JSON query scenarios.

@Copilot Copilot AI review requested due to automatic review settings June 5, 2025 18:09
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors json_HasKeyLookup in mssql/functions.py to streamline parameter initialization and SQL generation for JSON key lookups, and updates _get_check_sql for Django 5.1+ compatibility.

  • Initialized lhs_params and conditions lists for more consistent handling.
  • Simplified JSON path checks using JSON_VALUE and unified logic across SQL Server versions.
  • Updated _get_check_sql to use self.condition for Django 5.1+ while preserving backwards compatibility.
Comments suppressed due to low confidence (1)

mssql/functions.py:284

  • The variable cast_sql is assigned but never used: the subsequent conditions use lhs instead of cast_sql, and cast_params are not applied. You should use cast_sql in the JSON_VALUE expression and include cast_params (or merge them with lhs_params) in the returned params.
cast_sql, cast_params = self.lhs.as_sql(compiler, connection)

@@ -297,10 +296,8 @@ def json_HasKeyLookup(self, compiler, connection):
else:
# if no operators are specified
sql = conditions[0]

return sql, cast_params
return sql, lhs_params
Copy link
Preview

Copilot AI Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning only lhs_params here discards the cast_params needed for the CAST expression. Consider returning cast_params + lhs_params (or vice versa) to include all required parameters.

Suggested change
return sql, lhs_params
return sql, cast_params + lhs_params

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check this comment & resolve.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved

logical_op = " " + self.logical_operator + " "
sql = "(" + logical_op.join(conditions) + ")"
path_escaped = path.replace("'", "''")
conditions.append("JSON_VALUE(%s, '%s') IS NOT NULL" % (lhs, path_escaped))
Copy link
Preview

Copilot AI Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inlines both lhs and path_escaped into the SQL string, leaving no placeholders; passing lhs_params to the caller may introduce extraneous parameters. Either switch to placeholders with a params list or return an empty list when there are none.

Suggested change
conditions.append("JSON_VALUE(%s, '%s') IS NOT NULL" % (lhs, path_escaped))
conditions.append("JSON_VALUE(%s, %s) IS NOT NULL")
lhs_params.append(path_escaped)

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider parameterizing the SQL instead of injecting path_escaped directly—this will improve security and prevent bugs with unusual key names. Also, the code for building conditions is duplicated across multiple branches; factoring it out into a helper (e.g. _build_json_condition) could make future changes easier and less error prone. Finally, double-check that all return statements yield consistent parameter lists, not just empty ones in some cases.

e.g.
Instead of

conditions.append("JSON_VALUE(%s, '%s') IS NOT NULL" % (lhs, path_escaped))

Can change to

conditions.append("JSON_VALUE(%s, %s) IS NOT NULL")
lhs_params.extend([lhs, path])

Why: This uses SQL parameters (%s placeholders) for both the column and the path. The path is passed as a parameter, avoiding SQL injection and escaping bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the suggested changes

logical_op = " " + self.logical_operator + " "
sql = "(" + logical_op.join(conditions) + ")"
path_escaped = path.replace("'", "''")
conditions.append("JSON_VALUE(%s, '%s') IS NOT NULL" % (lhs, path_escaped))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider parameterizing the SQL instead of injecting path_escaped directly—this will improve security and prevent bugs with unusual key names. Also, the code for building conditions is duplicated across multiple branches; factoring it out into a helper (e.g. _build_json_condition) could make future changes easier and less error prone. Finally, double-check that all return statements yield consistent parameter lists, not just empty ones in some cases.

e.g.
Instead of

conditions.append("JSON_VALUE(%s, '%s') IS NOT NULL" % (lhs, path_escaped))

Can change to

conditions.append("JSON_VALUE(%s, %s) IS NOT NULL")
lhs_params.extend([lhs, path])

Why: This uses SQL parameters (%s placeholders) for both the column and the path. The path is passed as a parameter, avoiding SQL injection and escaping bugs.

@@ -297,10 +296,8 @@ def json_HasKeyLookup(self, compiler, connection):
else:
# if no operators are specified
sql = conditions[0]

return sql, cast_params
return sql, lhs_params
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check this comment & resolve.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants