{"id":"af40d1eccd4536c7","repo":"sequelize/sequelize","slug":"query-includes-bind-parameter-parametername","errorCode":null,"errorMessage":"Query includes bind parameter \"$${parameterName}\", but no value has been provided for that bind parameter.","messagePattern":"Query includes bind parameter \"\\$(.+?)\", but no value has been provided for that bind parameter\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/sequelize.js","lineNumber":287,"sourceCode":"      if (!isPlainObject(options.bind) && !isBindArray) {\n        throw new TypeError(\n          'options.bind must be either a plain object (for named parameters) or an array (for numeric parameters)',\n        );\n      }\n\n      const isOracleBulkBind = this.dialect.name === 'oracle' && isBindArray && options.executeMany;\n\n      if (isOracleBulkBind) {\n        // skip mapBindParameters and all bind name validation\n        // oracle driver executeMany() expects this format\n        bindParameters = options.bind;\n      } else {\n        const mappedResult = mapBindParameters(sql, this.dialect);\n\n        for (const parameterName of mappedResult.parameterSet) {\n          if (isBindArray) {\n            if (!/[1-9][0-9]*/.test(parameterName) || options.bind.length < Number(parameterName)) {\n              throw new Error(\n                `Query includes bind parameter \"$${parameterName}\", but no value has been provided for that bind parameter.`,\n              );\n            }\n          } else if (!(parameterName in options.bind)) {\n            throw new Error(\n              `Query includes bind parameter \"$${parameterName}\", but no value has been provided for that bind parameter.`,\n            );\n          }\n        }\n\n        sql = mappedResult.sql;\n\n        // used by dialects that support \"INOUT\" parameters to map the OUT parameters back to the name the dev used.\n        options.bindParameterOrder = mappedResult.bindOrder;\n        if (mappedResult.bindOrder == null) {\n          bindParameters = options.bind;\n        } else {\n          bindParameters = mappedResult.bindOrder.map(key => {","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/sequelize/sequelize/blob/7e1deec499d5afbb8d1877c2f4d545cead1214ec/packages/core/src/sequelize.js#L269-L305","documentation":"When options.bind is an array, queryRaw maps each $N token in the SQL to bind[N-1]. Line 286 throws if a parameter name is non-numeric or if its index exceeds the array length — i.e. the SQL references a positional bind the caller did not supply. This catches SQL/bind mismatches before they reach the driver.","triggerScenarios":"SQL contains `$3` but bind array has only 2 elements; SQL contains `$x` (non-numeric token) while bind is an array (named tokens require an object).","commonSituations":"Editing a query and adding a new `?`/`$N` placeholder without extending the bind array; off-by-one when generating SQL dynamically; mixing named and positional syntax in the same query.","solutions":["Count placeholders in the SQL and ensure the bind array has at least that many entries in the right order.","If the SQL uses named tokens ($name), pass a plain object for bind instead of an array.","Generate both SQL and bind list from the same source (e.g. a query builder) so they cannot drift."],"exampleFix":"// before\nawait sequelize.queryRaw('SELECT $1, $2, $3', { bind: [10, 20] }); // missing $3\n\n// after\nawait sequelize.queryRaw('SELECT $1, $2, $3', { bind: [10, 20, 30] });","handlingStrategy":"validation","validationCode":"function assertArrayBindCoversSql(sql, bind) {\n  const tokens = [...sql.matchAll(/\\$(\\d+)/g)].map(m => Number(m[1]));\n  const max = tokens.length ? Math.max(...tokens) : 0;\n  if (bind.length < max) {\n    throw new Error(`SQL references $${max} but bind array length is ${bind.length}.`);\n  }\n  return bind;\n}\nawait sequelize.queryRaw(sql, { bind: assertArrayBindCoversSql(sql, bind) });","typeGuard":"function arrayBindMatchesSql(sql, bind) {\n  const tokens = [...sql.matchAll(/\\$(\\d+)/g)].map(m => Number(m[1]));\n  const max = tokens.length ? Math.max(...tokens) : 0;\n  return Array.isArray(bind) && bind.length >= max;\n}","tryCatchPattern":"try {\n  await sequelize.queryRaw(sql, { bind });\n} catch (e) {\n  if (/no value has been provided for that bind parameter/.test(e.message) && Array.isArray(bind)) {\n    throw new Error(`Bind mismatch in SQL: ${sql}. Provided ${bind.length} values.`);\n  } else throw e;\n}","preventionTips":["Generate SQL and bind array together from one source to avoid drift.","Prefer the sql`` tagged template so placeholders and values are co-located.","Add a query lint test that asserts every $N in raw SQL has a matching bind entry."],"tags":["bind-parameters","queryraw","sql-bind-mismatch"],"analyzedSha":"7e1deec499d5afbb8d1877c2f4d545cead1214ec","analyzedAt":"2026-08-03T18:58:44.549Z","schemaVersion":2}