diff --git a/lib/sql.js b/lib/sql.js index e2c7506..aa2b3ac 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -407,28 +407,32 @@ class Sql { * @returns {String} - updated sql query */ addParameters({ query, request, parameters, forWhere = false }) { - if (!parameters) { - return query; - } + if (!parameters) return query; + const { buildParameterName, dataTypes, forceCaseInsensitive } = this; - const paramNames = Object.keys(parameters); const whereClauses = []; - for (let index = 0, len = paramNames.length; index < len; index++) { - let paramName = paramNames[index]; - const props = parameters[paramName]; - if (props === undefined || (typeof props === 'number' && isNaN(props))) { - continue; - } - let operator = "="; + const paramNames = Object.keys(parameters); + + for (let i = 0; i < paramNames.length; i++) { + const originalName = paramNames[i]; + let paramName = originalName; + let props = parameters[paramName]; + + if (props === undefined || (typeof props === 'number' && isNaN(props))) continue; + + let operator = '='; let value = props; let ignoreNull = true; let fieldName = paramName; let sqlType; - if (props !== undefined && props !== null && typeof props === 'object') { + + // Extract object-style properties + if (props && typeof props === 'object' && props !== null) { if (props.statement) { whereClauses.push(props.statement); continue; } + operator = props.operator || operator; value = props.value; ignoreNull = props.ignoreNull !== false; @@ -436,57 +440,68 @@ class Sql { sqlType = props.sqlType; } - if ((!isNullNotNullOperators.includes(operator)) && (value === undefined || (value === null && ignoreNull))) { - continue; - } + // Skip null/undefined values for normal operators + const isNullOp = isNullNotNullOperators.includes(operator); + if (!isNullOp && (value === undefined || (value === null && ignoreNull))) continue; + // Case-insensitive handling if (forceCaseInsensitive) { if (typeof value === 'string' || sqlType === dataTypes.string) { value = value.toUpperCase(); fieldName = `UPPER(${fieldName})`; - } - if (Array.isArray(value)) { - value = value.map(val => typeof val === 'string' ? val.toUpperCase() : val); - // Only apply UPPER() to fieldName if the array contains at least one string - const hasString = value.some(val => typeof val === 'string'); - if(hasString) { - fieldName = `UPPER(${fieldName})`; - } + } else if (Array.isArray(value)) { + const hasString = value.some(v => typeof v === 'string'); + if (hasString) fieldName = `UPPER(${fieldName})`; + value = value.map(v => typeof v === 'string' ? v.toUpperCase() : v); } } - if (paramName.indexOf('.') > -1) { - const parts = paramName.split('.'); - paramName = parts[parts.length - 1]; - } - let statement = `${fieldName} ${operator}${!isNullNotNullOperators.includes(operator) ? ` ${buildParameterName(paramName)}` : ''}`; - const isBetweenOperator = operator.toLowerCase() === 'between' || operator.toLowerCase() === 'not between'; + // Shorten param name if it's nested + const shortName = paramName.includes('.') ? paramName.split('.').pop() : paramName; - if (Array.isArray(value) || ["in", "not in"].includes(operator.toLowerCase())) { - const inResult = isBetweenOperator ? this.between({ request, fieldName, paramName, values: value, operator, sqlType: sqlType || dataTypes.integer }) : this.in({ request, fieldName, paramName, values: value, operator, sqlType: sqlType || dataTypes.integer }); - if (inResult.paramNames.length === 0) { - continue; - } + const isBetween = /between/i.test(operator); + const isInOperator = ['in', 'not in'].includes(operator.toLowerCase()); + + let statement = `${fieldName} ${operator}${!isNullOp ? ` ${buildParameterName(shortName)}` : ''}`; + + // Handle IN / BETWEEN operators + if (Array.isArray(value) || isInOperator) { + const fn = isBetween ? this.between : this.in; + const inResult = fn.call(this, { + request, + fieldName, + paramName: shortName, + values: value, + operator, + sqlType: sqlType || dataTypes.integer + }); + + if (!inResult || !inResult.paramNames?.length) continue; statement = inResult.statement; - if (query && !isBetweenOperator) { - // ability to replace @{FieldName} if we have embedded parameter in inner query - query = query.replaceAll(`${this.parameterPrefix}{${paramName}}`, inResult.paramNames.join(', ')); + + // Prevent infinite replacements + const placeholder = `${this.parameterPrefix}{${shortName}}`; + const replacement = inResult.paramNames.join(', '); + + if (typeof query === 'string' && !replacement.includes(placeholder) && !isBetween) { + query = query.replaceAll(placeholder, replacement); } } else { - if (sqlType !== undefined && sqlType !== null) { - request.input(paramName, sqlType, value); + // Add SQL parameter binding safely + if (sqlType != null) { + request.input(shortName, sqlType, value); } else { - request.input(paramName, value); + request.input(shortName, value); } } - if (forWhere) { - whereClauses.push(statement); - } + if (forWhere) whereClauses.push(statement); } - if (whereClauses.length > 0) { - query += " WHERE " + whereClauses.join(' AND '); + + if (forWhere && whereClauses.length > 0) { + query += ' WHERE ' + whereClauses.join(' AND '); } + return query; } diff --git a/package.json b/package.json index 5b464cb..d6d30fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@durlabh/dframework", - "version": "1.0.48", + "version": "1.0.49", "main": "index.js", "license": "MIT", "type": "module",