database abstraction layer

database abstraction layer

am 02.02.2010 21:23:47 von Lars Nielsen

Hi List

I am trying to make a Database Abstraction Layer so I can which the DB
of my application between MySQL and Postgresql. I have been looking at
the way phpBB does it, and it seems that it is only then php-functions
which are different. The SQL seems to be the same.

Is it save to assume that I can use the same SQL, or should i make some
exceptions?

Regards
Lars Nielsen



--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 02.02.2010 21:35:24 von James Colannino

Lars Nielsen wrote:
> Is it save to assume that I can use the same SQL, or should i make some
> exceptions?

Standard SQL should work across all SQL servers with only a few
exceptions (for example, MySQL doesn't support full outer joins.)
Anything that has to do with server administration, however, such as
dealing with users and permissions, will be unique to the db engine.

James

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 02.02.2010 21:39:04 von Michael Peters

Lars Nielsen wrote:
> Hi List
>
> I am trying to make a Database Abstraction Layer so I can which the DB
> of my application between MySQL and Postgresql. I have been looking at
> the way phpBB does it, and it seems that it is only then php-functions
> which are different. The SQL seems to be the same.
>
> Is it save to assume that I can use the same SQL, or should i make some
> exceptions?

Is there a reason why you want to write your own instead of using
something like Pear MDB2?

With Pear MDB2 - if your SQL syntax is database specific it will work in
the specific database but MDB2 will not try to port a specialized SQL
string to another database.

It will port some features to some databases, IE if you use the MDB2
facilities for prepared statements (highly recommended) and the target
database does not support prepared statements, it will emulate them (I
think, haven't tried, that's what I recall reading anyway) but for your
actual SQL syntax it is best to stick to standard SQL.

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 02.02.2010 21:41:18 von Lester Caine

Lars Nielsen wrote:
> Hi List
>
> I am trying to make a Database Abstraction Layer so I can which the DB
> of my application between MySQL and Postgresql. I have been looking at
> the way phpBB does it, and it seems that it is only then php-functions
> which are different. The SQL seems to be the same.
>
> Is it save to assume that I can use the same SQL, or should i make some
> exceptions?

Simple SQL is almost identical. But there are many of the more advanced
functions that have major differences. Check out ADOdb for an existing
abstraction layer that handles a lot of them.
http://adodb.sourceforge.net/

--
Lester Caine - G8HFL
-----------------------------
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk//
Firebird - http://www.firebirdsql.org/index.php

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 02.02.2010 22:01:07 von Rene Veerman

i'm a fan of adodb.sf.net, which i've used with both postgresql and mysql.

On Tue, Feb 2, 2010 at 9:23 PM, Lars Nielsen wrote:
> Hi List
>
> I am trying to make a Database Abstraction Layer so I can which the DB
> of my application between MySQL and Postgresql. I have been looking at
> the way phpBB does it, and it seems that it is only then php-functions
> which are different. The SQL seems to be the same.
>
> Is it save to assume that I can use the same SQL, or should i make some
> exceptions?
>
> Regards
> Lars Nielsen
>
>
>
> --
> PHP General Mailing List (http://www.php.net/)
> To unsubscribe, visit: http://www.php.net/unsub.php
>
>

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 02.02.2010 22:06:07 von Paul M Foster

On Tue, Feb 02, 2010 at 09:23:47PM +0100, Lars Nielsen wrote:

> Hi List
>
> I am trying to make a Database Abstraction Layer so I can which the DB
> of my application between MySQL and Postgresql. I have been looking at
> the way phpBB does it, and it seems that it is only then php-functions
> which are different. The SQL seems to be the same.
>
> Is it save to assume that I can use the same SQL, or should i make some
> exceptions?
>
> Regards
> Lars Nielsen

Quote of values is different between PostgreSQL and MySQL. I would
suggest you do a wrapper class around the PDO classes, which will take
care of quoting, etc.

Paul

--
Paul M. Foster

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

RE: database abstraction layer

am 02.02.2010 22:15:22 von Daevid Vincent

------=_NextPart_000_00C8_01CAA409.C7A7BC00
Content-Type: text/plain;
charset="us-ascii"
Content-Transfer-Encoding: 7bit

> -----Original Message-----
> From: Lars Nielsen [mailto:lars@mit-web.dk]
> Sent: Tuesday, February 02, 2010 12:24 PM
> To: php-general@lists.php.net
> Subject: [PHP] database abstraction layer
>
> Hi List
>
> I am trying to make a Database Abstraction Layer so I can which the DB
> of my application between MySQL and Postgresql. I have been looking at
> the way phpBB does it, and it seems that it is only then php-functions
> which are different. The SQL seems to be the same.
>
> Is it save to assume that I can use the same SQL, or should i
> make some
> exceptions?
>
> Regards
> Lars Nielsen

There are differences in the actual schema between mySQL and Postgress.

At least there were a few years back when we looked at converting. In the
end, we decided it was too much hassle to switch all our code and database
tables, so just coughed up the licensing for mysql (we were shipping mysql
on our appliance).

So, before you jump into writing all this code, I would first try to make
your app run in postgress and find out about which mySQL statements are
'extensions' to ANSI standard. Case sensitivity was a huge issue for us, as
in one of those RDBMS was very particular about it. There were some other
issues that I can't remember ATM, but perhaps they've been addressed by
now.

One thing I would maybe suggest is (what I do), write a wrapper around your
wrapper -- AKA "Double Bag It". :)

Here at Panasonic Avionics (PAC) we use the PEAR::DB class (the really old
version) since we have to interface with mySQL, SQL Server, Oracle (two
versions). That's where PEAR::DB comes in. However, it's very crude and you
have a lot of redundant code in every page. Like this:
http://pear.php.net/manual/en/package.database.db.intro-fetc h.php
You always have to open a connection, test for errors, do the query, test
for errors, fetch the rows, etc..

When I came on board a year ago, I put an end to that micky mouse crap. I
wrote a nice db.inc.php wrapper that handles all that sort of thing, and
then pumps it up like it's on steroids. I added auto-reconnect in case the
connection dropped. I added color-coded SQL output with substitution for
the '?'. I added a last_insert_it() routine which is proprietary to mySQL
BTW (speaking of incompatibilities). I added routines to get an Enum
column, or to get a simple array pairing, etc. It can even force reads from
slave and writes to master! It pretty much kicks ass.

Just simply do this:

$myfoo = sql_query('agis_core', 'SELECT * FROM foo WHERE bar = ?', $bar);

All the minutia is handled for you and $myfoo is now an array of your
results. :)

So, now we code using my wrapper and should we want to switch out the DBAL
later to a more modern one, we just change OUR wrapper calls. There is
minimal overhead, and the pros FAR outweigh any cons.

I've attached it here.

We have another config.inc.php that has the DB settings for each
DEV/TEST/PROD master/slave servers (as they are all different accounts for
security reasons. So just make one with entries like this:

// AGISCore Database DEV Master mySQL:
$global_db_dsn_agis_core_master = array(
'phptype' => 'mysql',
'username' => 'RWMaster',
'password' => 'rwmaster',
'hostspec' => '10.10.10.2:3306',
'database' => 'agis_core',
'persistent' => TRUE
);

// AGISCore Database DEV Slave mySQL:
$global_db_dsn_agis_core_slave = array(
'phptype' => 'mysql',
'username' => 'ROSlave',
'password' => 'roslave',
'hostspec' => '10.10.10.3:3306',
'database' => 'agis_core',
'persistent' => TRUE
);


$GLOBALS['DB_CONNECTIONS'] is a singleton (sans the class overhead) so that
you always get the same handle for each database call and don't spawn new
ones each time. Nice. :)

------=_NextPart_000_00C8_01CAA409.C7A7BC00
Content-Type: text/plain;
name="db.inc.php"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="db.inc.php"

/**
* All of the database wrapper functions
*
* This is a wrapper around the PEAR::DB class. It provides many =
enhancements including
* a singleton for database handle connections, retries for connections, =
debugging with ? substitutions,
* handy routines to populate arrays, select boxes, IN() statements, =
etc. It can do SQL timing profiling.
* There are routines for INSERT and UPDATE by simply passing in an =
array of key/value pairs.
*
* Confidential property of Panasonic Avionics. Do not copy or =
distribute.
* @copyright 2006-2010 Panasonic Avionics. All rights reserved.
* @category CategoryName
* @package PackageName
* @see
* @since DART2
* @author Daevid Vincent
* @date Created: 2009-01-20
* @version CVS: $Id: db.inc.php,v 1.39 2010/01/29 01:35:30 vincentd Exp =
$
*/
require_once '/usr/share/php/DB.php';

$SQL_OPTION['noHTML'] =3D false;
$SQL_OPTION['fullQuery'] =3D true;
$SQL_OPTION['useLogger'] =3D false;
$SQL_OPTION['profile'] =3D 0;
$SQL_OPTION['debug'] =3D false;
$SQL_OPTION['outfile'] =3D false; //set this to a filename, and use =
$show_sql in your queries and they'll go to this file.

$GLOBALS['DB_CONNECTIONS'] =3D array(); //this will hold each db =
connection so we'll only create one at a time. like a singleton.

/**
* A wrapper around the SQL query function that provides many extra =
features.
*
* Handles the $db handle, errors and echoing of the SQL query itself
* and stores any errors in the $GLOBALS['SQL_ERROR_STRING'];
*
* NOTE: PEAR:DB has many shortcomings, one of which is that the key of =
the returned hash will be lowercased!
*
* Prepared statements can use ?, !, & -- see =
http://pear.php.net/manual/en/package.database.db.intro-exec ute.php for =
more information.
*
* @access public
* @return mixed a hash of data, a result set handle pointer or false
* @param string $database the database to connect to (agis_core) is the =
default
* @param string $sql The SQL query to be executed, this can be SELECT, =
INSERT, UPDATE or DELETE amongst others.
* @param array $sqlvalues The sqlvalues to the $sql. One element per ? =
is required if this parameter is used.
* @param boolean $show_sql output the $sql to the display (for =
debugging purposes usually). false by default.
* @param array $parameters
* int db_fetch_mode (DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, =
DB_FETCHMODE_OBJECT)
* boolean $show_errors output any errors encountered to the display =
(for debugging purposes usually). true by default.
* boolean $execute useful for debuging when you don't want the SQL =
command to actually execute, but you may want to see the query passed =
i.e. SQL_QUERY($sql, true, true, false); true by default.
* boolean $no_html when using the function in console scripts to =
strip off HTML tags.
* int $parameters['profile'] detail level (1-3) to output the SQL to =
/tmp/SQL_profile.txt.
* profile SQL statements in varying detail levels.
* Detail Levels:
* 1 =3D m/d/y/ h:i:s
* 2 =3D SQL timing
* 3 =3D filename
* @see sql_connect()
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 06/04/09
* @todo Intercept SELECT vs. INSERT/UPDATE/DELETE and connect to SLAVE =
vs MASTER respectively
*/
function sql_query($database=3D'agis_core', $sql, $sqlvalues=3Darray(), =
$show_sql=3Dfalse, $parameters=3Darray())
{
if (!isset($parameters['db_fetch_mode'])) $parameters['db_fetch_mode'] =
=3D DB_FETCHMODE_ASSOC; //DB_FETCHMODE_ORDERED is the other common one =
to use
if (!isset($parameters['show_errors'])) $parameters['show_errors'] =
=3D true;
if (!isset($parameters['execute'])) $parameters['execute'] =3D =
true;
if (!isset($parameters['no_html'])) $parameters['no_html'] =3D =
false;
if (!isset($parameters['profile'])) $parameters['profile'] =3D 0;
//var_dump($parameters);
//$show_sql =3D true;

//[dv] The PEAR::DB library is so horribly stupid that if you don't =
have any '?' but you do pass in $sqlvalues,
// your result set will be an indexed array instead of a k:v hash =
-- regardless of forcing DB_FETCHMODE_ASSOC. UGH!
if (is_null($sqlvalues) || strpos($sql, '?') ===3D false)
$sqlvalues =3D array();
elseif (!empty($sqlvalues) && !is_array($sqlvalues))
$sqlvalues =3D array($sqlvalues);
//var_dump($sqlvalues);

global $SQL_OPTION;

//[dv] the preg_replace will magically strip out the spaces, newlines, =
tabs and other funky chars to make one nice string.
$sql =3D preg_replace("/\s+/",' ', (preg_replace("/\s/",' =
',trim($sql))) );

if ($SQL_OPTION['debug'] || $show_sql)
{
if ($parameters['no_html'] || $SQL_OPTION['noHTML'] || =
$SQL_OPTION['outfile']) $sql =3D str_replace( array("\n", "\r", ' '), ' =
', $sql);

$out_sql =3D '['.$database.'] '.sql_substitute_values($sql, =
$sqlvalues);
if ($SQL_OPTION['outfile'])
file_put_contents($SQL_OPTION['outfile'], 'SQL: '.$out_sql."\n", =
(FILE_APPEND | LOCK_EX) );
elseif ($parameters['no_html'] || $SQL_OPTION['noHTML'])
echo "SQL: ".$out_sql."\n";
else
{
$out_sql =3D sql_print($out_sql);
echo $out_sql;
}
}

if ($parameters['execute'] ===3D true)
{
// execute query only if it appears to be safe.
if ( ($error_str =3D sql_is_safe_query($sql)) ===3D TRUE )
{
if (!sql_connect($database,false)) return false; //this loads =
$GLOBALS['DB_CONNECTION'][$database]

//start profile stuff for this query
if ($SQL_OPTION['profile'] > 0) $parameters['profile'] =3D =
$SQL_OPTION['profile'];
if ($parameters['profile'] > 0)
{
if (!$handle =3D fopen("/tmp/SQL_profile.txt", 'a'))
{
echo "unable to open file /tmp/SQL_profile.txt\n";
$parameters['profile'] =3D 0;
}
$text =3D date("[m/d/y h:i:s ");
if ($parameters['profile'] >=3D 2) $sql_start =3D microtime(true); =
//start timer
}

//[dv] PEAR::DB is so LAME! that we have to FORCE the mode here,
//despite the fact that it should allow it to be passed as a =
parameter. What Garbage.
=
//http://pear.php.net/manual/en/package.database.db.db-commo n.setfetchmod=
e.php
=
$GLOBALS['DB_CONNECTION'][$database]->setFetchMode($paramete rs['db_fetch_=
mode']);

//determine if we need to do a 'query' or a 'getAll'
//so first grab the very first word of the $sql (stripping out =
newlines)
preg_match('/^\(?(\w+) /', str_replace( array("\n", "\r", ' '), ' ', =
$sql), $matches);
$first_word =3D strtolower($matches[1]);
//echo "first_word =3D $first_word
\n";
if (in_array($first_word, array('select','show','explain')))
{
$result =3D& $GLOBALS['DB_CONNECTION'][$database]->getAll($sql, =
$sqlvalues, $parameters['db_fetch_mode']);
}
elseif ( in_array($first_word, array('insert','update','create', =
'drop', 'delete','set','/*!40014','start')) or =
in_array(strtolower($sql), array('commit','rollback')))
{
//TODO: eventually this should do something like this:
//if ('agis_core' == $database)
// $result =3D& =
$GLOBALS['DB_CONNECTION']['agis_core_master']->query($sql,$s qlvalues);
//else
$result =3D& $GLOBALS['DB_CONNECTION'][$database]->query($sql, =
$sqlvalues);
}
else
{
notification_table('error',"db.inc.php::sql_query() does not know =
how to handle '".$first_word."'
\n".sql_print($sql));
backtrace(true);
exit; //we purposely exit here because any SQL after this point =
might give unexpected results.
}

if (DB::isError($result))
{
//var_dump($result);
preg_match("/\[nativecode=3D(\d+) \*\* (.*)\]/", =
$result->getUserInfo(), $errormatch);
$error =3D ' Error =
'.$errormatch[1]."
\n".$errormatch[2]."
\n";
$result =3D FALSE;
}

//end of profiling stuff for this query
if ($parameters['profile'] > 0)
{
if ($parameters['profile'] >=3D 2) $text .=3D number_format( =
(microtime(true) - $sql_start), 4 ).'s'; //end timer

//we do this here so as not to upset the timer too much
if ($parameters['profile'] >=3D 3)
{
$text .=3D ' '.$_SERVER['SCRIPT_FILENAME'];
$traceArray =3D debug_backtrace();
$text .=3D ' '.$traceArray[1]['file'].' =
('.$traceArray[1]['line'].')';
$text =3D str_replace('/public_html/', '', $text);
}

$sql =3D str_replace("\n", ' ', $sql);
$sql =3D preg_replace('/\s+/',' ', $sql);
if (!fwrite($handle, $text.'] '.$sql."\n"))
{
echo "unable to write to file /tmp/SQL_profile.txt\n";
}

@fclose($handle);
}
}
else
{
$error =3D "Malformed query (".$error_str."). Execution blocked.";
$result =3D FALSE; // indicate that we failed
}

if ($result ===3D false)
{
$GLOBALS['SQL_ERROR_STRING'] =3D '';

// if error has not been set, then we have a 'regular' mysql error. =
Otherwise it is a potentially malicious query.
/*
if(!isset($error))
{
$error =3D mysql_error($GLOBALS['DB_CONNECTION']);
$errno =3D mysql_errno($GLOBALS['DB_CONNECTION']);
}
else $errno =3D 0; // not 'regular' mysql error? well, we need some =
error code anyway.
*/

// trim to size if necessary
if(!$SQL_OPTION['fullQuery']) $error =3D substr($error,0,100)."...";

if ($parameters['show_errors'])
{
if ($parameters['no_html'] || $SQL_OPTION['noHTML']) $sql =3D =
preg_replace("/\s+/",' ', (preg_replace("/\s/",' ',$sql)) );

if (!$out_sql) $out_sql =3D sql_print($sql, $sqlvalues, $database);
$GLOBALS['SQL_ERROR_STRING'] .=3D "SQL ERROR =
::
".$error."
SIZE=3D'-2'>".$out_sql."".backtrace(false);

//TODO: [dv] is there a way to determine if we're in a CGI vs. Web =
page?
if ($parameters['no_html'] || $SQL_OPTION['noHTML'])
{
//$GLOBALS['SQL_ERROR_STRING'] =3D preg_replace("/\s+/",' ', =
(preg_replace("/\s/",' ',$GLOBALS['SQL_ERROR_STRING'])) );
echo strip_tags($GLOBALS['SQL_ERROR_STRING'])."\n";
}
else
notification_table('error', $GLOBALS['SQL_ERROR_STRING']);
//echo "

padding: 5px;'>
CLASS=3D'error'>".$GLOBALS['SQL_ERROR_STRING']."

\n";

if ($SQL_OPTION['outfile'])
{
//echo "Dumping error to outfile: ".$SQL_OPTION['outfile']."\n";
file_put_contents($SQL_OPTION['outfile'], =
strip_tags($GLOBALS['SQL_ERROR_STRING']."\n"), (FILE_APPEND | LOCK_EX) =
);
}

//if ($SQL_OPTION['useLogger']) =
logger(strip_tags($GLOBALS['SQL_ERROR_STRING']));
} //if ($parameters['show_errors'])
} //if (!$result)

//TODO: [dv] this should work, we just have to go audit the code for =
all the LIMIT 1 areas as they use [0] probably still...
//now check if there was a LIMIT 1
//if ($result && $first_word == 'select' && is_array($result) && =
preg_match('/( limit 1$)/i', $sql)) $result =3D& $result[0]; //return =
the first row as a convenience

return $result;
}
else
{
if ($show_sql)
{
global $SQL_INT;
echo " style=3D'background-color:#ffffff;'>DEBUG =
SQL[".($SQL_INT-1)."]:
Not =
Executed

\n";
}
}

return true;
}

/**
* Output the HTML debugging string in color coded glory for a sql query
* This is very nice for being able to see many SQL queries
*
* @access public
* @return string HTML color coded string of the input $query.
* @param string $query The SQL query to be executed.
* @param string $database (null) an optional string to print as the =
database
* @see sql_substitute_values()
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 05/27/09
* @todo highlight SQL functions.
*/
function sql_print($query, $sqlvalues=3Darray(), $database=3Dnull)
{
if (!$query) return false;
$query =3D sql_substitute_values($query, $sqlvalues);

global $SQL_INT;
if( !isset($SQL_INT) ) $SQL_INT =3D 0;

//[dv] I like my version better...
//require_once 'classes/geshi/geshi.php';
//$geshi =3D& new GeSHi($query, 'sql');
//echo $geshi->parse_code();
//return;

$query =3D str_replace( array("\n", "\r", ' '), ' ', $query);

//[dv] this has to come first or you will have goofy results later.

//[dv] work in progress. was trying to show comments in gray...
// I think I need to use a sprintf() or maybe preg_replace_callback() =
to sequence the COMMENT_BLOCK_Xs
/*
$comment_pattern =3D "/(\/\*.*?\*\/)/i";
preg_match_all($comment_pattern, $query, $comments);
unset($comments[0]);
//print_r($comments);
$query =3D preg_replace($comment_pattern, "COMMENT_BLOCK", $query, -1);
*/

//[dv] TODO: UGH this number one is causing me lots of grief... why =
can't i figure out the regex to use?
//highlight numbers
//$query =3D preg_replace("/[\s=3D](\d+)\s/", " color=3D'#FF6600'>$1", $query, -1);

//underline tables/databases but nothing in single quote marks as those =
are strings
//FIXME: [dv] this will match something like =
"MYFUNCTION(table_name.column)" and print it later as "MYFUNCTION =
table_name.column)"
// Note how the first ( is missing after the MYFUNCTION name
$query =3D preg_replace("/[^']`?\b(\w+)\.`?/", " $1.", $query, =
-1);

//highlight strings between quote marks
$query =3D preg_replace("/['\"]([^'\"]*)['\"]/i", "' color=3D'#FF6600'>$1'", $query, -1);

//highlight functions
$query =3D preg_replace("/(\w+)\s?\(/", " color=3D'#CC00FF'>".strtoupper('\\1')."(", $query, -1);

$query =3D str_ireplace(
array (
'*',
'SELECT ',
' GROUP BY ',
'UPDATE ',
'DELETE ',
'INSERT ',
'INTO ',
'VALUES ',
'FROM ',
'LEFT ',
'JOIN ',
'WHERE ',
'LIMIT ',
'ORDER BY ',
' AND ',
' OR ', //[dv] note the space. otherwise you match to 'colOR' =
;-)
' DESC',
' ASC',
' ON ',
' AS ',
' NULL'
),
array (
"*",
"SELECT ",
" GROUP BY ",
"UPDATE ",
"DELETE ",
"INSERT ",
"INTO ",
"VALUES ",
"FROM ",
"LEFT ",
"JOIN ",
"WHERE ",
"LIMIT ",
"ORDER BY ",
" AND ",
" OR ",
" DESC",
" ASC",
" ON ",
" AS ",
" NULL"
),
$query
);

//[dv] work in progress. was trying to show comments in gray...
/*
if ($comments[1])
{
foreach($comments[1] as $c)
{
$cb[] =3D 'COMMENT_BLOCK';
$crepl[] =3D "".$c."";
}
//sadly this doesn't do a 1:1 match in each array. it matches =
COMMENT_BLOCK then quits after $crepl[0]
$query =3D str_replace($cb, $crepl, $query);
}
*/

$query =3D " style=3D'background-color:#ffffff;'>DEBUG SQL[".$SQL_INT++."]: =
".$query.";

\n";
if ($database) $query =3D '['.$database.'] '.$query;
return $query;
}

/**
* Substitutes the sqlvalues into the query for debugging purposes
*
* @access public
* @return string
* @param string $query The SQL query
* @param array $sqlvalues the sqlvalues to be substituted into the =
$query
* @see sql_print()
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 05/27/09
* @todo put the DB_common::quoteSmart() around the sqlvalues
*/
function sql_substitute_values($query, $sqlvalues=3Darray())
{
if (!$sqlvalues || count($sqlvalues) < 1) return $query;

$query =3D str_replace('?', "'%s'", $query);
//TODO: wedge in =
http://pear.php.net/manual/en/package.database.db.db-common. quotesmart.ph=
p here for each parameter
//so the debug is more in line with what the real $query should be.
return vsprintf($query, $sqlvalues);
}

/**
* @return int Number of rows in the result set
* @access public
* @param object $result result set
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
*/
function sql_num_rows($result)
{
if ($result)
return $result->numRows();
else
return false;
}

/**
* A wrapper around the sql_query function to return an array of =
key/value pairs.
*
* This is very useful for those tables that are simply a key/value and =
you'd like it in an array
* then you can just reference the array and save yourself a JOIN =
perhaps.
*
* If only one column is given, then the values are a sequential array.
* If two columns are returned, then they are mapped as $col[0] =3D> =
$col[1]
*
* @access public
* @return array of key/value pairs.
* @param string $sql The SQL SELECT query to be executed in an order =
such as "SELECT id, name FROM foo ORDER BY name"
* @param boolean $show_sql output the $sql to the display (for =
debugging purposes usually). false by default.
* @param array $parameters (see sql_query() for available key/value =
pairs)
* @see sql_query()
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 05/28/09
*/
function sql_query_array_pair($database=3D'agis_core', $sql, =
$sqlvalues=3Darray(), $show_sql=3Dfalse, $parameters=3Darray())
{
$parameters['db_fetch_mode'] =3D DB_FETCHMODE_ORDERED; //this has to be =
an ordered array as we don't know the hash keys in an associative one

if ($result =3D sql_query($database, $sql, array(), $show_sql, =
$parameters))
{
foreach($result as $key =3D> $value)
{
if (!$value[1])
$tmpArray[] =3D $value[0];
else
$tmpArray[ $value[0] ] =3D $value[1];
}
return $tmpArray;
}
return false;
}

/**
* This will return the last inserted ID -- from a mySQL database only
*
* @return int Insert ID of last insert action
* @access public
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @see sql_insert()
*/
function sql_insert_id()
{
//not sure the PEAR equivallent for this one?
//http://pear.php.net/manual/en/package.database.db.php
//http://us3.php.net/manual/en/function.mysql-insert-id.php
//could also use a raw query: 'SELECT LAST_INSERT_ID()'
return @mysql_insert_id();
}


/**
* @return int Number of affected rows
* @param string the database to connect to (agis_core_master) is the =
default
* @access public
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
*/
function sql_affected_rows($database=3D'agis_core_master')
{
=
//http://pear.php.net/manual/en/package.database.db.db-commo n.affectedrow=
s.php
return $GLOBALS['DB_CONNECTION'][$database]->affectedRows();
}

/**
* Free up a mysql pointer
*
* @access public
* @param object $result result set
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
*/
function sql_free($result)
{
//http://pear.php.net/manual/en/package.database.db.db-resul t.free.php
if ($result)
return $result->free();
else
return false;
}

/**
* perform the smart quoting for SQL queries
*
* @param string $s the string to be quoted
* @param boolean $trim trim leading and trailing space (true)
* @param string $database a database connection to use ('agis_core')
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @return string
*/
function sql_escape($s, $trim=3Dtrue, $database=3D'agis_core')
{
if ($trim) $s =3D trim($s);

return sql_connect($database)->quoteSmart($s);
}

/**
* Outputs the error message from a failed SQL query/command/connection.
*
* @access public
* @return void
* @param object $db_resource the result of a sql_connect() or a =
sql_query $result
* if a string (such as 'agis_core') is passed in, it is =
automatically converted to the corresponding singleton object =
$GLOBALS['DB_CONNECTION'][$db_resource].
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @see sql_connect(), sql_query()
* @date 2010-01-12
*/
function sql_db_error($db_resource)
{
//var_dump($db_resource);

if (is_string($db_resource)) $db_resource =3D =
$GLOBALS['DB_CONNECTION'][$db_resource];

if (!$db_resource) return "Invalid resource in sql_db_error().
\n";

$error =3D '';
if (PEAR::isError($db_resource))
{
$error .=3D 'Standard Message: =
'.$db_resource->getMessage()."
\n";
$error .=3D 'Standard Code: =
'.$db_resource->getCode()."
\n";
if ($_SESSION['DEVELOPMENT'])
{
$error .=3D 'DBMS/User Message: =
'.$db_resource->getUserInfo()."
\n";
//$error .=3D 'DBMS/Debug Message: =
'.$db_resource->getDebugInfo()."
\n";
backtrace();
}
}

return $error;
}

/**
* Make a connection to a RDBMS and Database. Defaults to agis_core =
(slave).
* Note: dev/test/production is determined from the config.inc.php file =
that resides on each server.
*
* @access public
* @param string $database The RDBMS to connect to (reliability, =
[agis_core], agis_core_master, wfdmt, arinc_symonty, pcube, fmr, =
product_safety, fogbugz)
* @param boolean $show_error (true) show an error message on screen or =
not.
* @return PEAR DB::connect handle/object which is also set in =
$GLOBALS['DB_CONNECTION'][$database] or false if unable to connect
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @see sql_query()
*/
function sql_connect($database=3D'agis_core', $show_error=3Dtrue)
{
if (!$database) return false;

$database =3D strtolower($database);

// add a singleton snippet to not reconnect if the data connection =
object already exists (see sql_escape() for usage)
if (is_object($GLOBALS['DB_CONNECTION'][$database])) return =
$GLOBALS['DB_CONNECTION'][$database];


$tries =3D 5;
for($i =3D 1; $i <=3D $tries; $i++)
{
switch ($database)
{
case 'agis_core': $GLOBALS['DB_CONNECTION'][$database] =3D =
connect_to_db_agis_core(false, false); break 2;
case 'agis_core_master': $GLOBALS['DB_CONNECTION'][$database] =
=3D connect_to_db_agis_core_master(false); break 2;
case 'reliability': $GLOBALS['DB_CONNECTION'][$database] =3D =
connect_to_db_reliability(); break 2;
case 'wfdmt': $GLOBALS['DB_CONNECTION'][$database] =3D =
connect_to_db_wfdmt(); break 2;
case 'arinc_symonty': $GLOBALS['DB_CONNECTION'][$database] =
=3D connect_to_db_arinc_symonty(); break 2;
case 'pcube': $GLOBALS['DB_CONNECTION'][$database] =3D =
connect_to_db_pcube(); break 2;
case 'fmr': $GLOBALS['DB_CONNECTION'][$database] =3D =
connect_to_db_fmr(); break 2;
case 'product_safety': $GLOBALS['DB_CONNECTION'][$database] =
=3D connect_to_db_product_safety(); break 2;
case 'synergy_master': $GLOBALS['DB_CONNECTION'][$database] =
=3D connect_to_db_synergy_master(); break 2;
case 'synergy_reference':$GLOBALS['DB_CONNECTION'][$database] =
=3D connect_to_db_synergy_reference(); break 2;
case 'fogbugz': $GLOBALS['DB_CONNECTION'][$database] =3D =
connect_to_db_fogbugz(); break 2;
case 'pana_session_pw': $GLOBALS['DB_CONNECTION'][$database] =
=3D connect_to_db_pana_session_pw(); break 2;
case 'pana_session_db': $GLOBALS['DB_CONNECTION'][$database] =
=3D connect_to_db_pana_session_db(); break 2;

default: if ($_SESSION['DEVELOPMENT'])
{
notification_table('error', "No such database named =
'".substr($database, 0, 30)."...' Perhaps you have ommited the =
first sql_query() parameter?
\n");
backtrace(true);
}
else
notification_table('error', "No such database. This =
looks like a SQL query instead.
\n");

return false;
break 2;
}

$error .=3D "Unable to connect to ".$database." RDBMS. Retrying =
[$i/$tries] in 5 seconds.
\n";
sleep(5);
}

if ( !is_object($GLOBALS['DB_CONNECTION'][$database]) )
{
$error .=3D 'Could not connect to '.$database." server. =
Aborting.
\n";
if ($show_error) notification_table('error', $error);
return false;
}

if ( PEAR::isError($GLOBALS['DB_CONNECTION'][$database]) )
{
//TODO: we should log this connection failure (and the others like =
this) to syslog.
if ($show_error) notification_table('error', =
sql_db_error($GLOBALS['DB_CONNECTION'][$database]));
return false;
}

return $GLOBALS['DB_CONNECTION'][$database];
}

/**
* We are (currently) trying to prevent just one trivial type of sql =
injection.
* Namely, the one that attempts to end query with a ; and then add an =
extra query
* to the end. This is a common technique, and the one that is easiest =
to detect.
*
* First, we watch for unbalanced quotes. If any are found, the query is =
invalid anyway
* and thus will not be allowed to run.
*
* Second, I can't think of a single valid use of a semicolon outside =
the literals
* enclosed into ''. Semicolons will be alloedd in those literals, but =
not outside.
*
* Single quotes that are in the literals and have been SQL_ESCAPE()'d =
are treated properly,
* that is as a single character within the literal. So are the =
backslashed-escaped chars.
*
* Any other additions are welcome, but this is at least a good start.
*/
function sql_is_safe_query($q)
{
$len =3D strlen($q);
$inside =3D false; // inside a literal (enclosed by '')
$ret =3D true; // query assumed good unless we can prove otherwise.
for ($i =3D 0; $i < $len; $i++)
{
$more =3D ($i < ($len - 1)); // we have at least one more character
switch($q[$i])
{
case "\\":
//[krogebry] Why would there be a test for '$inside' here?
// anything after a \ should be an escaped char, that's what \ =
does.
#if($inside && $more)
#{
$i++; // whatever follows MUST be an escaped character.
#continue;
#}
break;

case "'":
// we are inside the string and came up with a properly-escaped quote
#if($inside && $more && ($q[$i+1] == "'")){
if($inside && $more && $q[$i-1] == "\\" ){
$i++;
continue;
}
$inside =3D !$inside;
break;

case ";":
// semicolons outside literals are not permitted.
if(!$inside){
$ret =3D "Semicolon is used to chain queries. Please, do not do =
that.";
break 2;
}

}// switch()
}

if ($inside) $ret =3D "Unbalanced single quotes";

#print "Ret: [$ret]
\n";
return $ret;
}

/**
* Dynamically generates a select box from a SQL query.
*
* The SELECT must return between two and three items.
* first is the VALUE the second is the text to display and optional =
third is shown in parenthesis
* form element prefilled in
*
* Tooltips do NOT work in IE. sorry. blame Microsoft for not following =
W3 standards...
*
* @access public
* @return int the number of rows in the SELECT box or false.
* @param int $size usually 1, but the select box can be any height.
* @param string $name the NAME=3D'$name' parameter of a SELECT tag.
* @param string $database the database to connect to (agis_core) is the =
default
* @param string $sql The SQL query to be executed, this can be SELECT, =
INSERT, UPDATE or DELETE amongst others.
* @param array $sqlvalues The sqlvalues to the $sql. One element per ? =
is required if this parameter is used.
* @param mixed $blank (boolean) add the extra 'empty' \n"; }
elseif ($blank && is_string($blank)) { echo "\t\n"; }

$items =3D 0;
foreach ($rslt as $key =3D> $value)
{
@list($key, $text, $description) =3D $value;

$items++;
// Check for selectbox sub-headings.
if ( 0 == strncmp( $text, "---", 3 ) )
{
echo "\t\n";
}
else
{
echo "\t\n";
}
}
}

echo "\n";

return $items;
}
else echo "Selectbox cannot be built because of an invalid SQL =
query.\n";

return false;
}

/**
* returns a string that can be appended to an SQL statement to form the =
ORDER BY portion.
*
* if you want to sort by 'name' in descending order, then simply use =
'name_DESC',
* conversely, 'name_ASC' would sort in ascending order. The order of the =
elements in the array
* will determine the order they are appended together.
*
* @access public
* @return string of the form ' ORDER BY element[1], element[2], =
element[3]'...
* @param $orderBy false, string, or array of elements like so: =
[sort_by] =3D> Array ( [1] =3D> name_DESC [2] =3D> id [3] =3D> price )
* @param $default a string to use as the default ORDER BY column
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 03/03/06
*/
function parse_order_by_array($orderBy =3D false, $default =3D false)
{
$sql =3D ' ORDER BY ';

if (!is_array($orderBy))
{
//[dv] is_string() is not enough, as empty values are coming across as =
strings according to var_dump()
if (strlen($orderBy) > 1)
return $sql.$orderBy;
elseif (is_string($default))
return $sql.$default;
else
return false;
}

foreach ($orderBy as $o)
$tmp[] =3D str_replace('_', ' ', $o);

return $sql.implode(', ',$tmp);
}

/**
* Builds the WHERE portion of a SQL statement using the keywords in =
various columns with wildcard support.
*
* @return string SQL statement fragment
* @param mixed $words either a string of words space deliminated or an =
array of words
* @param array $columns an array of table.column names to search the =
$words in. Use % as a wildcard for example pass in 'username%' or =
'%username%'.
* @param boolean $and (true) whether the words have to be ANDed or ORed =
together.
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 05/10/07
* @todo This should handle +, - and "" just like google or yahoo or =
other search engines do.
*/
function keyword_filter($words, $columns, $and =3D true)
{
// this maybe useful
// =
http://wiki.ittoolbox.com/index.php/Code:Translate_Boolean_Q uery_to_SQL_s=
elect_statement
// http://www.ibiblio.org/adriane/queries/
// =
http://www.zend.com/zend/tut/tutorial-ferrara1.php?article=3 Dtutorial-fer=
rara1&kind=3Dt&id=3D8238&open=3D1&anc=3D0&view=3D1

// =
http://evolt.org/article/Boolean_Fulltext_Searching_with_PHP _and_MySQL/18=
/15665/index.html
// http://www.databasejournal.com/features/mysql/article.php/35 12461

// this would be great, but the dumb-asses don't work with InnoDB =
tables. GRRR!
// http://dev.mysql.com/doc/refman/5.0/en/fulltext-boolean.html
//$sql .=3D " AND MATCH (".implode(',',$columns).") AGAINST =
('".implode(' ',$words)."' IN BOOLEAN MODE)";

if (!is_array($columns) or !$words) return;

if (is_string($words))
$words =3D preg_split("/\s+/",$words, -1, PREG_SPLIT_NO_EMPTY);

if(count($words) < 1) return '';

if ($and) //AND the words together
{
$sql =3D " AND ";
$sqlArray =3D array();
foreach($words as $word)
{
$tmp =3D array();
foreach($columns as $field)
{
$col =3D str_replace('%','',$field);
//[dv] read the http://php.net/preg_replace carefully. You must use =
this format,
// because otherwise $words that are digits will cause undesired =
results.
$myword =3D preg_replace("/(%)?([\w\.]+)(%)?/", =
"\${1}".$word."\${3}", $field );
$tmp[] =3D $col." LIKE '".SQL_ESCAPE($myword)."'";
}
$sqlArray[] =3D " (".implode(" OR ",$tmp).") ";
}
$sql .=3D implode(" AND ", $sqlArray);
}
else //OR the words together
{
$sql =3D " AND ( ";
$sqlArray =3D array();
foreach($columns as $field)
{
$col =3D str_replace('%','',$field);

$tmp =3D array();
foreach($words as $word)
{
//[dv] read the http://php.net/preg_replace carefully. You must use =
this format,
// because otherwise $words that are digits will cause undesired =
results.
$myword =3D preg_replace("/(%)?([\w\.]+)(%)?/", =
"\${1}".$word."\${3}", $field );
$tmp[] =3D $col." LIKE '".SQL_ESCAPE($myword)."'";
}
$sqlArray[] =3D "(".implode(" OR ",$tmp).") ";
}
$sql .=3D implode(" OR ", $sqlArray);
$sql .=3D ") ";
}

return $sql;
}

/**
* returns an array of ENUM values from a table/column.
*
* @access public
* @return array of enum values
* @param string $database the database to connect to (agis_core) is the =
default
* @param string $table the name of the table to query
* @param string $column the name of the enum column to query
* @param boolean $sorted by default the results are sorted otherwise =
they are in the order of the enum schema
* @param boolean $indexed by default the key/value are the same string. =
if true, then key is an integer.
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 05/27/09
* @see select_box_array()
*/
function sql_enum_values_array($database=3D'agis_core', $table, $column, =
$sorted =3D true, $indexed =3D false)
{
$parameters['db_fetch_mode'] =3D DB_FETCHMODE_ORDERED; //this has to be =
an ordered array as we don't know the hash keys in an associative one
if ( $dbQuery =3D sql_query($database, "SHOW COLUMNS FROM ".$table." =
LIKE '".$column."'", null, false, $parameters) )
{
$EnumArray =3D array();
$EnumValues =3D $dbQuery[0][1];
$EnumValues =3D substr($EnumValues, 6, strlen($EnumValues)-8);
$EnumValues =3D str_replace("','",",",$EnumValues);

if ($indexed)
{
$EnumArray =3D explode(",",$EnumValues);
if ($sorted) sort($EnumArray);
}
else
{
$tmp =3D explode(",",$EnumValues);
foreach($tmp as $k =3D> $v) $EnumArray[$v] =3D $v;
if ($sorted) ksort($EnumArray);
}

return $EnumArray;
}
return false;
}

/**
* Insert a single row into a $database.$table from an array hash of =
column =3D> value pairs
*
* Usually these are the form field names corresponding to the =
equivallent SQL database column/field name.
* Use the string 'null' to insert a true NULL.
*
* @access public
* @return mixed inserted ID on success or result on failure
* @param string $database the database to connect to (agis_core) is the =
default
* @param string $table the name of the table to insert into
* @param hash $rows hash of column =3D> value pairs (optionally columns =
validated against $validate_columns)
* @param array $valid_columns array of column/field names. Also useful =
to limit SQL to certain forced columns to prevent unwanted tampering =
with 'default' columns for example.
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 2010-01-20
* @see sql_update(), sql_insert_id()
*/
function sql_insert($database=3D'agis_core_master', $table, $rows, =
$valid_columns=3Dnull)
{
ksort($rows); //not required, just easier to debug and find appropriate =
keys.

$validate_columns =3D (is_array($valid_columns)) ? true : false;

$temp =3D array();
$arrays =3D array();
foreach ($rows as $column =3D> $val)
{
if (is_array($val))
{
//if we only have one element in the array, then count it as any =
other $val
if (count($val) == 1)
$val =3D $val[0];
else
{
$arrays[$column] =3D $val;
unset($rows[$column]);
continue;
}
}

if ($validate_columns && !in_array($column, $valid_columns))
{
unset($rows[$column]);
echo "\n
sql_insert() ".$column." is not in the =
valid_columns list";
continue;
}

$val =3D trim($val);
if (!$val)
{
unset($rows[$column]);
continue;
}

if (strtolower($val) == 'null')
$temp[$column] =3D 'NULL';
else
$temp[$column] =3D "'".mysql_escape_string($val)."'";
}

$values =3D implode(', ',$temp);
$columns =3D "`".implode("`, `", array_keys($rows))."`";
$sql =3D "INSERT INTO `".$table."` (".$columns.") VALUES =
(".$values.")";
//echo $sql;

if (count($arrays))
{
echo "\n
sql_insert() has arrays with multiple elements that need =
to be handled still: ".implode(', ', array_keys($arrays))."";
foreach ($arrays as $a) var_dump($a);
}

$result =3D sql_query($database, $sql, null, false);
if ($result)
{
$iid =3D sql_insert_id();
if (is_numeric($iid)) return $iid;
}

return $result;
}


/**
* Update rows in $database.$table from an array hash of column =3D> =
value pairs
*
* Usually these are the form field names corresponding to the =
equivallent SQL database column/field name.
* Use the string 'null' to insert a true NULL.
*
* @access public
* @return mixed affected rows on success or result on failure
* @param string $database the database to connect to (agis_core) is =
the default
* @param string $table the name of the table to insert into
* @param hash $rows hash of column =3D> value pairs (optionally =
columns validated against $validate_columns)
* @param mixed hash of ID column/field name and record ID value [such =
as array('id_foo' =3D> 69)] OR string to craft custom WHERE clause
* @param array $valid_columns array of column/field names. Also useful =
to limit SQL to certain forced columns to prevent unwanted tampering =
with 'default' columns for example.
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 2010-01-20
* @see sql_insert()
*/
function sql_update($database=3D'agis_core_master', $table, $rows, =
$where, $single=3Dtrue, $valid_columns=3Dnull)
{
ksort($rows); //not required, just easier to debug and find appropriate =
keys.

$validate_columns =3D (is_array($valid_columns)) ? true : false;

$temp =3D array();
$arrays =3D array();
foreach ($rows as $column =3D> $val)
{
if (is_array($val))
{
//if we only have one element in the array, then count it as any =
other $val
if (count($val) == 1)
$val =3D $val[0];
else
{
$arrays[$column] =3D $val;
unset($rows[$column]);
continue;
}
}

if ($validate_columns && !in_array($column, $valid_columns))
{
unset($rows[$column]);
echo "\n
sql_update() ".$column." is not in the =
valid_columns list";
continue;
}

$val =3D trim($val);
if (!isset($val))
{
unset($rows[$column]);
continue;
}

if (strtolower($val) == 'null')
$temp[$column] =3D '`'.$column."` =3D NULL";
else
$temp[$column] =3D '`'.$column."` =3D =
'".mysql_escape_string($val)."'";
}

$sql =3D "UPDATE `".$table."` SET ".implode(', ', $temp);

if (is_array($where))
{
foreach ($where as $c =3D> $v)
$w[] =3D '`'.$c."` =3D '".mysql_escape_string($v)."'";
$sql .=3D " WHERE ".implode(' AND ', $w);
}
else $sql .=3D ' '.$where;

if ($single) $sql .=3D ' LIMIT 1';

if (count($arrays))
{
echo "\n
sql_update() has arrays with multiple elements that need =
to be handled still: ".implode(', ', array_keys($arrays))."";
foreach ($arrays as $a) var_dump($a);
}

$result =3D sql_query($database, $sql, null, false);
if ($result)
{
$ar =3D sql_affected_rows($database);
if (is_numeric($ar)) return $ar;
}

return $result;
}

/**
* Given a single dimension array, it will sql_escape and quote all the =
values,
* returning as a string suitable for a SQL IN() call.
*
* @access public
* @return string string of the form "'foo,'bar','bee','boo'"
* @param array $inarray a single dimension array (not hash) of values =
to quote/escape for an IN() function
* @author Daevid Vincent [daevid.vincent@panasonic.aero]
* @date 2009-01-19
*/
function sql_make_IN_from_array($inarray)
{
if (!is_array($inarray)) return '-1';

foreach ($inarray as $value)
$tmp[] =3D sql_escape($value);

return implode(', ',$tmp);
}

function connect_to_db_reliability() {
// RELIABILITY DATA BASE
// 'persistent' =3D> TRUE,## caused connection problems

if ($GLOBALS['DB_CONNECTIONS']['reliability']) return =
$GLOBALS['DB_CONNECTIONS']['reliability'];

global $global_db_dsn_reliability;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_reliability, $options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['reliability'] =3D $db_connection;
=
$GLOBALS['DB_CONNECTIONS']['reliability']->setFetchMode(DB_F ETCHMODE_ASSO=
C);

return $GLOBALS['DB_CONNECTIONS']['reliability'];
}

/**
* Connect to the agis_core database
*
* @param boolean $use_master use the master rather than the slave =
(false)
* @param boolean $die if there is a PEAR error, PHP dies on the spot. =
(true)
* @return PEAR::DB object
*/
function connect_to_db_agis_core($use_master=3DFALSE, $die=3Dtrue) {

if ($use_master == TRUE) return connect_to_db_agis_core_master();

if ($GLOBALS['DB_CONNECTIONS']['agis_core_slave']) return =
$GLOBALS['DB_CONNECTIONS']['agis_core_slave'];

global $global_db_dsn_agis_core_slave;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> TRUE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_agis_core_slave, =
$options);
if ( PEAR::isError( $db_connection ) )
{
//TODO: we should log this connection failure (and the others like =
this) to syslog.
//if ($die !== false) die( $db_connection->getMessage() );
if ($die !== false) die( sql_db_error($db_connection) );
}

$GLOBALS['DB_CONNECTIONS']['agis_core_slave'] =3D $db_connection;
if ($die !== false)
=
$GLOBALS['DB_CONNECTIONS']['agis_core_slave']->setFetchMode( DB_FETCHMODE_=
ASSOC);

return $GLOBALS['DB_CONNECTIONS']['agis_core_slave'];
}

/**
* Connect to the agis_core database
*
* @param boolean $die if there is a PEAR error, PHP dies on the spot. =
(true)
* @return PEAR::DB object
*/
function connect_to_db_agis_core_master($die=3Dtrue) {

if ($GLOBALS['DB_CONNECTIONS']['agis_core_master']) return =
$GLOBALS['DB_CONNECTIONS']['agis_core_master'];

global $global_db_dsn_agis_core_master;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> TRUE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_agis_core_master, =
$options);
if ( PEAR::isError( $db_connection ) )
{
//TODO: we should log this connection failure (and the others like =
this) to syslog.
//if ($die !== false) die( $db_connection->getMessage() );
if ($die !== false) die( sql_db_error($db_connection) );
}

$GLOBALS['DB_CONNECTIONS']['agis_core_master'] =3D $db_connection;
if ($die !== false)
=
$GLOBALS['DB_CONNECTIONS']['agis_core_master']->setFetchMode (DB_FETCHMODE=
_ASSOC);

return $GLOBALS['DB_CONNECTIONS']['agis_core_master'];
}

function connect_to_db_wfdmt() {
// wFDMT data base (tool that creates fdmt.dat, fdmt.xml)
if ($GLOBALS['DB_CONNECTIONS']['wfdmt']) return =
$GLOBALS['DB_CONNECTIONS']['wfdmt'];

global $global_db_dsn_wfdmt;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> TRUE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_wfdmt, $options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['wfdmt'] =3D $db_connection;
$GLOBALS['DB_CONNECTIONS']['wfdmt']->setFetchMode(DB_FETCHMO DE_ASSOC);

return $GLOBALS['DB_CONNECTIONS']['wfdmt'];
}

function connect_to_db_arinc_symonty() {
// SYMONTEK DATABASE FOR SMS/EMAIL FROM ARINC
if ($GLOBALS['DB_CONNECTIONS']['arinc_symonty']) return =
$GLOBALS['DB_CONNECTIONS']['arinc_symonty'];

global $global_db_dsn_arinc_symonty;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_arinc_symonty, =
$options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['arinc_symonty'] =3D $db_connection;
=
$GLOBALS['DB_CONNECTIONS']['arinc_symonty']->setFetchMode(DB _FETCHMODE_AS=
SOC);

return $GLOBALS['DB_CONNECTIONS']['arinc_symonty'];
}

function connect_to_db_pcube() {
// PCUBE DATA BASE FOR RELIABILITY (older oracle 8)

if ($GLOBALS['DB_CONNECTIONS']['pcube']) return =
$GLOBALS['DB_CONNECTIONS']['pcube'];

global $global_db_dsn_pcube;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_pcube, $options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['pcube'] =3D $db_connection;
$GLOBALS['DB_CONNECTIONS']['pcube']->setFetchMode(DB_FETCHMO DE_ASSOC);

return $GLOBALS['DB_CONNECTIONS']['pcube'];
}

function connect_to_db_fmr() {
// FMR DATA BASE - MMS Cabin Log Defects And Resolutions
//'persistent' =3D> TRUE,## May cause connection problems like =
reliability?
if ($GLOBALS['DB_CONNECTIONS']['fmr']) return =
$GLOBALS['DB_CONNECTIONS']['fmr'];

global $global_db_dsn_fmr;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_fmr, $options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['fmr'] =3D $db_connection;
$GLOBALS['DB_CONNECTIONS']['fmr']->setFetchMode(DB_FETCHMODE _ASSOC);

return $GLOBALS['DB_CONNECTIONS']['fmr'];
}

function connect_to_db_fogbugz() {
if ($GLOBALS['DB_CONNECTIONS']['fogbugz']) return =
$GLOBALS['DB_CONNECTIONS']['fogbugz'];

global $global_db_dsn_fogbugz;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_fogbugz, $options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['fogbugz'] =3D& $db_connection;
=
$GLOBALS['DB_CONNECTIONS']['fogbugz']->setFetchMode(DB_FETCH MODE_ASSOC);

return $GLOBALS['DB_CONNECTIONS']['fogbugz'];
}

function connect_to_db_product_safety() {
if ($GLOBALS['DB_CONNECTIONS']['product_safety']) return =
$GLOBALS['DB_CONNECTIONS']['product_safety'];

global $global_db_dsn_product_safety;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_product_safety, =
$options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['product_safety'] =3D $db_connection;
=
$GLOBALS['DB_CONNECTIONS']['product_safety']->setFetchMode(D B_FETCHMODE_A=
SSOC);

return $GLOBALS['DB_CONNECTIONS']['product_safety'];
}

function connect_to_db_synergy_master() {
// myIFE Submit Issue to synergy_master/Telelogic

if ($GLOBALS['DB_CONNECTIONS']['synergy_master']) return =
$GLOBALS['DB_CONNECTIONS']['synergy_master'];

global $global_db_dsn_synergy_master;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_synergy_master, =
$options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['synergy_master'] =3D $db_connection;
=
$GLOBALS['DB_CONNECTIONS']['synergy_master']->setFetchMode(D B_FETCHMODE_A=
SSOC);

return $GLOBALS['DB_CONNECTIONS']['synergy_master'];
}

function connect_to_db_synergy_reference() {
// myIFE Submit Issue to synergy_reference/Telelogic

if ($GLOBALS['DB_CONNECTIONS']['synergy_reference']) return =
$GLOBALS['DB_CONNECTIONS']['synergy_reference'];

global $global_db_dsn_synergy_reference;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL
);

$db_connection =3D& DB::connect($global_db_dsn_synergy_reference, =
$options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['synergy_reference'] =3D $db_connection;
=
$GLOBALS['DB_CONNECTIONS']['synergy_reference']->setFetchMod e(DB_FETCHMOD=
E_ASSOC);

return $GLOBALS['DB_CONNECTIONS']['synergy_reference'];
}

/**
connect_to_db_pana_session_pw()

Connects to the password database, which holds
users (as opposed to the session database,
which holds sessions).

This is just a utility function for internal
use. You probably don't need to use this functiion.

Args: None.
Returns:The PEAR::DB $db_connection. It will die()
with an error message if there was a db error.
*/
function connect_to_db_pana_session_pw() {

if ($GLOBALS['DB_CONNECTIONS']['pana_session_password_db']) return =
$GLOBALS['DB_CONNECTIONS']['pana_session_password_db'];

/* Look it up in BlackComb: */

/* NOTE: Connecting as type "mssql" did not work for me.
That was on my WinXP workstation. Switching it to
ODBC (below) made it work. I think things will
be different under Linux, though.
$pana_session_password_dsn =3D array( 'phptype' =3D> 'odbc',
'dbsyntax' =3D> 'mssql',
'database' =3D> 'DRIVER=3D{SQL Server};SERVER=3DWEBDEV',
'username' =3D> 'agis',
'password' =3D> 'Please do not forget the AGIS SQL Server Password',
'persistent' =3D> TRUE );
*/
global $pana_session_password_dsn;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> FALSE,
'portability' =3D> DB_PORTABILITY_ALL,
);

$db_connection =3D& DB::connect($pana_session_password_dsn, $options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['pana_session_password_db'] =3D =
$db_connection;
=
$GLOBALS['DB_CONNECTIONS']['pana_session_password_db']->setF etchMode(DB_F=
ETCHMODE_ASSOC);

return $GLOBALS['DB_CONNECTIONS']['pana_session_password_db'];
}

/**
connect_to_db_pana_session_db()

Connects to the session database, which holds
session data (as opposed to the password
database(s), which holds users).

This is just a utility function for internal
use. You probably don't need to use this function,
but you may need to edit it to match the settings
for your particular environment.

Args: None.
Returns:The PEAR::DB $db_connection. It
will die() with an error message if there was a db error.
*/
function connect_to_db_pana_session_db() {

if ($GLOBALS['DB_CONNECTIONS']['pana_session_db']) return =
$GLOBALS['DB_CONNECTIONS']['pana_session_db'];
/*
$dsn =3D array(
'phptype' =3D> 'mysql',
'username' =3D> 'root',
'password' =3D> 'noway',
'hostspec' =3D> 'dev-agis03',
'database' =3D> 'agis_core_0_0_2_3f',
); */
global $pana_session_session_dsn;

$options =3D array(
'debug' =3D> 0,
'persistent' =3D> TRUE,
'portability' =3D> DB_PORTABILITY_ALL,
);

$db_connection =3D& DB::connect($pana_session_session_dsn, $options);
if ( PEAR::isError( $db_connection ) )
die( sql_db_error($db_connection) );

$GLOBALS['DB_CONNECTIONS']['pana_session_db'] =3D $db_connection;
=
$GLOBALS['DB_CONNECTIONS']['pana_session_db']->setFetchMode( DB_FETCHMODE_=
ASSOC);

return $GLOBALS['DB_CONNECTIONS']['pana_session_db'];
}
?>


------=_NextPart_000_00C8_01CAA409.C7A7BC00
Content-Type: text/plain; charset=us-ascii

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php
------=_NextPart_000_00C8_01CAA409.C7A7BC00--

Re: database abstraction layer

am 02.02.2010 22:59:36 von Paul M Foster

On Tue, Feb 02, 2010 at 01:15:22PM -0800, Daevid Vincent wrote:

> > -----Original Message-----
> > From: Lars Nielsen [mailto:lars@mit-web.dk]
> > Sent: Tuesday, February 02, 2010 12:24 PM
> > To: php-general@lists.php.net
> > Subject: [PHP] database abstraction layer
> >
> > Hi List
> >
> > I am trying to make a Database Abstraction Layer so I can which the DB
> > of my application between MySQL and Postgresql. I have been looking at
> > the way phpBB does it, and it seems that it is only then php-functions
> > which are different. The SQL seems to be the same.
> >
> > Is it save to assume that I can use the same SQL, or should i
> > make some
> > exceptions?
> >
> > Regards
> > Lars Nielsen
>
> There are differences in the actual schema between mySQL and Postgress.
>
> At least there were a few years back when we looked at converting. In the
> end, we decided it was too much hassle to switch all our code and database
> tables, so just coughed up the licensing for mysql (we were shipping mysql
> on our appliance).
>
> So, before you jump into writing all this code, I would first try to make
> your app run in postgress and find out about which mySQL statements are
> 'extensions' to ANSI standard. Case sensitivity was a huge issue for us, as
> in one of those RDBMS was very particular about it. There were some other
> issues that I can't remember ATM, but perhaps they've been addressed by
> now.
>
> One thing I would maybe suggest is (what I do), write a wrapper around your
> wrapper -- AKA "Double Bag It". :)
>
> Here at Panasonic Avionics (PAC) we use the PEAR::DB class (the really old
> version) since we have to interface with mySQL, SQL Server, Oracle (two
> versions). That's where PEAR::DB comes in. However, it's very crude and you
> have a lot of redundant code in every page. Like this:
> http://pear.php.net/manual/en/package.database.db.intro-fetc h.php
> You always have to open a connection, test for errors, do the query, test
> for errors, fetch the rows, etc..
>
> When I came on board a year ago, I put an end to that micky mouse crap. I
> wrote a nice db.inc.php wrapper that handles all that sort of thing, and
> then pumps it up like it's on steroids. I added auto-reconnect in case the
> connection dropped. I added color-coded SQL output with substitution for
> the '?'. I added a last_insert_it() routine which is proprietary to mySQL
> BTW (speaking of incompatibilities). I added routines to get an Enum
> column, or to get a simple array pairing, etc. It can even force reads from
> slave and writes to master! It pretty much kicks ass.
>
> Just simply do this:
>
> $myfoo = sql_query('agis_core', 'SELECT * FROM foo WHERE bar = ?', $bar);
>
> All the minutia is handled for you and $myfoo is now an array of your
> results. :)
>
> So, now we code using my wrapper and should we want to switch out the DBAL
> later to a more modern one, we just change OUR wrapper calls. There is
> minimal overhead, and the pros FAR outweigh any cons.

+1

Though I would use PDO instead of Pear::DB. Also
sequential/autoincrement values are differently specified in
MySQL/PostgreSQL. I did something similar to Daevid using PDO, and also
wrote a "last_insert_id()" function. It requires the database class to
know what flavor of SQL it's using, and implements the proper function
to return the ID based on that (PostgreSQL has its own version). I would
also suggest that failed queries and commands (not just no useful
result) terminate script execution. PDO functions generally return false
when you feed them absolute garbage, and you don't want to try to
continue execution after that.

An awful lot of SQL is the same between engines, but there are a lot of
edge cases. The only other alternative is something like Active Record,
and I personally wouldn't wish that on anyone. My personal opinion is
that a programmer should learn the SQL dialect he's working with and use
it, rather than something like Active Record. Internally we use
PostgreSQL exclusively. The only time I use MySQL is for customer sites
where their hosting companies don't support PostgreSQL. In that case, I
simply write SQL targetted at MySQL's dialect. It all goes through the
same database class to perform error checking and results return.

Paul

--
Paul M. Foster

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 02.02.2010 23:19:29 von Rene Veerman

oh, on using adodb.sf.net and 0-overhead for jumping between mysql and
postgresql;

keep all your queries to as simple & early-standard sql as possible.
the auto_increment incompatibilities can be circumvented with a
relatively simple
function getMax($table, $field) {

in adodb, you'd loop through a huge dataset like this, ensuring proper
comms & mem-usage betweeen the db server and php.

$dbConn = adoEasyConnection(); //returns adodb connection object,
initialized with values from config.php
$sql = 'select * from data where bladiebla="yep"';
$q = $dbConn->execute ($sql);
if (!$q || $q->EOF) {
$errorMsg = $dbConn->ErrorMsg();
handleError ($errorMsg, $sql);
} else {
while (!$q->EOF) {

//use $q->fields['field_name']; // from the currently loaded record

$q->MoveNext();
}
}

for short resultsets you could call $q->getRows() to get all the rows
returned as 1 multilevel array.

instead of difficult outer-join constructs and stored procedures,
(that are not portable), i find it much easier to aim for small
intermediate
computation-result arrays in php, which are used to construct
fetch-final-result-sql on the fly.
itnermediate &/ result arrays can be stored on db / disk in json, too ;)

i built a cms that can store media items and their meta-properties in db,
with the ability to update some meta-properties of an arbitrary
selection of media items to new
values, in 1 go.
i had no problem switching from postgresql to mysql, at all, using the
methods described above.

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 02.02.2010 23:46:03 von Ashley Sheridan

--=-WyfY1ACSUMc2B4LKjQim
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:

> function getMax($table, $field)


If I saw this sort of code I'd be appalled! It's possibly the worst way
to get the auto increment value. You won't notice it testing the site
out on your own, but all hell will break loose when you start getting a
lot of hits, and two people cause an auto increment at the same time!

Thanks,
Ash
http://www.ashleysheridan.co.uk



--=-WyfY1ACSUMc2B4LKjQim--

Re: database abstraction layer

am 03.02.2010 00:05:06 von Rene Veerman

--00032555b95af48738047ea621c0
Content-Type: text/plain; charset=ISO-8859-1

i haven't had the pleasure yet of writing for sites that generate so many
hits/sec that
they'd update the max value of any table at exactly the same time.

i usually ask for the max value about 2 milliseconds before doing the
insert.
And if the insert fails, i can auto-retry via a wrapper function after
sleep(rand(1,3));
I dare say i could work this way at facebook (not that i really want to,
happy with where i am)

On Tue, Feb 2, 2010 at 11:46 PM, Ashley Sheridan
wrote:

> On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:
>
> function getMax($table, $field)
>
>
> If I saw this sort of code I'd be appalled! It's possibly the worst way to
> get the auto increment value. You won't notice it testing the site out on
> your own, but all hell will break loose when you start getting a lot of
> hits, and two people cause an auto increment at the same time!
>
> Thanks,
> Ash
> http://www.ashleysheridan.co.uk
>
>
>

--00032555b95af48738047ea621c0--

Re: database abstraction layer

am 03.02.2010 00:11:45 von Ashley Sheridan

--=-p+uodJCiY16wl/S0SvCx
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

On Wed, 2010-02-03 at 00:05 +0100, Rene Veerman wrote:

> i haven't had the pleasure yet of writing for sites that generate so many
> hits/sec that
> they'd update the max value of any table at exactly the same time.
>
> i usually ask for the max value about 2 milliseconds before doing the
> insert.
> And if the insert fails, i can auto-retry via a wrapper function after
> sleep(rand(1,3));
> I dare say i could work this way at facebook (not that i really want to,
> happy with where i am)
>
> On Tue, Feb 2, 2010 at 11:46 PM, Ashley Sheridan
> wrote:
>
> > On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:
> >
> > function getMax($table, $field)
> >
> >
> > If I saw this sort of code I'd be appalled! It's possibly the worst way to
> > get the auto increment value. You won't notice it testing the site out on
> > your own, but all hell will break loose when you start getting a lot of
> > hits, and two people cause an auto increment at the same time!
> >
> > Thanks,
> > Ash
> > http://www.ashleysheridan.co.uk
> >
> >
> >


I saw it happen on a site that was getting only about 3000 hits a day.
It just takes the right combination of circumstances and it all goes
pear shaped. You really should get out of the habit of doing it.

Thanks,
Ash
http://www.ashleysheridan.co.uk



--=-p+uodJCiY16wl/S0SvCx--

Re: database abstraction layer

am 03.02.2010 00:17:50 von Rene Veerman

--000325559b1e81a6fd047ea64f48
Content-Type: text/plain; charset=ISO-8859-1

and after the sleep(rand(1,3)) it might need a short loop like this;
$rnd = rand(1,99999); $a=0;
for ($i=0; $i<$rnd; $i++) { $a++ }

to further randomize the retry attempt..


On Wed, Feb 3, 2010 at 12:05 AM, Rene Veerman wrote:

> i haven't had the pleasure yet of writing for sites that generate so many
> hits/sec that
> they'd update the max value of any table at exactly the same time.
>
> i usually ask for the max value about 2 milliseconds before doing the
> insert.
> And if the insert fails, i can auto-retry via a wrapper function after
> sleep(rand(1,3));
> I dare say i could work this way at facebook (not that i really want
> to, happy with where i am)
>
>
> On Tue, Feb 2, 2010 at 11:46 PM, Ashley Sheridan > > wrote:
>
>> On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:
>>
>> function getMax($table, $field)
>>
>>
>> If I saw this sort of code I'd be appalled! It's possibly the worst way to
>> get the auto increment value. You won't notice it testing the site out on
>> your own, but all hell will break loose when you start getting a lot of
>> hits, and two people cause an auto increment at the same time!
>>
>> Thanks,
>> Ash
>> http://www.ashleysheridan.co.uk
>>
>>
>>
>

--000325559b1e81a6fd047ea64f48--

Re: database abstraction layer

am 03.02.2010 00:18:04 von Ashley Sheridan

--=-rxSkvAZd0q4tEfMmMqI0
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

On Wed, 2010-02-03 at 00:17 +0100, Rene Veerman wrote:

> and after the sleep(rand(1,3)) it might need a short loop like this;
> $rnd = rand(1,99999); $a=0;
> for ($i=0; $i<$rnd; $i++) { $a++ }
>
> to further randomize the retry attempt..
>
>
> On Wed, Feb 3, 2010 at 12:05 AM, Rene Veerman wrote:
>
> > i haven't had the pleasure yet of writing for sites that generate so many
> > hits/sec that
> > they'd update the max value of any table at exactly the same time.
> >
> > i usually ask for the max value about 2 milliseconds before doing the
> > insert.
> > And if the insert fails, i can auto-retry via a wrapper function after
> > sleep(rand(1,3));
> > I dare say i could work this way at facebook (not that i really want
> > to, happy with where i am)
> >
> >
> > On Tue, Feb 2, 2010 at 11:46 PM, Ashley Sheridan > > > wrote:
> >
> >> On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:
> >>
> >> function getMax($table, $field)
> >>
> >>
> >> If I saw this sort of code I'd be appalled! It's possibly the worst way to
> >> get the auto increment value. You won't notice it testing the site out on
> >> your own, but all hell will break loose when you start getting a lot of
> >> hits, and two people cause an auto increment at the same time!
> >>
> >> Thanks,
> >> Ash
> >> http://www.ashleysheridan.co.uk
> >>
> >>
> >>
> >


The problem is where 2 people choose the same instant to perform an
action on your site that inserts a record into your db. The db engine
inserts them one after the other, and then responds about the max(id) to
your PHP script. Then, you now have 2 people who have the same max(id)
retrieved, but one of the values is wrong.

Thanks,
Ash
http://www.ashleysheridan.co.uk



--=-rxSkvAZd0q4tEfMmMqI0--

Re: database abstraction layer

am 03.02.2010 00:18:38 von Rene Veerman

--0015174be6bc63e552047ea652e4
Content-Type: text/plain; charset=ISO-8859-1

eh thats "randomize the timing of the retry attempt"..


On Wed, Feb 3, 2010 at 12:17 AM, Rene Veerman wrote:

> and after the sleep(rand(1,3)) it might need a short loop like this;
> $rnd = rand(1,99999); $a=0;
> for ($i=0; $i<$rnd; $i++) { $a++ }
>
> to further randomize the retry attempt..
>
>

--0015174be6bc63e552047ea652e4--

Re: database abstraction layer

am 03.02.2010 00:19:10 von Ashley Sheridan

--=-SHc/5pl92VLRJk2wjcQm
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

On Wed, 2010-02-03 at 00:21 +0100, Rene Veerman wrote:

> the auto_increment sytnax is not uniform across servers, is it?
>
> On Wed, Feb 3, 2010 at 12:11 AM, Ashley Sheridan
> wrote:
>
> > I saw it happen on a site that was getting only about 3000 hits a day. It
> > just takes the right combination of circumstances and it all goes pear
> > shaped. You really should get out of the habit of doing it.
> >


It is a MySQL only function. MSSQL has @@IDENTITY, not sure how other
engines implement it.

Thanks,
Ash
http://www.ashleysheridan.co.uk



--=-SHc/5pl92VLRJk2wjcQm--

Re: database abstraction layer

am 03.02.2010 00:21:12 von Rene Veerman

--0003255566569567e0047ea65ba5
Content-Type: text/plain; charset=ISO-8859-1

the auto_increment sytnax is not uniform across servers, is it?

On Wed, Feb 3, 2010 at 12:11 AM, Ashley Sheridan
wrote:

> I saw it happen on a site that was getting only about 3000 hits a day. It
> just takes the right combination of circumstances and it all goes pear
> shaped. You really should get out of the habit of doing it.
>

--0003255566569567e0047ea65ba5--

Re: database abstraction layer

am 03.02.2010 00:31:03 von Rene Veerman

--00032555942ac63268047ea67ea1
Content-Type: text/plain; charset=ISO-8859-1

On Wed, Feb 3, 2010 at 12:18 AM, Ashley Sheridan
wrote:

> The problem is where 2 people choose the same instant to perform an
> action on your site that inserts a record into your db. The db engine
> inserts them one after the other, and then responds about the max(id) to
> your PHP script. Then, you now have 2 people who have the same max(id)
> retrieved, but one of the values is wrong.
>

well, i only use getmaxid()s for inserts.
the timelag between getmaxid() and the insert is so small it'd take 300-800
insert-requests/sec
(on that particular table) before an error condition would arise.
in which case, a tested piece of sql would fail, and can be routed through
the retry functions .
These would imo provide ample timing re-randomization, aswell as a measure
of stress-relief for both php and mysql server.
You may correct me if i'm wrong :)

BTW: php core developers: can we have a sleep() that accepts a float? :)

--00032555942ac63268047ea67ea1--

Re: database abstraction layer

am 03.02.2010 00:35:04 von Ashley Sheridan

--=-8r5Cn264ZNe5531Z67nz
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

On Wed, 2010-02-03 at 00:31 +0100, Rene Veerman wrote:

> On Wed, Feb 3, 2010 at 12:18 AM, Ashley Sheridan
> wrote:
>
> > The problem is where 2 people choose the same instant to perform an
> > action on your site that inserts a record into your db. The db engine
> > inserts them one after the other, and then responds about the max(id) to
> > your PHP script. Then, you now have 2 people who have the same max(id)
> > retrieved, but one of the values is wrong.
> >
>
> well, i only use getmaxid()s for inserts.
> the timelag between getmaxid() and the insert is so small it'd take 300-800
> insert-requests/sec
> (on that particular table) before an error condition would arise.
> in which case, a tested piece of sql would fail, and can be routed through
> the retry functions .
> These would imo provide ample timing re-randomization, aswell as a measure
> of stress-relief for both php and mysql server.
> You may correct me if i'm wrong :)
>
> BTW: php core developers: can we have a sleep() that accepts a float? :)


It's the reason transactions exist, to prevent things happening like
this. When you have two actions where one is dependent on the other,
unless you have a way to tie them together so that they can't be broken,
you run the risk of collisions.

Thanks,
Ash
http://www.ashleysheridan.co.uk



--=-8r5Cn264ZNe5531Z67nz--

Re: database abstraction layer

am 03.02.2010 00:48:52 von Rene Veerman

--00151758f3c480aa85047ea6be9a
Content-Type: text/plain; charset=ISO-8859-1

On Wed, Feb 3, 2010 at 12:35 AM, Ashley Sheridan
wrote:

> It's the reason transactions exist, to prevent things happening like this.
> When you have two actions where one is dependent on the other, unless you
> have a way to tie them together so that they can't be broken, you run the
> risk of collisions.
>
>
Yea, and i wish they'd standarized features like that across sql servers.
But they haven't, so i avoid them like the plague.

Whatever dependencies and threading problems might arise, there's always the
principle that says:

If it doesn't work whlie it should work and threading-timing problems are
the only possible cause, then
by delay by a random timeperiod and retry the query.

In really advanced cases, one can work with last-modified timestamps and/or
build up a simple sort of work-queue (also in a table),
whereby threads inform each other of the status of their computations.

--00151758f3c480aa85047ea6be9a--

Re: database abstraction layer

am 03.02.2010 01:50:20 von Phpster

Yep, love those race conditions. We have them all over the app cuz the
app ciders don't know shit!

Bastien

Sent from my iPod

On Feb 2, 2010, at 5:46 PM, Ashley Sheridan
wrote:

> On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:
>
>> function getMax($table, $field)
>
>
> If I saw this sort of code I'd be appalled! It's possibly the worst
> way
> to get the auto increment value. You won't notice it testing the site
> out on your own, but all hell will break loose when you start
> getting a
> lot of hits, and two people cause an auto increment at the same time!
>
> Thanks,
> Ash
> http://www.ashleysheridan.co.uk
>
>

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 01:51:29 von Phpster

Good lord that is exacty the same logic applied in our app. It only
takes about 100 users to create the issue.

Bastien

Sent from my iPod

On Feb 2, 2010, at 6:05 PM, Rene Veerman wrote:

> i haven't had the pleasure yet of writing for sites that generate so
> many
> hits/sec that
> they'd update the max value of any table at exactly the same time.
>
> i usually ask for the max value about 2 milliseconds before doing the
> insert.
> And if the insert fails, i can auto-retry via a wrapper function after
> sleep(rand(1,3));
> I dare say i could work this way at facebook (not that i really
> want to,
> happy with where i am)
>
> On Tue, Feb 2, 2010 at 11:46 PM, Ashley Sheridan
> wrote:
>
>> On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:
>>
>> function getMax($table, $field)
>>
>>
>> If I saw this sort of code I'd be appalled! It's possibly the worst
>> way to
>> get the auto increment value. You won't notice it testing the site
>> out on
>> your own, but all hell will break loose when you start getting a
>> lot of
>> hits, and two people cause an auto increment at the same time!
>>
>> Thanks,
>> Ash
>> http://www.ashleysheridan.co.uk
>>
>>
>>

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 02:31:21 von Robert Cummings

Rene Veerman wrote:
> i haven't had the pleasure yet of writing for sites that generate so many
> hits/sec that
> they'd update the max value of any table at exactly the same time.
>
> i usually ask for the max value about 2 milliseconds before doing the
> insert.
> And if the insert fails, i can auto-retry via a wrapper function after
> sleep(rand(1,3));
> I dare say i could work this way at facebook (not that i really want to,
> happy with where i am)

This is a race condition... all you need are two hits per day... one
from person A and one from person B. If they happen within short enough
temporal proximity then the race is on.

Now... a big fat lock around the table before the request for max ID and
the insert query ought to mitigate your issue. Although I wouldn't call
my function getMaxId() I would call it getNextId() :)

Cheers,
Rob.
--
http://www.interjinn.com
Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 02:34:26 von Robert Cummings

Rene Veerman wrote:
> eh thats "randomize the timing of the retry attempt"..
>
>
> On Wed, Feb 3, 2010 at 12:17 AM, Rene Veerman wrote:
>
>> and after the sleep(rand(1,3)) it might need a short loop like this;
>> $rnd = rand(1,99999); $a=0;
>> for ($i=0; $i<$rnd; $i++) { $a++ }
>>
>> to further randomize the retry attempt..

While this decreases the probability of a collision you're just setting
up another race. They teach about this stuff in computer science...
right around first year... or at least they once upon a time did.

Cheers,
Rob.
--
http://www.interjinn.com
Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 02:36:38 von Robert Cummings

Rene Veerman wrote:
> On Wed, Feb 3, 2010 at 12:18 AM, Ashley Sheridan
> wrote:
>
>> The problem is where 2 people choose the same instant to perform an
>> action on your site that inserts a record into your db. The db engine
>> inserts them one after the other, and then responds about the max(id) to
>> your PHP script. Then, you now have 2 people who have the same max(id)
>> retrieved, but one of the values is wrong.
>>
>
> well, i only use getmaxid()s for inserts.
> the timelag between getmaxid() and the insert is so small it'd take 300-800
> insert-requests/sec
> (on that particular table) before an error condition would arise.
> in which case, a tested piece of sql would fail, and can be routed through
> the retry functions .
> These would imo provide ample timing re-randomization, aswell as a measure
> of stress-relief for both php and mysql server.
> You may correct me if i'm wrong :)
>
> BTW: php core developers: can we have a sleep() that accepts a float? :)

This works right up until someone else maintains this system and can't
understand why the database is corrupt. Then they find find your
database layer and want to stab themselves :)

Cheers,
Rob.
--
http://www.interjinn.com
Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 02:40:11 von Robert Cummings

Rene Veerman wrote:
> On Wed, Feb 3, 2010 at 12:35 AM, Ashley Sheridan
> wrote:
>
>> It's the reason transactions exist, to prevent things happening like this.
>> When you have two actions where one is dependent on the other, unless you
>> have a way to tie them together so that they can't be broken, you run the
>> risk of collisions.
>>
>>
> Yea, and i wish they'd standarized features like that across sql servers.
> But they haven't, so i avoid them like the plague.

This is why you're creating your own layer... to smooth the wrinkles
between the systems via your abstracted layer. That isn't usually a good
reason for you to do it improperly.

> Whatever dependencies and threading problems might arise, there's always the
> principle that says:
>
> If it doesn't work whlie it should work and threading-timing problems are
> the only possible cause, then
> by delay by a random timeperiod and retry the query.

Yikes, please cite your reference for that horrible advice.

> In really advanced cases, one can work with last-modified timestamps and/or
> build up a simple sort of work-queue (also in a table),
> whereby threads inform each other of the status of their computations.

Wow... this gets ever more complex to do something simple.

Cheers,
Rob.
--
http://www.interjinn.com
Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 02:41:34 von Robert Cummings

Phpster wrote:
> Yep, love those race conditions. We have them all over the app cuz the
> app ciders don't know shit!

Mmmm... apple cider... to cure what ails you or at least get you drunk
enough to not care about the horrible race conditions >:)

Cheers,
Rob.
--
http://www.interjinn.com
Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 02:45:30 von Robert Cummings

Rene Veerman wrote:
> On Wed, Feb 3, 2010 at 12:18 AM, Ashley Sheridan
> wrote:
>
>> The problem is where 2 people choose the same instant to perform an
>> action on your site that inserts a record into your db. The db engine
>> inserts them one after the other, and then responds about the max(id) to
>> your PHP script. Then, you now have 2 people who have the same max(id)
>> retrieved, but one of the values is wrong.
>>
>
> well, i only use getmaxid()s for inserts.
> the timelag between getmaxid() and the insert is so small it'd take 300-800
> insert-requests/sec
> (on that particular table) before an error condition would arise.
> in which case, a tested piece of sql would fail, and can be routed through
> the retry functions .
> These would imo provide ample timing re-randomization, aswell as a measure
> of stress-relief for both php and mysql server.
> You may correct me if i'm wrong :)
>
> BTW: php core developers: can we have a sleep() that accepts a float? :)

Is the following too difficult?


function fsleep( $seconds )
{
usleep( 1000000 * $seconds );
}

fsleep( .003 );

?>

Cheers,
Rob.
--
http://www.interjinn.com
Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 02:58:16 von Michael Peters

Robert Cummings wrote:
> Rene Veerman wrote:
>> eh thats "randomize the timing of the retry attempt"..
>>
>>
>> On Wed, Feb 3, 2010 at 12:17 AM, Rene Veerman wrote:
>>
>>> and after the sleep(rand(1,3)) it might need a short loop like this;
>>> $rnd = rand(1,99999); $a=0;
>>> for ($i=0; $i<$rnd; $i++) { $a++ }
>>>
>>> to further randomize the retry attempt..
>
> While this decreases the probability of a collision you're just setting
> up another race. They teach about this stuff in computer science...
> right around first year... or at least they once upon a time did.
>
> Cheers,
> Rob.

Einstein I believe said something along the lines of

"A smart person solves a problem.
A wise person avoids it in the first place"

Might not have been Einstein, but anyway ...

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 03:05:51 von Robert Cummings

Michael A. Peters wrote:
> Robert Cummings wrote:
>> Rene Veerman wrote:
>>> eh thats "randomize the timing of the retry attempt"..
>>>
>>>
>>> On Wed, Feb 3, 2010 at 12:17 AM, Rene Veerman wrote:
>>>
>>>> and after the sleep(rand(1,3)) it might need a short loop like this;
>>>> $rnd = rand(1,99999); $a=0;
>>>> for ($i=0; $i<$rnd; $i++) { $a++ }
>>>>
>>>> to further randomize the retry attempt..
>> While this decreases the probability of a collision you're just setting
>> up another race. They teach about this stuff in computer science...
>> right around first year... or at least they once upon a time did.
>>
>> Cheers,
>> Rob.
>
> Einstein I believe said something along the lines of
>
> "A smart person solves a problem.
> A wise person avoids it in the first place"
>
> Might not have been Einstein, but anyway ...

Do you mean the following quote?

The difference between a smart person and a wise person is that
a smart person knows what to say and a wise person knows whether
or not to say it.

Cheers,
Rob.
--
http://www.interjinn.com
Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 03:27:29 von Michael Peters

Robert Cummings wrote:
*snip*
>>
>> Einstein I believe said something along the lines of
>>
>> "A smart person solves a problem.
>> A wise person avoids it in the first place"
>>
>> Might not have been Einstein, but anyway ...
>
> Do you mean the following quote?
>
> The difference between a smart person and a wise person is that
> a smart person knows what to say and a wise person knows whether
> or not to say it.
>
> Cheers,
> Rob.

No - this is it:

http://www.famousquotes.com/show/1022246/

“A clever person solves a problem. A wise person avoids it.”

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 03:53:24 von Paul M Foster

On Tue, Feb 02, 2010 at 11:19:29PM +0100, Rene Veerman wrote:

> oh, on using adodb.sf.net and 0-overhead for jumping between mysql and
> postgresql;
>
> keep all your queries to as simple & early-standard sql as possible.
> the auto_increment incompatibilities can be circumvented with a
> relatively simple
> function getMax($table, $field) {

This approach is guaranteed to run into race conditions. The only way to
positively ensure proper results is to let the DB engine take care of it
itself. The engines typically track incremental IDs by session, which
prevents you from getting an ID someone else has just used.

>
> in adodb, you'd loop through a huge dataset like this, ensuring proper
> comms & mem-usage betweeen the db server and php.
>
> $dbConn = adoEasyConnection(); //returns adodb connection object,
> initialized with values from config.php
> $sql = 'select * from data where bladiebla="yep"';
> $q = $dbConn->execute ($sql);
> if (!$q || $q->EOF) {
> $errorMsg = $dbConn->ErrorMsg();
> handleError ($errorMsg, $sql);
> } else {
> while (!$q->EOF) {
>
> //use $q->fields['field_name']; // from the currently loaded record
>
> $q->MoveNext();
> }
> }
>
> for short resultsets you could call $q->getRows() to get all the rows
> returned as 1 multilevel array.
>
> instead of difficult outer-join constructs and stored procedures,
> (that are not portable), i find it much easier to aim for small
> intermediate
> computation-result arrays in php, which are used to construct
> fetch-final-result-sql on the fly.
> itnermediate &/ result arrays can be stored on db / disk in json, too ;)

For MySQL I would agree. But I prefer the ability to use the full SQL
standard when manipulating a database; that is, all joins, foreign keys,
etc. For that same reason, I tend to avoid stored procedures as well. If
I have to do things like handle foreign key constraints in my PHP code
(instead of letting the DBMS handle them), I have to wonder why I'm even
using a "relational" DBMS.

Paul

--
Paul M. Foster

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 04:46:39 von Phpster

Lol, damn iPod corrections. The app designers is what was meant.

Bastien

Sent from my iPod

On Feb 2, 2010, at 8:41 PM, Robert Cummings
wrote:

> Phpster wrote:
>> Yep, love those race conditions. We have them all over the app cuz
>> the app ciders don't know shit!
>
> Mmmm... apple cider... to cure what ails you or at least get you
> drunk enough to not care about the horrible race conditions >:)
>
> Cheers,
> Rob.
> --
> http://www.interjinn.com
> Application and Templating Framework for PHP

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 05:49:50 von Jochem Maas

Op 2/3/10 12:19 AM, Ashley Sheridan schreef:
> On Wed, 2010-02-03 at 00:21 +0100, Rene Veerman wrote:
>
>> the auto_increment sytnax is not uniform across servers, is it?
>>
>> On Wed, Feb 3, 2010 at 12:11 AM, Ashley Sheridan
>> wrote:
>>
>>> I saw it happen on a site that was getting only about 3000 hits a day. It
>>> just takes the right combination of circumstances and it all goes pear
>>> shaped. You really should get out of the habit of doing it.
>>>
>
>
> It is a MySQL only function. MSSQL has @@IDENTITY, not sure how other
> engines implement it.

firebird does it via what they call 'generators', 2 seconds of searching
shows postgres has this:

CREATE TABLE tableName (
id serial PRIMARY KEY,
name varchar(50) UNIQUE NOT NULL,
dateCreated timestamp DEFAULT current_timestamp
);

you can bet you ass that every other DB out there that's worth it's salt
has atomic id incrementor functionality exposed in some way or other.

@Rene: all that talk of maxId functions and random retries etc, etc, is complete pooh.
don't do it, **please** use the proper tools provided by the DB in question.

>
> Thanks,
> Ash
> http://www.ashleysheridan.co.uk
>
>
>


--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 06:39:29 von Rene Veerman

On Wed, Feb 3, 2010 at 5:49 AM, Jochem Maas wrote:
> you can bet you ass that every other DB out there that's worth it's salt
> has atomic id incrementor functionality exposed in some way or other.
>
> @Rene: all that talk of maxId functions and random retries etc, etc, is complete pooh.
> don't do it, **please** use the proper tools provided by the DB in question.
>

i just checked how my 1 app that did generate over a million hits/day
(all with an insert for stats purposes)
for a few weeks handles the getMax issue, and i see i did use mysql's
auto_increment there.

i suppose the difference in syntax between sql servers for this one is
acceptable.

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 07:12:16 von Paul M Foster

On Wed, Feb 03, 2010 at 06:39:29AM +0100, Rene Veerman wrote:

> On Wed, Feb 3, 2010 at 5:49 AM, Jochem Maas wrote:
> > you can bet you ass that every other DB out there that's worth it's salt
> > has atomic id incrementor functionality exposed in some way or other.
> >
> > @Rene: all that talk of maxId functions and random retries etc, etc,
> is complete pooh.
> > don't do it, **please** use the proper tools provided by the DB in
> question.
> >
>
> i just checked how my 1 app that did generate over a million hits/day
> (all with an insert for stats purposes)
> for a few weeks handles the getMax issue, and i see i did use mysql's
> auto_increment there.
>
> i suppose the difference in syntax between sql servers for this one is
> acceptable.

Am I the only one who's seeing Rene's replies but not the posts which
generated them?

Paul

--
Paul M. Foster

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Re: database abstraction layer

am 03.02.2010 09:37:33 von Lester Caine

Ashley Sheridan wrote:
> On Tue, 2010-02-02 at 23:19 +0100, Rene Veerman wrote:
>
>> function getMax($table, $field)
>
> If I saw this sort of code I'd be appalled! It's possibly the worst way
> to get the auto increment value. You won't notice it testing the site
> out on your own, but all hell will break loose when you start getting a
> lot of hits, and two people cause an auto increment at the same time!

ADOdb handles SEQUENCE correctly across all databases. Since MySQL does not
understand SEQUENCE or GENERATOR, ADOdb simulates it with a dummy table which
autoincrements and gets around the problem. Then one can use a secure generic
GetID ;)

--
Lester Caine - G8HFL
-----------------------------
Contact - http://lsces.co.uk/wiki/?page=contact
L.S.Caine Electronic Services - http://lsces.co.uk
EnquirySolve - http://enquirysolve.com/
Model Engineers Digital Workshop - http://medw.co.uk//
Firebird - http://www.firebirdsql.org/index.php

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php