Skip to content

FEAT:Support 5.1-Modified JSON_Haskey_Lookup #459

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

Open
wants to merge 11 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.

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

Successfully merging this pull request may close these issues.

3 participants