[关闭]
@WrRan 2016-11-18T07:28:30.000000Z 字数 13597 阅读 11147

node-oracledb

13. Bind Parameters for Prepared Statements

SQL and PL/SQL statements may contain bind parameters, indicated by colon-prefixed identifiers or numerals. These indicate where separately specified values are substituted when the statement is executed. Bind parameters are also called bind variables.

Using bind parameters is recommended in preference to constructing SQL or PL/SQL statements by string concatenation. This is for performance and security.

Inserted data that is bound is passed to the database separately from the statement text. It can never be executed. This means there is no need to escape bound data inserted into the database.

If a statement is executed more than once with different values for the bind parameters, Oracle can re-use context from the initial execution, thus improving performance. However, if similar statements contain hard coded values instead of bind parameters, Oracle sees the statement text is different and would be less efficient.

IN binds are values passed into the database. OUT binds are used to retrieve data. IN OUT binds are passed in, and may return a different value after the statement executes.

OUT bind parameters for RETURNING INTO clauses will always return an array of values. See DML RETURNING Bind Parameters.

13.1 IN Bind Parameters

With IN binds, the bound data value, or current value of a JavaScript variable, is used during execution of the SQL statement.

In this example, the SQL bind parameters :country_id and :country_name can be bound to values in node-oracledb using an array. This is often called "bind by position":

  1. connection.execute(
  2. "INSERT INTO countries VALUES (:country_id, :country_name)",
  3. [90, "Tonga"],
  4. function(err, result)
  5. {
  6. if (err)
  7. console.error(err.message);
  8. else
  9. console.log("Rows inserted " + result.rowsAffected);
  10. });

The position of the array values corresponds to the position of the SQL bind variables as they occur in the statement, regardless of their names. This is still true even if the bind variables are named like :0, :1 etc. The following snippet will fail because the country name needs to be the second entry of the array so it becomes the second value in the INSERT statement

  1. connection.execute(
  2. "INSERT INTO countries VALUES (:1, :0)",
  3. ["Tonga", 90], // fail
  4. . . .

Instead of binding by array, an object that names each bind value can be used. The attributes can in be any order but their names must match the SQL bind parameter names. This is often called "bind by name":

  1. connection.execute(
  2. "INSERT INTO countries VALUES (:country_id, :country_name)",
  3. {country_id: 90, country_name: "Tonga"},
  4. function(err, result)
  5. {
  6. if (err)
  7. console.error(err.message);
  8. else
  9. console.log("Rows inserted " + result.rowsAffected);
  10. });

The default direction for binding is BIND_IN. The datatype used for IN binds is inferred from the bind value.

If desired, each IN bind parameter can be described by an object having explicit attributes for the bind direction (dir), the datatype (type) and the value (val):

  1. var oracledb = require('oracledb');
  2. . . .
  3. connection.execute(
  4. "INSERT INTO countries VALUES (:country_id, :country_name)",
  5. {
  6. country_id: { val: 90, dir: oracledb.BIND_IN, type: oracledb.NUMBER },
  7. country_name: { val: "Tonga", dir: oracledb.BIND_IN, type:oracledb.STRING }
  8. },
  9. function(err, result)
  10. {
  11. if (err)
  12. console.error(err.message);
  13. else
  14. console.log("Rows inserted " + result.rowsAffected);
  15. });

For IN binds the direction must be BIND_IN. The type can be STRING, NUMBER, DATE matching the data. The type BUFFER can bind a Node.js Buffer to an Oracle Database RAW type. The type CURSOR cannot be used with IN binds.

13.2 OUT and IN OUT Bind Parameters

For each OUT and IN OUT bind parameter, a bind value object containing val, dir, type and maxSize properties is used. For PL/SQL Associative Array binds a maxArraySize property is required.

The dir attribute should be BIND_OUT or BIND_INOUT.

For BIND_INOUT parameters, the type attribute should be STRING, NUMBER, DATE or BUFFER.

For BIND_OUT parameters the type attribute should be STRING, NUMBER, DATE, CURSOR, BLOB, CLOB or BUFFER.

The type BUFFER is used to bind an Oracle Database RAW to a Node.js Buffer.

If type is not specified then STRING is assumed.

A maxSize should be set for STRING OUT or IN OUT binds. This is the maximum number of bytes the bind parameter will return. If the output value does not fit in maxSize bytes, then an error such ORA-06502: PL/SQL: numeric or value error: character string buffer too small or NJS-016: buffer is too small for OUT binds occurs.

A default value of 200 bytes is used when maxSize is not provided for OUT binds of type STRING or BUFFER.

The results parameter of the execute() callback contains an outBinds property that has the returned OUT and IN OUT binds as either array elements or property values. This depends on whether an array or object was initially passed as the bindParams parameter to the execute() call. That is, if bind-by-name is done by passing an object with keys matching the bind variable names, then the OUT bind is also returned as an object with the same keys. Similarly, if bind-by-position is done by passing an array of bind values, then the OUT and IN OUT binds are in an array with the bind positions in the
same order.

Here is an example program showing the use of binds:

  1. var oracledb = require('oracledb');
  2. . . .
  3. var bindVars = {
  4. i: 'Chris', // default direction is BIND_IN. Datatype is inferred from the data
  5. io: { val: 'Jones', dir: oracledb.BIND_INOUT },
  6. o: { type: oracledb.NUMBER, dir: oracledb.BIND_OUT },
  7. }
  8. connection.execute(
  9. "BEGIN testproc(:i, :io, :o); END;",
  10. bindVars,
  11. function (err, result)
  12. {
  13. if (err) { console.error(err.message); return; }
  14. console.log(result.outBinds);
  15. });

Given the creation of TESTPROC using:

  1. CREATE OR REPLACE PROCEDURE testproc (
  2. p_in IN VARCHAR2, p_inout IN OUT VARCHAR2, p_out OUT NUMBER)
  3. AS
  4. BEGIN
  5. p_inout := p_in || p_inout;
  6. p_out := 101;
  7. END;
  8. /
  9. show errors

The Node.js output would be:

  1. { io: 'ChrisJones', o: 101 }

An alternative to the named bind syntax is positional syntax:

  1. var bindVars = [
  2. 'Chris',
  3. { val: 'Jones', dir: oracledb.BIND_INOUT },
  4. { type: oracledb.NUMBER, dir: oracledb.BIND_OUT }
  5. ];

Mixing positional and named syntax is not supported. The following will throw an error:

  1. var bindVars = [
  2. 'Chris',
  3. { val: 'Jones', dir: oracledb.BIND_INOUT },
  4. { o: { type: oracledb.NUMBER, dir: oracledb.BIND_OUT } } // invalid
  5. ];

13.3 DML RETURNING Bind Parameters

Bind parameters from "DML RETURNING" statements (such as INSERT ... RETURNING ... INTO ...) can use STRING, NUMBER or DATE for the OUT type.

For STRING types, an error occurs if maxSize is not large enough to hold a returned value.

Note each DML RETURNING bind parameter is returned as an array containing zero or more elements. Application code that is designed to expect only one value could be made more robust if it confirms the returned array length is not greater than one. This will help identify invalid data or an incorrect WHERE clause that causes more results to be returned.

Oracle Database DATE, TIMESTAMP and TIMESTAMP WITH LOCAL TIME ZONE types can be bound as DATE for DML RETURNING. These types and ROWID can also be bound as STRING.

An example of DML RETURNING binds is:

  1. var oracledb = require('oracledb');
  2. . . .
  3. connection.execute(
  4. "UPDATE mytab SET name = :name "
  5. + "WHERE id = :id "
  6. + "RETURNING id, name INTO :rid, :rname",
  7. {
  8. id: 1001,
  9. name: "Krishna",
  10. rid: { type: oracledb.NUMBER, dir: oracledb.BIND_OUT },
  11. rname: { type: oracledb.STRING, dir: oracledb.BIND_OUT }
  12. },
  13. function(err, result)
  14. {
  15. if (err) { console.error(err); return; }
  16. console.log(result.outBinds);
  17. });

If the WHERE clause matches one record, the output would be like:

  1. { rid: [ 1001 ], rname: [ 'Krishna' ] }

When a couple of rows match, the output could be:

  1. { rid: [ 1001, 1001 ], rname: [ 'Krishna', 'Krishna' ] }

If the WHERE clause matches no rows, the output would be:

  1. { rid: [], rname: [] }

13.4 REF CURSOR Bind Parameters

Oracle REF CURSORS can be fetched in node-oracledb by binding a CURSOR to a PL/SQL call. The resulting bind variable becomes a ResultSet, allowing rows to be fetched using getRow() or getRows(). The ResultSet can also be converted to a Readable Stream by using toQueryStream().

If using getRow() or getRows() the result set must be freed using close() when all rows have been fetched, or when the application does not want to continue getting more rows. If the REF CURSOR is set to NULL or is not set in the PL/SQL procedure then the returned ResultSet is invalid and methods like getRows() will return an error when invoked.

When using Oracle Database 11gR2 or greater, then prefetchRows can be used to tune the performance of fetching REF CURSORS.

Given a PL/SQL procedure defined as:

  1. CREATE OR REPLACE PROCEDURE get_emp_rs (
  2. p_sal IN NUMBER,
  3. p_recordset OUT SYS_REFCURSOR) AS
  4. BEGIN
  5. OPEN p_recordset FOR
  6. SELECT first_name, salary, hire_date
  7. FROM employees
  8. WHERE salary > p_sal;
  9. END;
  10. /

This PL/SQL procedure can be called in node-oracledb using:

  1. var oracledb = require('oracledb');
  2. var numRows = 10; // number of rows to return from each call to getRows()
  3. var plsql = "BEGIN get_emp_rs(:sal, :cursor); END;";
  4. var bindvars = {
  5. sal: 6000,
  6. cursor: { type: oracledb.CURSOR, dir: oracledb.BIND_OUT }
  7. }
  8. connection.execute(
  9. plsql,
  10. bindvars,
  11. function(err, result)
  12. {
  13. if (err) { . . . }
  14. fetchRowsFromRS(connection, result.outBinds.cursor, numRows);
  15. });
  16. function fetchRowsFromRS(connection, resultSet, numRows)
  17. {
  18. resultSet.getRows( // get numRows rows
  19. numRows,
  20. function (err, rows)
  21. {
  22. if (err) {
  23. . . . // close the result set and release the connection
  24. } else if (rows.length > 0) { // got some rows
  25. console.log(rows); // process rows
  26. if (rows.length === numRows) // might be more rows
  27. fetchRowsFromRS(connection, resultSet, numRows);
  28. else // got fewer rows than requested so must be at end
  29. . . . // close the result set and release the connection
  30. } else { // else no rows
  31. . . . // close the result set and release the connection
  32. }
  33. });
  34. }

See refcursor.js
for a complete example.

To convert the REF CURSOR ResultSet to a stream, use
toQueryStream(). With the PL/SQL and bind values
from the previous examples, the code would become:

  1. connection.execute(
  2. plsql,
  3. bindvars,
  4. function(err, result)
  5. {
  6. if (err) { . . . }
  7. fetchRCFromStream(connection, result.outBinds.cursor);
  8. });
  9. function fetchRCFromStream(connection, cursor)
  10. {
  11. var stream = cursor.toQueryStream();
  12. stream.on('error', function (error) {
  13. // console.log("stream 'error' event");
  14. console.error(error);
  15. return;
  16. });
  17. stream.on('metadata', function (metadata) {
  18. // console.log("stream 'metadata' event");
  19. console.log(metadata);
  20. });
  21. stream.on('data', function (data) {
  22. // console.log("stream 'data' event");
  23. console.log(data);
  24. });
  25. stream.on('end', function () {
  26. // console.log("stream 'end' event");
  27. connection.release(
  28. function(err) {
  29. if (err) {
  30. console.error(err.message);
  31. }
  32. });
  33. });
  34. }

The connection must remain open until the stream is completely read.
Query results must be fetched to completion to avoid resource leaks.
The ResultSet close() call for streaming query results will be
executed internally when all data has been fetched.

13.5 LOB Bind Parameters

LOBs can be bound with dir set to BIND_OUT. Binding LOBs with
BIND_IN or BIND_INOUT is currently not supported.

To use the node-oracledb Lob API, CLOB variables should be bound with
type CLOB. BLOB variables should be bound
with type BLOB.

PL/SQL OUT CLOB parameters can also be bound as STRING but this is
not recommended because the returned length is limited to the maximum
limit of maxSize.

The following code snippet binds a PL/SQL OUT CLOB parameter and
returns a Lob to node-oracledb:

  1. /*
  2. CREATE TABLE mylobs (id number, c CLOB);
  3. INSERT INTO mylobs . . .
  4. CREATE OR REPLACE PROCEDURE myproc (p_id IN NUMBER, p_c OUT CLOB) AS
  5. BEGIN
  6. SELECT c INTO p_c FROM mylobs WHERE id = p_id;
  7. END;
  8. */
  9. var oracledb = require('oracledb');
  10. . . .
  11. connection.execute(
  12. "BEGIN myproc(:id, :cbv); END;",
  13. { id: 1, cbv: { type: oracledb.CLOB, dir: oracledb.BIND_OUT} },
  14. function(err, result)
  15. {
  16. if (err) { console.error(err.message); return; }
  17. var lob = result.outBinds.cbv;
  18. // Use Node.js Streams API to fetch the CLOB data from lob
  19. . . .

See Working with CLOB and BLOB Data for more information
on working with Lob streams.

13.6 PL/SQL Collection Associative Array (Index-by) Bind Parameters

Arrays of strings and numbers can be bound to PL/SQL IN, IN OUT, and
OUT parameters of PL/SQL INDEX BY associative array type. This type
was formerly called PL/SQL tables or index-by tables. This method of
binding can be a very efficient way of transferring small data sets.
Note PL/SQL's VARRAY and nested table collection types cannot be
bound.

Given this table and PL/SQL package:

  1. DROP TABLE mytab;
  2. CREATE TABLE mytab (id NUMBER, numcol NUMBER);
  3. CREATE OR REPLACE PACKAGE mypkg IS
  4. TYPE numtype IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  5. PROCEDURE myinproc(p_id IN NUMBER, vals IN numtype);
  6. PROCEDURE myoutproc(p_id IN NUMBER, vals OUT numtype);
  7. END;
  8. /
  9. CREATE OR REPLACE PACKAGE BODY mypkg IS
  10. PROCEDURE myinproc(p_id IN NUMBER, vals IN numtype) IS
  11. BEGIN
  12. FORALL i IN INDICES OF vals
  13. INSERT INTO mytab (id, numcol) VALUES (p_id, vals(i));
  14. END;
  15. PROCEDURE myoutproc(p_id IN NUMBER, vals OUT numtype) IS
  16. BEGIN
  17. SELECT numcol BULK COLLECT INTO vals FROM mytab WHERE id = p_id ORDER BY 1;
  18. END;
  19. END;
  20. /

To bind an array in node-oracledb using "bind by name" syntax for insertion into mytab use:

  1. connection.execute(
  2. "BEGIN mypkg.myinproc(:id, :vals); END;",
  3. {
  4. id: 1234,
  5. vals: { type: oracledb.NUMBER,
  6. dir: oracledb.BIND_IN,
  7. val: [1, 2, 23, 4, 10]
  8. }
  9. }, . . .

Alternatively, "bind by position" syntax can be used:

```javascript
connection.execute(
"BEGIN mypkg.myinproc(:id, :vals); END;",
[
1234,
{ type: oracledb.NUMBER,
dir: oracledb.BIND_IN,
val: [1, 2, 23, 4, 10]
}
],

function (err) { . . . });

  1. After executing either of these `mytab` will contain:
ID         NUMCOL

  1234          1
  1234          2
  1234         23
  1234          4
  1234         10
  1. The [`type`](#executebindParams) must be set for PL/SQL array binds.
  2. It can be set to [`STRING`](#oracledbconstantsnodbtype) or [`NUMBER`](#oracledbconstantsnodbtype).
  3. For OUT and IN OUT binds, the [`maxArraySize`](#executebindParams)
  4. bind property must be set. Its value is the maximum number of
  5. elements that can be returned in an array. An error will occur if the
  6. PL/SQL block attempts to insert data beyond this limit. If the PL/SQL
  7. code returns fewer items, the JavaScript array will have the actual
  8. number of data elements and will not contain null entries. Setting
  9. `maxArraySize` larger than needed will cause unnecessary memory
  10. allocation.
  11. For IN OUT binds, `maxArraySize` can be greater than the number of
  12. elements in the input array. This allows more values to be returned
  13. than are passed in.
  14. For IN binds, `maxArraySize` is ignored, as also is `maxSize`.
  15. For `STRING` IN OUT or OUT binds, the string length
  16. [`maxSize`](#executebindParams) property may be set. If it is not set
  17. the memory allocated per string will default to 200 bytes. If the
  18. value is not large enough to hold the longest string data item in the
  19. collection a runtime error occurs. To avoid unnecessary memory
  20. allocation, do not let the size be larger than needed.
  21. The next example fetches an array of values from a table. First,
  22. insert these values:
  23. ```sql
  24. INSERT INTO mytab (id, numcol) VALUES (99, 10);
  25. INSERT INTO mytab (id, numcol) VALUES (99, 25);
  26. INSERT INTO mytab (id, numcol) VALUES (99, 50);
  27. COMMIT;
  28. <div class="md-section-divider"></div>

With these values, the following node-oracledb code will print
[ 10, 25, 50 ].

  1. connection.execute(
  2. "BEGIN mypkg.myoutproc(:id, :vals); END;",
  3. {
  4. id: 99,
  5. vals: { type: oracledb.NUMBER,
  6. dir: oracledb.BIND_OUT,
  7. maxArraySize: 10 // allocate memory to hold 10 numbers
  8. }
  9. },
  10. function (err, result) {
  11. if (err) { console.error(err.message); return; }
  12. console.log(result.outBinds.vals);
  13. });
  14. <div class="md-section-divider"></div>

If maxArraySize was reduced to 2, the script would fail with:

  1. ORA-06513: PL/SQL: index for PL/SQL table out of range for host language array

See Oracledb Constants and
execute(): Bind Parameters for more information
about binding.

See
plsqlarray.js
for a runnable example.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注