Skip to content

Commit d8f2f3f

Browse files
authored
Merge pull request #181 from mk3008/feat_dynamic_condition
feat: enhance SqlParamInjector with improved column validation handling
2 parents a1cc479 + c831d63 commit d8f2f3f

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rawsql-ts",
3-
"version": "0.11.23-beta",
3+
"version": "0.11.24-beta",
44
"description": "[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.",
55
"main": "dist/src/index.js",
66
"module": "dist/esm/index.js",

packages/core/src/transformers/SqlParamInjector.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { SelectQuery, SimpleSelectQuery } from "../models/SelectQuery";
22
import { BinarySelectQuery } from "../models/BinarySelectQuery";
33
import { SelectableColumnCollector } from "./SelectableColumnCollector";
4-
import { BinaryExpression, FunctionCall, ParameterExpression, ParenExpression, ValueComponent, ValueList, SqlParameterValue } from "../models/ValueComponent";
4+
import { BinaryExpression, FunctionCall, ParameterExpression, ParenExpression, ValueComponent, ValueList, SqlParameterValue, ColumnReference } from "../models/ValueComponent";
55
import { UpstreamSelectQueryFinder } from "./UpstreamSelectQueryFinder";
66
import { SelectQueryParser } from "../parsers/SelectQueryParser";
77

@@ -13,6 +13,8 @@ export interface SqlParamInjectorOptions {
1313
ignoreCaseAndUnderscore?: boolean;
1414
/** Whether to allow injection when all parameters are undefined (defaults to false for safety) */
1515
allowAllUndefined?: boolean;
16+
/** Whether to skip column validation entirely (defaults to false for safety) */
17+
skipColumnValidation?: boolean;
1618
}
1719

1820
// Type for state parameter values - can be simple values, conditions, or complex objects
@@ -510,6 +512,25 @@ export class SqlParamInjector {
510512
injectComplexConditions: Function,
511513
validateOperators: Function
512514
): void {
515+
// Skip column validation if option is enabled
516+
if (this.options.skipColumnValidation) {
517+
// if object, validate its keys
518+
if (this.isValidatableObject(stateValue)) {
519+
validateOperators(stateValue, allowedOps, name);
520+
}
521+
522+
// Create a column reference using the name directly
523+
const columnRef = new ColumnReference(null, name);
524+
525+
// Handle complex conditions if needed
526+
if (this.isValidatableObject(stateValue)) {
527+
injectComplexConditions(query, columnRef, name, stateValue);
528+
} else {
529+
injectSimpleCondition(query, columnRef, name, stateValue);
530+
}
531+
return;
532+
}
533+
513534
const queries = finder.find(query, name);
514535
if (queries.length === 0) {
515536
throw new Error(`Column '${name}' not found in query`);

packages/core/tests/transformers/SqlParamInjector.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,44 @@ describe('SqlParamInjector', () => {
8585
expect(params).toEqual({ articleId: 100 });
8686
});
8787

88+
test('should skip column validation for undefined values', () => {
89+
// Arrange: parse base query with limited columns
90+
const baseQuery = SelectQueryParser.parse('select a.article_id from article as a') as SimpleSelectQuery;
91+
// Arrange: state with undefined value for non-existent column - should not throw error
92+
const state = { nonExistentColumn: undefined, article_id: 100 };
93+
94+
// Act: inject parameters - should not throw error for undefined values
95+
const injector = new SqlParamInjector();
96+
const injectedQuery = injector.inject(baseQuery, state);
97+
98+
// Act: format SQL and extract parameters
99+
const formatter = new SqlFormatter();
100+
const { formattedSql, params } = formatter.format(injectedQuery);
101+
102+
// Assert: only defined values should be in WHERE clause
103+
expect(formattedSql).toBe('select "a"."article_id" from "article" as "a" where "a"."article_id" = :article_id');
104+
expect(params).toEqual({ article_id: 100 });
105+
});
106+
107+
test('should skip column validation when skipColumnValidation option is enabled', () => {
108+
// Arrange: parse base query with limited columns
109+
const baseQuery = SelectQueryParser.parse('select a.article_id from article as a') as SimpleSelectQuery;
110+
// Arrange: state with non-existent column - should not throw error with skipColumnValidation
111+
const state = { nonExistentColumn: 'value' };
112+
113+
// Act: inject parameters with skipColumnValidation option
114+
const injector = new SqlParamInjector({ skipColumnValidation: true });
115+
const injectedQuery = injector.inject(baseQuery, state);
116+
117+
// Act: format SQL and extract parameters
118+
const formatter = new SqlFormatter();
119+
const { formattedSql, params } = formatter.format(injectedQuery);
120+
121+
// Assert: WHERE clause should be added even for non-existent column
122+
expect(formattedSql).toBe('select "a"."article_id" from "article" as "a" where "nonExistentColumn" = :nonExistentColumn');
123+
expect(params).toEqual({ nonExistentColumn: 'value' });
124+
});
125+
88126
test('injects state using custom tableColumnResolver', () => {
89127
// Custom tableColumnResolver returns columns for "users" table
90128
const customResolver = (tableName: string) => {

0 commit comments

Comments
 (0)