P. 1
The Underground PHP and Oracle Manual

The Underground PHP and Oracle Manual

|Views: 38|Likes:
Published by silviupirvu

More info:

Published by: silviupirvu on Nov 18, 2010
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

11/07/2011

pdf

text

original

Bind variables are just like %s print format specifiers. They let you re-execute a statement with different
values for the variables and get different results. In the PHP community statements like this are known as
prepared statements.

If you do not bind, Oracle must reparse and cache multiple statements.

141

Executing SQL Statements With OCI8

Binding is highly recommended. It can improve overall database throughput. Oracle is more likely to find
the matching statement in its cache and be able to reuse the execution plan and context for that statement,
even if someone else originally executed it.

Bind variables are also an important way to prevent SQL injection security attacks. SQL injection may occur
when SQL statements are hard coded text concatenated with user input:

$w = "userid = 1";
$s = oci_parse($c, "select * from mytable where $w");

If the user input is not carefully checked, then it may be possible for a malicious user to execute a SQL
statement of their choice instead of the one you intended.
In Oracle, a bind variable is a colon-prefixed name in the SQL text. A oci_bind_by_name() call tells
Oracle which PHP variable to actually use when executing the statement.

142

Figure 90: Binding improves performance and security.

Figure 89: Not binding wastes database resources.

Tuning SQL Statements in PHP Applications

Script 16: bindvar.php

$c = oci_connect("hr", "hrpwd", "localhost/XE");

$s = oci_parse($c, "select last_name from employees where employee_id = :eidbv");
$myeid = 101;
oci_bind_by_name($s, ":eidbv", $myeid);
oci_execute($s);
$row = oci_fetch_array($s, OCI_ASSOC);
echo "Last name is: ". $row['LAST_NAME'] ."
\n";

?>

The output is the last name of employee 101:

Last name is: Kochhar

There is no need to (and for efficiency you should not) re-parse the SQL statement if you just want to
change the value of the bind variable. The following code would work when appended to the end of
bindvar.php:

// No need to re-parse or re-bind
$myeid = 102;
oci_execute($s);
$row = oci_fetch_array($s, OCI_ASSOC);
echo "Last name is: ". $row['LAST_NAME'] ."
\n";

Re-running bindvar.php now gives:

Last name is: Kochhar
Last name is: De Haan

You can bind a single value with each oci_bind_by_name() call. Multiple values can be bound with
another function, oci_bind_array_by_name(), and passed to PL/SQL blocks. This is discussed in the
chapter on PL/SQL.
The syntax of oci_bind_by_name() is:
$rc = oci_bind_by_name($statement, $bindvarname, $phpvariable, $length, $type)

The length and type are optional. The default type is the string type, SQLT_CHR. Oracle will convert most
basic types to or from this as needed. For example when binding a number, you can omit the type
parameter.

It is recommended to pass the length if a query is being re-executed in a script with different bind
values. For example when binding, pass the length of the largest potential string. Passing the length also
avoids potential edge-case behavior differences if a script runs with multiple different Oracle character
sets.

Some older PHP examples use & with oci_bind_by_name() parameters. Do not do this. Since a call-
by-reference clean up in the implementation of PHP, this syntax has been deprecated and may cause
problems.

143

Executing SQL Statements With OCI8

A bind call tells Oracle which memory address to read data from. That address needs to contain valid
data when oci_execute() is called. If the bind call is made in a different scope from the execute call there
could be a problem. For example, if the bind is in a function and a function-local PHP variable is bound,
then Oracle may read an invalid memory location if the execute occurs after the function has returned. This
has an unpredictable outcome.
It is a common problem with binding in a foreach loop:
$ba = array(':dname' => 'IT Support', ':loc' => 1700);
foreach ($ba as $key => $val) {
oci_bind_by_name($s, $key, $val);

}

The problem here is that $val is local to the loop (and is reused). The SQL statement will not execute as
expected. Changing the bind call in the loop to use $ba[$key] solves the problem:

Script 17: bindloop.php

$c = oci_connect("hr", "hrpwd", "localhost/XE");

$s = oci_parse($c, 'select *

from departments
where department_name = :dname and location_id = :loc');

$ba = array(':dname' => 'IT Support', ':loc' => 1700);
foreach ($ba as $key => $val) {
oci_bind_by_name($s, $key,
$ba[$key]);

}

oci_execute($s);

while (($row = oci_fetch_array($s, OCI_ASSOC))) {
foreach ($row as $item) {
echo $item." ";

}
echo "
\n";

}

?>

There is one case where you might decide not to use bind variables. When queries contain bind variables,
the optimizer does not have any information about the value you may eventually use when the statement
is executed. If your data is highly skewed, you might want to hard code values. But if the data is derived
from user input be sure to sanitize it.
Finally, Oracle does not use question mark '?' for bind variable placeholders at all and OCI8 supports
only named placeholders with a colon prefix. Some PHP database abstraction layers will simulate support
for question marks by scanning your statements and replacing them with supported syntax.

144

Tuning SQL Statements in PHP Applications

Binding with LIKE and REGEXP_LIKE Clauses

Similar to the simple example above, you can bind the value used in a pattern-matching SQL LIKE or
REGEXP_LIKE clause:

Script 18: bindlike.php

$c = oci_connect("hr", "hrpwd", "localhost/XE");

$s = oci_parse($c,

"select city, state_province from locations where city like :bv");

$city = 'South%';
oci_bind_by_name($s, ":bv", $city);
oci_execute($s);
oci_fetch_all($s, $res);

var_dump($res);

?>

This uses Oracle's traditional LIKE syntax, where '%' means match anything. An underscore in the pattern
string '_' would match exactly one character.
The output from bindlike.php is cities and states where the city starts with 'South':

array(2) {
["CITY"]=>
array(3) {
[0]=>
string(15) "South Brunswick"
[1]=>
string(19) "South San Francisco"
[2]=>
string(9) "Southlake"

}
["STATE_PROVINCE"]=>
array(3) {
[0]=>
string(10) "New Jersey"
[1]=>
string(10) "California"
[2]=>
string(5) "Texas"

}

}

Oracle also supports regular expression matching with functions like REGEXP_LIKE, REGEXP_INSTR,
REGEXP_SUBSTR, and REGEXP_REPLACE.
In a query from PHP you might bind to REGEXP_LIKE using:

145

Executing SQL Statements With OCI8

Script 19: bindregexp.php

$c = oci_connect("hr", "hrpwd", "localhost/XE");

$s = oci_parse($c, "select city from locations where regexp_like(city, :bv)");
$city = '.*ing.*';
oci_bind_by_name($s, ":bv", $city);
oci_execute($s);
oci_fetch_all($s, $res);

var_dump($res);

?>

This displays all the cities that contain the letters 'ing':

array(1) {
["CITY"]=>
array(2) {
[0]=>
string(7) "Beijing"
[1]=>
string(9) "Singapore"

}

}

Binding Multiple Values in an IN Clause

User data for a bind variable is always treated as pure data and never as part of the SQL statement. Because
of this, trying to use a comma separated list of items in a single bind variable will be recognized by Oracle
only as a single value, not as multiple values. The common use case is when allowing a web user to choose
multiple options from a list and wanting to do a query on all values.
Hard coding multiple values in an IN clause in the SQL statement works:
$s = oci_parse($c,

"select last_name from employees where employee_id in (101,102)");

oci_execute($s);
oci_fetch_all($s, $res);
foreach ($res['LAST_NAME'] as $name) {
echo "Last name is: ". $name ."
\n";

}

This displays both surnames but it leads to the scaling and security issues that bind variables overcome.
Trying to emulate the query with a bind variable does not work:

$s = oci_parse($c,

"select last_name from employees where employee_id in (:eidbv)");

$myeids = "101,102";
oci_bind_by_name($s, ":EIDBV", $myeids);
oci_execute($s);

146

Tuning SQL Statements in PHP Applications

oci_fetch_all($s, $res);

All this gives is the error ORA-01722: invalid number because the $myeids string is treated as a single
value and is not recognized as a list of numbers.
The solution for a fixed, small number of values in the IN clause is to use individual bind variables.
NULLs can be bound for any unknown values:

Script 20: bindinlist.php

$c = oci_connect("hr", "hrpwd", "localhost/XE");

$s = oci_parse($c,

"select last_name from employees where employee_id in (:e1, :e2, :e3)");

$mye1 = 103;
$mye2 = 104;
$mye3 = NULL; // pretend we were not given this value
oci_bind_by_name($s, ":E1", $mye1);
oci_bind_by_name($s, ":E2", $mye2);
oci_bind_by_name($s, ":E3", $mye3);
oci_execute($s);
oci_fetch_all($s, $res);

foreach ($res['LAST_NAME'] as $name) {
echo "Last name is: ". $name ."
\n";

}

?>

The output is:

Last name is: Ernst
Last name is: Hunold

Tom Kyte discusses the general problem and gives solutions for other cases in the March – April 2007
Oracle Magazine.

Using Bind Variables to Fetch Data

As well as what are called IN binds, which pass data into Oracle, there are also OUT binds that return
values. These are mostly used to return values from PL/SQL procedures and functions. (See the chapter on
using PL/SQL). If the PHP variable associated with an OUT bind does not exist, you need to specify the
optional length parameter. Another case when the length should be specified is when returning numbers.
By default in OCI8, numbers are converted to and from strings when they are bound. This means the
length parameter should also be passed to oci_bind_by_name() when returning a number otherwise
digits may be truncated:

oci_bind_by_name($s, ":MB", $mb, 10);

147

Executing SQL Statements With OCI8

There is also an optional fifth parameter, which is the datatype. This mostly used for binding LOBS and
result sets as shown in a later chapter. One micro-optimization when numbers are known to be integral, is
to specify the datatype as SQLT_INT. This avoids the type conversion cost:
oci_bind_by_name($s, ":MB", $mb, -1, SQLT_INT);

In this example, the length was set to –1 meaning use the native data size of an integer.

Binding in an ORDER BY Clause

Some applications allow the user to choose the presentation order of results. Typically the number of
variations for an ORDER BY clause are small and so having different statements executed for each condition
is efficient:

switch ($v) {
case 1:

$ob = ' order by first_name';
break;
default:

$ob = ' order by last_name';
break;

}

$s = oci_parse($c, 'select first_name, last_name from employees' . $ob);

But if your tuning indicates that binding in a ORDER BY clause is necessary, and the columns are of the
same type, try using a SQL CASE statement:
$s = oci_parse($c, "select first_name, last_name
from employees
order by

case :ob

when 'FIRST_NAME' then first_name
else last_name

end");
oci_bind_by_name($s, ":ob", $vs);
oci_execute($s);

Using ROWID Bind Variables

The pseudo-column ROWID uniquely identifies a row within a table. This example shows fetching a record,
changing the data, and binding its rowid in the WHERE clause of an UPDATE statement.

Script 21: rowid.php

$c = oci_connect('hr', 'hrpwd', 'localhost/XE');

// Fetch a record
$s = oci_parse($c,

148

Tuning SQL Statements in PHP Applications

'select rowid, street_address
from locations where location_id = :l_bv for update');
$locid = 3000; // location to fetch
oci_bind_by_name($s, ':l_bv', $locid);
oci_execute($s, OCI_DEFAULT);
$row = oci_fetch_array($s, OCI_ASSOC+OCI_RETURN_NULLS);

$rid = $row['ROWID'];
$addr = $row['STREET_ADDRESS'];

// Change the address to upper case
$addr = strtoupper($addr);

// Save new value
$s = oci_parse($c,

'update locations set street_address = :a_bv where rowid = :r_bv');
oci_bind_by_name($s, ':r_bv', $rid, -1, OCI_B_ROWID);
oci_bind_by_name($s, ':a_bv', $addr);
oci_execute($s);

?>

After running rowid.php, the address has been changed from

Murtenstrasse 921

to

MURTENSTRASSE 921

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->