Table of Contents
Biferno can interact with the most common commercial and open source relational DBMS. Two implementation of the database interface are available, using either native drivers (when available) or ODBC (Open DataBase Connectivity). ODBC is a standard database access method that can be used by a client application to request data from a DBMS. DBMS interaction uses SQL (Structured Query Language). SQL is a standard query language that allows to analyze, read, write, delete and modify data stored in a relational database.
This chapter illustrates the functionality provided in Biferno to communicate with a database. We assume a good understanding of SQL in the examples that will be presented in the following, since a detailed discussion of SQL as a query language is well beyond the scope of this manual.
ODBC is an API (Application Programming Interface) that client applications can use to request data from a server. Client applications using ODBC send their queries to an ODBC driver manager that runs on the same client machine. The ODBC driver manager recognizes the data source specified in the query and knows which ODBC driver is associated to that data source. The query, coded in the SQL query language, is sent via the appropriate driver to the database on the server, and the response is returned to the client application via the ODBC driver manager.
A DSN (Data Source Name) can be seen as a shortcut that allows a client application to connect directly to a database using ODBC. A DSN identifies a database with a unique name, while additional information is associated to the corresponding ODBC data source (among others, database server address and security options, such as username and password). We refer the interested reader to the in-depth discussion of ODBC in Roger E. Sanders, "ODBC 3.5 - Developer's Guide", McGraw-Hill.
Before discussing how to interface to a database with a Biferno script it is necessary to briefly describe the elements that define an SQL database and to clarify the terminology we will use in the following.
Until now we have used the word database indifferently to refer to the DBMS as well as to the true database. A database is actually a data structure managed by a DBMS. Many individual entities, which are separate but correlated, can exist within a database: tables, indices, sequences, etc. The actual data is stored in tables, which are organized in columns (fields) and rows (records).
For exemplification purposes, assume to have access to a DBMS where, in a
database called mydb, we have created a users table that contains
information on the users of an on- line service. The information is
structured along the same lines as described in the description of the user
class in Chapter 11, User classes. To connect to the database we
will use an ODBC data source with a DSN called MYDB. We assume that the
corresponding data source has been previously defined via the ODBC driver
manager.
Table 15.1. Structure of the users table
| user_id | first_name | last_name | sex | age | username | password |
|---|---|---|---|---|---|---|
| 1 | James | Kirk | M | 38 | jtkirk | captain |
| 2 | Leonard | McCoy | M | 42 | lmccoy | bones |
| 3 | Montgomery | Scott | M | 51 | scotty | beammeup |
| 4 | Nyota | Uhura | F | 34 | nuhura | commoff |
| 5 | Pavel | Chekov | M | 28 | pcheckov | navigator |
Table 15.1, “Structure of the users table” shows the structure of the users table. The
table is organized in seven columns and contains five rows (records). The
user_id column contains a unique numerical index for each record, which is
used as a primary key during search and comparison operations. The values
of this column are automatically calculated and inserted by the DBMS when a
new record is created.
Finally we are ready to show the procedure used in Biferno to connect to a data source via ODBC (connection via native drivers uses a similar approach).
A db class variable, which represents the connection itself, i.e. a
communication channel with the database, has to be instantiated. After this
step, methods of the db class applied to the corresponding object allow
database interaction. The constructor of the db class is as follows:
db(string initString, string db)
The initString parameter identifies the connection string that contains all
necessary parameters to connect to the database. The db parameter
identifies the kind of database that we want to connect to. For an ODBC
connection the connection string has to contain the username and password
to be used to gain database access (if requested), and the db parameter
must have value "odbc". An example is:
dbconn = db("DSN=datasource;UID=username;PWD=password", "odbc")
If we use a native driver, the connection string is different. An example is the connection to a MySQL database via the Biferno native driver, which uses a syntax similar to the following:
dbconn = db("mysqlhost, mysqluser, mysqlpassword, mydb", "mysql")
The db parameter is the name of the driver, which is usually the same as
the DBMS name (the parameter is case-sensitive).
The connection string syntax (first constructor parameter) and the driver name (second constructor parameter) are listed in the following table for several native drivers available at the time of this writing:
Table 15.2. Connecting via Native Drivers
| Database | Connection String | Driver Name |
|---|---|---|
| MySQL | "[host], [user], [password], [db]" | "mysql" |
| Postgres | "hostaddr=[host] dbname=[db] user=[user]" | "postgres" |
| Oracle | "[instance], [username], [password]" | "oracle" |
With the exception of the parameters of the constructor method, all methods
of the db class do not depend on the particular DBMS or ODBC used. The
following discussion is independent of the particular database used, unless
explicitly noted.
This section discusses a practical example of database interaction and
illustrates the main methods of the db class that allow to execute SQL
queries on a database and to obtain the resulting information.
In the following script we connect to the MYDB data source (which we will
use in all our examples), then we interrogate the users table and send in
output to the browser the content of all records, formatted as an HTML
table.
<html>
<body>
<?
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
query = "SELECT first_name, last_name, sex, age FROM users"
dbconn.Exec(query)
nrec = dbconn.GetCurRecs()
if (nrec)
{
?>
<table border="0" cellpadding="4" cellspacing="0">
<?
for (i = 1; i <= nrec; i++)
{
recArray = dbconn.FetchRec()
?>
<tr>
<td>$recArray["first_name"]$</td>
<td>$recArray["last_name"]$</td>
<td>$recArray["sex"]$</td>
<td>$recArray["age"]$</td>
</tr>
<?
}
?>
</table>
<?
}
?>
</body>
</html>
After instantiation of the dbconn variable of class db, we send an SQL
query to the database using the Exec method on the dbconn object. This
method has the following prototype:
int Exec(string sql_statement, int mode=defaultMode, int rowSetSize,
boolean freeCurs)
The Exec method sends the query contained in the sql_statement parameter to
the database and returns an integer number that represents a cursor
identifier. In an SQL- capable database, a cursor indicates the current
position within a group of records resulting from the execution of an SQL
query (record set). A cursor determines which record in a record set will
be returned to the application at the next data request from that record
set. A cursor can be considered as an entity identifying both the whole
record set and the current record position within the record set.
In the previous example the cursor has not been explicitly used because
most methods of the db class operate by default on the current cursor,
which is the one returned by the last call to the Exec method.
The mode parameter of the Exec method specifies the type of cursor that
should be created (static or dynamic) and can assume the values defined by
the defaultMode, staticMode and dynamicMode constants of the db class. The
default value of this parameter is defaultMode, and in this case the db
class chooses the most efficient cursor type depending on the driver and
database type.
A static cursor is a cursor associated to a static record set resulting from an SQL query. Static cursors do not report modifications made by other application (or by other connections/users of the same application) to the record set after their creation, but they do recognize modifications made by the same application within the same connection. Static cursors can request all records in a single query to the database server.
A dynamic cursor reports all modifications to its record set after its creation, including those made by other applications or users. Finally, dynamic cursors usually request records to the database server in a sequence of steps.
The freeCurs parameter can be used to indicate that the cursor can be
automatically disposed by Biferno because it will be no longer used in the
code (see the Section "Cursor Deletion" in the following). The rowSetSize
parameter specifies the number of records that should be extracted by the
cursor when the application performs a data request to the current record
set. For performance optimization reasons it is advisable to use a value
corresponding to the number of records that the application will present
together on a page. If no value is specified for the rowSetSize parameter,
a default value is chosen that optimizes performance for the driver used.
This cursor characteristic can be modified also after its creation using
the RowSetSize method, with prototype:
void RowSetSize(int size, int cursorID)
If the cursorID parameter is omitted, the method acts on the current
cursor, which, as mentioned, is the common behavior for all db class
methods that operate on cursors.
Going back to the previous example, after execution of SQL query, the
GetCurRecs method is invoked on the dbconn variable. This method returns
the total number of records for a cursor, and has prototype:
int GetCurRecs(int cursorID)
If an SQL query modifies, inserts, or deletes records from a table, the
number of affected records can be discovered calling the GetAffectedRecs
method, with prototype:
int GetAffectedRecs(int cursorID)
To extract a record from a cursor the FetchRec method is available with prototype:
array FetchRec(int cursorID, boolean undefNULL)
When invoked, the FetchRec method advances the cursor to the record
following the current record in the record set, and returns in an
associative array the record content. On the first invocation the FetchRec
method returns the first record of the current record set. Only values
corresponding to columns specified in the SQL query are returned in the
array. If the true value is passed as the undefNULL parameter, array
elements corresponding to null values (NULL) in the record are undefined
(notice that the default for null values is to contain the empty string).
In our example, after the first call to the FetchRec method, the recArray
array will have the following structure:
array(first_name:James, last_name:Kirk, sex:M, age:38)
Element index names in the associative array that is returned correspond to
column names in the users table that are included in the SQL query "SELECT
first_name, last_name, sex, age FROM users". Values of the array elements
returned by the FetchRec methods are always of the string class,
regardless of the native type of table columns.
Calling the FetchRec method in a loop causes the cursor to iterate through
the record set extracting records one at a time. When the cursor is
positioned on the last record of the set, the next invocation of the
FetchRec method returns an empty array. Based on this behavior, the loop in
the previous example can be rewritten as follows, using a while instruction
instead of the for instruction:
<?
while (recArray = dbconn.FetchRec())
{
...
}
?>
The loop exit condition is based on the size of the recArray array (when
the size is zero, i.e. the array is empty, the loop terminates). This shows
how the implicit typecast from the array class to the boolean class is
actually done on the base of the dim array property. In other terms,
writing: if (my_array) has the same effect as writing: if (my_array.dim >
0).
The main characteristic (and limitation) of the FetchRec method is that
the cursor can be moved only forwards in a sequential fashion. To position
the cursor on an arbitrary record of the current set, we can use the Seek
method with prototype:
void Seek(int index, int cursorID)
The index parameter represents the numerical index of the record that we
want to position the cursor on. If we write:
dbconn.Seek(1)
we will position the current cursor on the first record of the set. If the
specified index is greater than the number of records in the current set,
the cursor is positioned after the last record and a successive invocation
of the FetchRec method returns an empty array. The index of the record
currently under the cursor can be obtained using the Tell method with
prototype:
int Tell(int cursorID)
An example is:
curRecIndex = dbconn.Tell()
When a script uses more than one cursor, it is sometimes necessary to
delete a cursor that is no longer necessary, because there is a limit in
Biferno on the number of cursors that can be simultaneously used on the
same db variable. Cursor deletion also optimizes database performance by
freeing resources.
To eliminate a cursor the Free method is available with prototype:
void Free(int cursorID)
There are cases when the cursor is no longer necessary after the Exec
method was called. In such cases a value of true can be passed to the
fourth argument of the Exec (freeCurs), which causes the cursor to be
automatically deleted and eliminates the need for a subsequent call to
Free. Usually the cursor is necessary when executing SELECT-like SQL
statements to be able to subsequently invoke the GetCurRecs, FetchRec etc.
methods. Sometimes the cursor is ignored for SQL statements such as
UPDATE, DELETE, INSERT (nonetheless the cursor might still be necessary,
e.g. to be able to execute the GetAffectedRecs method).
All cursors associated to a database connection are automatically
eliminated when the corresponding db class variable is deleted. This is
because cursors associated to database connections stored in variables
(local or global) having the current script as their scope are deleted
when script execution terminates. If we use cursors associated to
connections stored in variables with wider scope (e.g. application), these
cursors will have to be explicitly eliminated using the Free method when
they are no longer necessary or when the script is exited.
We have described how to execute an SQL query using the Exec method, which
immediately submits the query to the database asking immediate execution.
This method should be used when a query is used only once within a Biferno
script.
For queries that will be repeatedly used the ExecPrepared method can be a
better alternative. With this method queries are prepared using the Prepare
method, which only submits them to the database server for later execution.
It should be noted that, in Biferno, not all DBMS have native support for prepared queries.
In practice it is only worthwhile to use the two-step SQL query strategy (preparation, execution) if we make use of parametric queries, because in this case the same query template can be used multiple times with different values.
The following script shows a simple preparation and execution sequence for a non-parametric SQL query with the goal of illustrating the methods that are used. Parametric SQL queries are discussed in the following.
<?
// Database connection
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
// Query preparation
query = "SELECT * FROM users WHERE last_name = 'Kirk'"
prep_id = dbconn.Prepare(query)
// Selection and execution of the prepared query
curs_id = dbconn.GetPrepared(prep_id)
dbconn.ExecPrepared(curs_id)
// Processing of results
if (dbconn.GetCurRecs(curs_id))
{
recArray = dbconn.FetchRec(curs_id)
}
// Cursor deletion
dbconn.Free(curs_id)
?>
After the database connection has been established we prepare our SQL query
calling the Prepare method with prototype:
int Prepare(string sql_statement, int totPrepared=1, int mode=defaultMode,
int rowSetSize)
This method prepares the query corresponding to the sql_statement parameter
and returns an integer value representing a unique identifier associated to
the prepared query.
The totPrepared parameter specifies the number of queries to be prepared,
i.e. the number of cursors associated to the query that are created by the
database at preparation time. It might be convenient to prepare in advance
(when the application is started) a certain number of parametric queries,
which might be the maximum number of users that can access the application
at the same time. In this way the application performance can be improved,
especially if using ODBC, because the execution of prepared queries
requires fewer information exchanges between the client and the database.
The difference can run as high as 50%.
The mode and rowSetSize parameters have the same meaning as for the Exec
method. Before executing a previously prepared query, the corresponding
cursor must be obtained from the database. Toward this end we use the
GetPrepared method, passing as a parameter the identifier of the prepared
query we want to execute. The prototype is as follows:
int GetPrepared(int prepareID)
The invocation returns a new cursor for the prepared query (identified by
prepareID). To execute the prepared query we call the ExecPrepared method,
passing the cursor identifier obtained from the GetPrepared method as
parameter. The prototype of the ExecPrepared method is as follows:
void ExecPrepared(int cursorID)
After query execution, the results can be processed acting on the cursor
using the previously described methods (GetCurRecs, FetchRec, etc.).
Considering the previous example, assume that the string corresponding to
the surname to retrieve from the users table is obtained from user input
(e.g. from a text field within a HTML form). If we call lname the variable
associated to the value inserted by the user, we can dynamically build our
SQL string as follows:
<?
lname = db.Escape(lname)
query = "SELECT * FROM users WHERE last_name = '" + lname + "'"
?>
The static method Escape of the db class transforms a literal string into
the appropriate format for insertion in an SQL statement. The prototype is
as follows:
static string Escape(string str)
This method escapes single quotes that might be present in the string by
doubling them (see also the RealEscape method of class db in "Biferno: Reference Guide").
A parametric SQL query contains internally substitution markers (a
question mark "?"), which may be substituted by values before query
execution.
SQL markers can be used in the WHERE clause of a SELECT statement, in the SET clause of an UPDATE statement, and in the VALUES clause of an INSERT statement. Markers can not be used to make table or column names parametric.
We will now modify the script used as an example of preparation and execution of an SQL query to use a parametric query. The first step is to prepare the query, e.g. by inserting the following code rows in the "Biferno.config.bfr" configuration file of our application:
<?
// Database connection
application aDBconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
// Query preparation
local query = "SELECT * FROM users WHERE last_name = ?"
application aQuery_selname = aDBconn.Prepare(query)
?>
Since the database connection must be established before a query can be prepared, the connection must be established in the "Biferno.config.bfr" file.
This technique allows to open a single database connection at application
startup. By associating the connection to a variable with application
scope, the connection will be visible to and usable by all application
scripts, avoiding reconnection at every request.
The following script assigns the value "Kirk" to the lname variable (in
practice this variable is likely to be associated to an input value) and,
after associating the variable to the query marker using the Bind method,
executes the query and processes its results.
<?
lname = "Kirk"
// Selection of prepared query
curs_id = aDBconn.GetPrepared(aQuery_selname)
// Association of the lname variable to query marker
aDBconn.Bind(1, "lname", lname.length, inputBindMode, curs_id)
// Execution of prepared query
aDBconn.ExecPrepared(curs_id)
// Results processing
if (aDBconn.GetCurRecs(curs_id))
{
recArray = aDBconn.FetchRec(curs_id)
}
// Cursor deletion
aDBconn.Free(curs_id)
?>
The Bind method allows to associate a variable to a marker in an SQL query
and has the following prototype:
void Bind(int pos, string variableName, int bytes, int mode, int cursorID)
When the query is executed via the ExecPrepared the current value of the
variable specified by the variableName parameter is substituted to the
marker corresponding to the position identified by the pos parameter and
the resulting SQL statement is submitted to the database (more precisely,
the pos parameter specifies the index of the occurrence of the ? character
within the SQL string).
The bytes parameter denotes the length of the variable called
variableName. The mode parameter is the kind of Bind that we are
executing. In this case we are passing an input variable and therefore we
use the db.inputBindMode value. The Bind method can be also used to
obtain return values, e.g. from a stored procedure. In this case
db.outputBindMode or db.inputOutputBindMode should be used for the mode
parameter.
The cursorID parameter supplies the cursor identifier returned by the
GetPrepared method.
The Bind method can associate a variable that has not been defined. The
important point is that this variable must be defined when the
ExecPrepared method is called, or an error will be generated.
The usefulness of the Bind method is apparent when the value of the
variable associated to the SQL parametric string is modified within a
loop. In the following example we see how the value corresponding to an
array element with index varying within a for loop can be associated to a
parametric query.
<html>
<body>
<?
namesArray = array("John", "James", "Andrew", "Leonard")
names = namesArray.dim
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
query = "SELECT first_name, last_name, sex, age FROM users WHERE first_name = ?"
prep_id = dbconn.Prepare(query)
curs_id = dbconn.GetPrepared(prep_id)
dbconn.Bind(1, "namesArray[i]", namesArray[i].length,
inputBindMode, curs_id)
for (i = 1; i <= names; i++)
{
dbconn.ExecPrepared(curs_id)
nrec = dbconn.GetCurRecs(curs_id)
if (nrec)
{
?>
<table border="0" cellpadding="4" cellspacing="0">
<?
while (recArray = dbconn.FetchRec(curs_id))
{
?>
<tr>
<td>$recArray["first_name"]$</td>
<td>$recArray["last_name"]$</td>
<td>$recArray["sex"]$</td>
<td>$recArray["age"]$</td>
</tr>
<?
}
}
?>
</table>
<?
}
dbconn.Free(curs_id)
?>
</body>
</html>
Internally to the for cycle the ExecPrepared method is called. The method
executes the parametric query, where the value of the element of the
namesArray array corresponding to the current value of the loop index (i)
is substituted to the ? marker. Notice how the array name followed by the
index between square brackets is passed to the Bind method. Of course both
the array and the index must be defined when the ExecPrepared method is
called. With this technique, the search string is changed automatically at
every loop iteration.
The BindAll method is similar to the Bind method, and allows to associate
a variable to all cursors associated to a prepared query (the Bind method
associates the variable to a single cursor). The BindAll method has the
following prototype:
void BindAll(int pos, string variableName, int bytes, int mode, int poolID)
Let's see an example of the use of this method. In the "Biferno.config.bfr" configuration file of our application we write the following code lines:
<?
application aDBconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
local query = "SELECT * FROM users WHERE last_name = ?"
application aQuery_selname = aDBconn.Prepare(query, 16)
aDBconn.BindAll(1, "lname", 255, inputBindMode, aQuery_selname)
?>
Using the Prepare method 16 identical parametric queries are sent to the
database and the database generates 16 cursors associated to the
parametric query. On the next line, the lname variable (which is not yet
defined when the "Biferno.config.bfr" is executed) is associated to all 16
cursors associated to the prepared query by calling the BindAll method and
passing the query identifier returned by the Prepare method. The BindAll
method must be called right after preparation of the query and before the
GetPrepared method is invoked. Notice that the length of the lname
variable is not known when the BindAll method is called, and thus the
value 255 is passed for the bytes parameter. When executing the prepared
query (ExecPrepared call), the code should check that the length of lname
does not exceed 255 bytes.
Using this technique we avoid calling the Bind method multiple times to
associate the lname variable to the individual cursors returned by the
GetPrepared method in the application scripts where the prepared query
will be executed.
A transaction is a sequence of one or more SQL queries executed by an application that are grouped in a single logical unit in such a way that their effect on the database can be reversed. The queries typically modify the database (e.g. by record, table or index insertion, modification, deletion).
The documentation of the database we interface to should be carefully consulted, because not all DBMS support transactions. In the ODBC case transactions support is defined by the specific driver. In the MySQL case, for which Biferno implements a native MySQL driver, transactions are also supported.
The db class makes three methods available to manage transactions:
Transaction, to initiate a transaction.
Commit, to finalize the result of a transaction.
RollBack, to undo the result of a transaction.
All these methods have no parameters and do not return any value. The following script provides an example of the use of these methods:
<?
// Database connection (e.g. via ODBC)
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
// Start of a transaction
dbconn.Transaction()
// Query execution (e.g. record modification)
query = "UPDATE users SET age = 40 WHERE user_id = 1"
dbconn.Exec(query)
// End of the transaction and finalization of the modification
dbconn.Commit()
// or, to undo the database modification
// dbconn.RollBack()
?>
It should be noted that both the Commit and the RollBack methods close the
transaction and that the RollBack method can not be used to undo the effect
of a transaction closed by Commit method.
This class supports decoding of a string containing logical operators (e.g.
| or &) and parentheses to make it easier to manipulate the substrings
(keywords) relevant to a database search. In other terms, this class is
useful to execute archive searches on the base of formatted strings input
by the user in a HTML form.
The search class is only used to prepare the string and can not be directly
used to execute a database search. To do this, the search string supplied
by the user must be first translated into an SQL string of equivalent
meaning using the search class.
Table 15.3. Operators supported by the search class in search strings
| Logical Operator | Characters used |
|---|---|
| AND | Ampersand (&) |
| OR | Vertical bar (|) |
| NOT | Exclamation mark (!) |
| Wildcard character | Star (*) |
To instantiate a search class variable a suitably formatted string should
be passed to the class constructor. Table 15.3, “Operators supported by the search class in search strings” lists
the operators supported in a formatted search string for the search class
(default characters).
The following example of search strings clarify the use of these operators:
<?
mySearch = search("movies & sport")
mySearch = search("movies | sport")
mySearch = search("sport & !tennis")
mySearch = search("movi*")
?>
The first search string means: Find all elements containing both "movies"
and "sport". The second string means: Find all elements containing either
"movies" or "sport". The third string means: Find all elements containing
"movies" but not "sport". The fourth string means: Find all elements
containing words starting with "movi".
The wildcard character can be used either as the first character of the search substring, or as its last character, or both as its first and last character. Its meaning is "starts with", "ends with", or "contains" the substring, respectively.
More complex strings can be implemented using parentheses, as in:
<?
mySearch = search("movies & (action | horror)")
mySearch = search("(soccer & maradona) | (cars & senna)")
?>
Different characters (or strings) can be specified for the operators that
can be used in search strings, both at application and script level. The
default application level operators can be changed by changing the values
of the SEARCH_AND, SEARCH_OR, SEARCH_NOT and SEARCH_WILD configuration
variables in the "Biferno.config.bfr" file. Default operators can be
modified for the current script only using the SetOption static method of
the search class, with prototype:
static void SetOption(string search_and, string search_or,
string search_not, string search_wild)
The method is used as follows:
<?
search.SetOption("AND", "OR", "NOT", "%")
?>
After this call to the SetOption method the search class will recognize in search strings, within the current script, the SQL-style operators and wildcard character.
The search class has a number of properties that allow to obtain
information on the associated search string and to modify the meaning of
the different substrings. Substrings are single words (or groups of words
separated by spaces) and limited by logical operators and parentheses.
The tot read-only property contains the total number of substrings in the
search string. The mode property contains the implicit concatenation
logical operator for substrings of the search string consisting in
space-separated words. This property is used at writing time, when the
kind of SQL query that will be generated by the ToSQL method can be
defined by the concatenation of strings contained in a search class
variable and by assigning one of the logical operators and or or (search
class constants) to the mode property. We will show some examples in the
following.
The string read-only property is an array containing all substrings of the
search string. The oper read/write property is an array containing all
logical operators separating substrings of the search string. The possible
values of the array elements are the constants all, allNot, and, andNot,
or and orNot of the search class.
The group read/write property is an array containing, for each substring
of the search string, an identifier indicating the associated type of
parenthesis (open, closed, none). In correspondence to an open parenthesis
the array contains the value of the openPar constant of the search class.
In correspondence to a closed parenthesis the array contains the value of
the closePar constant of the search class. The noPar constant denotes the
absence of a parenthesis.
The findType read/write property is an array containing, for each
substring of the search string, the kind of comparison corresponding to
the position of a wildcard character in that string to use during database
search. For each substring one of the values specified by the begins, ends
and contains constants of the search class will be used.
We will now show some examples of use of the ToSQL method of the search
class to construct SQL strings for database search. The ToSQL method has
the following prototype:
string ToSQL(string fieldName, boolean isNumeric, boolean lowerSQL)
The fieldName parameter is the search column name (if no value is
supplied, the method uses the "[field]" default value). The isNumeric
Boolean parameter specifies if the column is a numeric column, i.e. if
values should be enclosed between single quotes in the SQL string. The
default value is false, based on the fact that searches are more commonly
performed on text fields. The lowerSQL Boolean parameter specifies that
the result string should use lowercase SQL keywords (the default is
uppercase keywords).
The ToSQL method returns an SQL string equivalent to the search associated
to the search class object on which the method was called.
We assume in the following script that the search string assigned to the
src_str variable was input by the user in a HTML form. Based on this
string, a search is executed on the last_name field of the users table
(see Table 15.3, “Operators supported by the search class in search strings”).
<html>
<body>
<?
src_str = "K* | M*"
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
query = "SELECT first_name, last_name FROM users WHERE "
query += search(src_str).ToSQL("last_name")
curs_id = dbconn.Exec(query)
if (dbconn.GetCurRecs(curs_id))
{
while (arr_rec = dbconn.FetchRec(curs_id))
print(arr_rec["first_name"]+" "+arr_rec["last_name"]+"<br>")
}
?>
</body>
</html>
The ToSQL method is used in the above code to generate, starting from a
search class object, an SQL string to be inserted after the WHERE clause
in the SQL SELECT statement. In the case of the "K* | M*" search string
that is contained in the src_str variable, the string is translated into
its SQL equivalent: last_name LIKE 'K%' OR last_name LIKE 'M%'. The query
resulting from this procedure selects all records in the users table where
the last_name field contains a string starting with the letters K or M.
The following example shows the combined use of the ToSQL method and of
the value of the mode property of the search class. Assume that an input
word sequence separated by spaces should result in a database search for
the same words separated by AND operators. Notice that this is not
normally the case, as spaces are usually not considered as separators in
search strings and a group of words is treated as a single substring. The
script shows the technique to use in this case:
<?
src_str = "movies sport music"
my_src = search(src_str)
my_src.mode = search.and // Search for the AND of the words
query = "SELECT * FROM tv_channels WHERE "
query += my_src.ToSQL("category")
?>
The SQL string contained in the query variable after script execution has terminated has the following value:
SELECT * FROM tv_channels WHERE category = 'movies' AND category =
'sport' AND category = 'music'.
The use of the mode property of the search class can be a valid
alternative to the use of operators, because it allows to simplify search
strings while maintaining a degree of flexibility.
An obvious limit to the above script is the fact that keywords are compared with the field content using the "equals" operator. If we wish to search for all records containing one or more keywords of the search string without forcing the user to specify wildcard characters at the beginning and at the end of all keywords, we can operate as in the following example:
<?
function SetFindType(search *inSrc, int inFind)
{
if (inFind == 0 || inFind == search.begins || inFind == search.ends || \
inFind == search.contains)
{
nFind = inSrc.findType.dim
for (i = 1; i <= nFind; i++)
inSrc.findType[i] = inFind
}
}
src_str = "movies sport music"
my_src = search(src_str)
my_src.mode = search.or // Search for the OR of the words
SetFindType(&my_src, search.contains)
query = "SELECT * FROM tv_channels WHERE "
query += my_src.ToSQL("category")
?>
The SQL query resulting from script execution is:
SELECT * FROM tv_channels WHERE category LIKE '%movies%' OR category LIKE
'%sport%' OR category LIKE '%music%'.
The SetFindType function modifies the value of the findType property for
all substrings of a search class variable passed by reference and it
assigns one of the constant values begins, ends or contains to the
property (the zero value removes the property).