Select Git revision
mssql.lib.php 25.32 KiB
<?php
/* Copyright (C) 2002-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2004-2007 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2005-2007 Regis Houssin <regis.houssin@cap-networks.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Id$
* $Source$
*/
/**
\file htdocs/lib/mysql.lib.php
\brief Fichier de la classe permettant de grer une base mysql
\author Fabien Seisen
\author Rodolphe Quiedeville.
\author Laurent Destailleur.
\version $Revision$
*/
/**
\class DoliDb
\brief Classe de gestion de la database de dolibarr
*/
class DoliDb
{
//! Handler de base
var $db;
//! Nom du gestionnaire
var $type='mssql';
//! Charset
var $forcecharset='latin1';
//! Collate
var $forcecollate='latin1_swedish_ci';
//! Version min database
var $versionmin=array(3,1,0);
//! Resultset de la dernire requete
var $results;
//! 1 si connect, 0 sinon
var $connected;
//! 1 si base slectionn, 0 sinon
var $database_selected;
//! Nom base slectionne
var $database_name;
//! 1 si une transaction est en cours, 0 sinon
var $transaction_opened;
//! Derniere requete excute
var $lastquery;
//! Derniere requete excute avec echec
var $lastqueryerror;
//! Message erreur mysql
var $lasterror;
//! Message erreur mysql
var $lasterrno;
var $ok;
var $error;
// Constantes pour conversion code erreur MSSql en code erreur gnrique
var $errorcode_map = array(
1004 => 'DB_ERROR_CANNOT_CREATE',
1005 => 'DB_ERROR_CANNOT_CREATE',
1006 => 'DB_ERROR_CANNOT_CREATE',
1007 => 'DB_ERROR_ALREADY_EXISTS',
1008 => 'DB_ERROR_CANNOT_DROP',
1025 => 'DB_ERROR_NO_FOREIGN_KEY_TO_DROP',
1046 => 'DB_ERROR_NODBSELECTED',
1048 => 'DB_ERROR_CONSTRAINT',
2714 => 'DB_ERROR_TABLE_ALREADY_EXISTS',
1051 => 'DB_ERROR_NOSUCHTABLE',
1054 => 'DB_ERROR_NOSUCHFIELD',
1060 => 'DB_ERROR_COLUMN_ALREADY_EXISTS',
1061 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS',
2627 => 'DB_ERROR_RECORD_ALREADY_EXISTS',
1064 => 'DB_ERROR_SYNTAX',
1068 => 'DB_ERROR_PRIMARY_KEY_ALREADY_EXISTS',
1075 => 'DB_ERROR_CANT_DROP_PRIMARY_KEY',
1091 => 'DB_ERROR_NOSUCHFIELD',
1100 => 'DB_ERROR_NOT_LOCKED',
1136 => 'DB_ERROR_VALUE_COUNT_ON_ROW',
1146 => 'DB_ERROR_NOSUCHTABLE',
1216 => 'DB_ERROR_NO_PARENT',
1217 => 'DB_ERROR_CHILD_EXISTS',
1451 => 'DB_ERROR_CHILD_EXISTS'
);
/**
\brief Ouverture d'une connection vers le serveur et ventuellement une database.
\param type Type de base de donnes (mysql ou pgsql)
\param host Addresse de la base de donnes
\param user Nom de l'utilisateur autoris
\param pass Mot de passe
\param name Nom de la database
\param newlink ???
\return int 1 en cas de succs, 0 sinon
*/
function DoliDb($type='mssql', $host, $user, $pass, $name='', $newlink=0)
{
global $conf,$langs;
$this->transaction_opened=0;
if (! function_exists("mssql_connect"))
{
$this->connected = 0;
$this->ok = 0;
$this->error="Mssql PHP functions for using MSSql driver are not available in this version of PHP";
dolibarr_syslog("DoliDB::DoliDB : MSsql PHP functions for using MSsql driver are not available in this version of PHP");
return $this->ok;
}
if (! $host)
{
$this->connected = 0;
$this->ok = 0;
$this->error=$langs->trans("ErrorWrongHostParameter");
dolibarr_syslog("DoliDB::DoliDB : Erreur Connect, wrong host parameters");
return $this->ok;
}
// Essai connexion serveur
$this->db = $this->connect($host, $user, $pass, $name, $newlink);
if ($this->db)
{
// Si client connect avec charset different de celui de la base Dolibarr
// (La base Dolibarr a t force en this->forcecharset l'install)
$this->connected = 1;
$this->ok = 1;
}
else
{
// host, login ou password incorrect
$this->connected = 0;
$this->ok = 0;
dolibarr_syslog("DoliDB::DoliDB : Erreur Connect mssql_get_last_message=".mssql_get_last_message());
}
// Si connexion serveur ok et si connexion base demande, on essaie connexion base
if ($this->connected && $name)
{
if ($this->select_db($name))
{
$this->database_selected = 1;
$this->database_name = $name;
$this->ok = 1;
}
else
{
$this->database_selected = 0;
$this->database_name = '';
$this->ok = 0;
$this->error=$this->error();
dolibarr_syslog("DoliDB::DoliDB : Erreur Select_db");
}
}
else
{
// Pas de selection de base demande, ok ou ko
$this->database_selected = 0;
}
return $this->ok;
}
/**
\brief Selectionne une database.
\param database Nom de la database
\return boolean true si ok, false si ko
*/
function select_db($database)
{
return mssql_select_db($database, $this->db);
}
/**
\brief Connection vers le serveur
\param host addresse de la base de donnes
\param login nom de l'utilisateur autoris
\param passwd mot de passe
\param name nom de la database (ne sert pas sous mysql, sert sous pgsql)
\return resource handler d'accs la base
\seealso close
*/
function connect($host, $login, $passwd, $name)
{
dolibarr_syslog("DoliDB::connect host=$host, login=$login, passwd=--hidden--, name=$name");
$this->db = @mssql_connect($host, $login, $passwd);
//force les enregistrement en latin1 si la base est en utf8 par dfaut
// Supprim car plante sur mon PHP-Mysql. De plus, la base est forcement en latin1 avec
// les nouvelles version de Dolibarr car forc par l'install Dolibarr.
//$this->query('SET NAMES '.$this->forcecharset);
//print "Resultat fonction connect: ".$this->db;
return $this->db;
}
/**
\brief Renvoie la version du serveur
\return string Chaine version
*/
function getVersion()
{
return '?';
}
/**
\brief Renvoie la version du serveur dans un tableau
\return array Tableau de chaque niveau de version
*/
function getVersionArray()
{
return split('\.',$this->getVersion());
}
/**
\brief Fermeture d'une connection vers une database.
\return resource
\seealso connect
*/
function close()
{
return mssql_close($this->db);
}
/**
\brief Debut d'une transaction.
\return int 1 si ouverture transaction ok ou deja ouverte, 0 en cas d'erreur
*/
function begin()
{
if (! $this->transaction_opened)
{
$ret=$this->query("BEGIN TRANSACTION");
if ($ret) $this->transaction_opened++;
return $ret;
}
else
{
$this->transaction_opened++;
return 1;
}
}
/**
\brief Validation d'une transaction
\return int 1 si validation ok ou niveau de transaction non ouverte, 0 en cas d'erreur
*/
function commit()
{
if ($this->transaction_opened<=1)
{
$ret=$this->query("COMMIT TRANSACTION");
if ($ret) $this->transaction_opened=0;
return $ret;
}
else
{
$this->transaction_opened--;
return 1;
}
}
/**
\brief Annulation d'une transaction et retour aux anciennes valeurs
\return int 1 si annulation ok ou transaction non ouverte, 0 en cas d'erreur
*/
function rollback()
{
dolibarr_syslog("ROLLBACK ".$this->transaction_opened, LOG_ERR);
if ($this->transaction_opened<=1)
{
$ret=$this->query("ROLLBACK TRANSACTION");
$this->transaction_opened=0;
return $ret;
}
else
{
$this->transaction_opened--;
return 1;
}
}
/**
\brief Effectue une requete et renvoi le resultset de rponse de la base
\param query Contenu de la query
\return resource Resultset de la reponse
*/
function query($query)
{
$query = trim($query);
// Conversion syntaxe MySql vers MSDE.
$query = str_ireplace("now()", "getdate()", $query);
// Erreur SQL: cannot update timestamp field
$query = str_ireplace(", tms = tms", "", $query);
// Voir si l'on peut directement utiliser $query = str_ireplace("file", "[file]", $query);
// au lieu des 3 lignes ci-dessous
$query = str_ireplace(".file", ".[file]", $query);
$query = str_ireplace(" file ", " [file] ", $query);
$query = str_ireplace(" file,", " [file],", $query);
// Idem file
$query = str_ireplace(".percent", ".[percent]", $query);
$query = str_ireplace("\'", "''", $query);
$limitfound = stripos($query, " limit ");
if ($limitfound !== false) {
$number = stristr($query, " limit ");
$number = substr($number, 7);
$query = str_ireplace("select ", "select top ".$number." ", $query);
$query = str_ireplace(" limit ".$number, "", $query);
}
//print "<!--".$query."-->";
if (! $this->database_name)
{
// Ordre SQL ne ncessitant pas de connexion une base (exemple: CREATE DATABASE)
$ret = mssql_query($query, $this->db);
}
else
{
$ret = mssql_query($query, $this->db);
}
if (! eregi("^COMMIT",$query) && ! eregi("^ROLLBACK",$query))
{
// Si requete utilisateur, on la sauvegarde ainsi que son resultset
if (! $ret)
{
$this->lastqueryerror = $query;
$result = mssql_query("SELECT @@ERROR as code", $this->db);
$row = mssql_fetch_array($result);
$this->lasterror = $this->error();
$this->lasterrno = $row["code"];
}
$this->lastquery=$query;
$this->results = $ret;
}
return $ret;
}
/**
\brief Renvoie la ligne courante (comme un objet) pour le curseur resultset.
\param resultset Curseur de la requete voulue
\return resource
*/
function fetch_object($resultset=0)
{
// Si le resultset n'est pas fourni, on prend le dernier utilis sur cette connexion
if (! is_resource($resultset)) { $resultset=$this->results; }
return mssql_fetch_object($resultset);
}
/**
\brief Renvoie les donnes dans un tableau.
\param resultset Curseur de la requete voulue
\return array
*/
function fetch_array($resultset=0)
{
// Si le resultset n'est pas fourni, on prend le dernier utilis sur cette connexion
if (! is_resource($resultset)) { $resultset=$this->results; }
return mssql_fetch_array($resultset);
}
/**
\brief Renvoie les donnes comme un tableau.
\param resultset Curseur de la requete voulue
\return array
*/
function fetch_row($resultset=0)
{
// Si le resultset n'est pas fourni, on prend le dernier utilis sur cette connexion
if (! is_resource($resultset)) { $resultset=$this->results; }
return @mssql_fetch_row($resultset);
}
/**
\brief Renvoie le nombre de lignes dans le resultat d'une requete SELECT
\see affected_rows
\param resultset Curseur de la requete voulue
\return int Nombre de lignes
*/
function num_rows($resultset=0)
{
// Si le resultset n'est pas fourni, on prend le dernier utilis sur cette connexion
if (! is_resource($resultset)) { $resultset=$this->results; }
return mssql_num_rows($resultset);
}
/**
\brief Renvoie le nombre de lignes dans le resultat d'une requete INSERT, DELETE ou UPDATE
\see num_rows
\param resultset Curseur de la requete voulue
\return int Nombre de lignes
*/
function affected_rows($resultset=0)
{
// Si le resultset n'est pas fourni, on prend le dernier utilis sur cette connexion
if (! is_resource($resultset)) { $resultset=$this->results; }
// mssql necessite un link de base pour cette fonction contrairement
// a pqsql qui prend un resultset
return mssql_affected_rows($this->db);
}
/**
\brief Libre le dernier resultset utilis sur cette connexion.
\param resultset Curseur de la requete voulue
*/
function free($resultset=0)
{
// Si le resultset n'est pas fourni, on prend le dernier utilis sur cette connexion
if (! is_resource($resultset)) { $resultset=$this->results; }
// Si resultset en est un, on libere la mmoire
if (is_resource($resultset)) mssql_free_result($resultset);
}
/**
\brief Dfini les limites de la requte.
\param limit nombre maximum de lignes retournes
\param offset numro de la ligne partir de laquelle recuprer les lignes
\return string chaine exprimant la syntax sql de la limite
*/
function plimit($limit=0,$offset=0)
{
global $conf;
if (! $limit) $limit=$conf->liste_limit;
if ($offset > 0) return " LIMIT $offset,$limit ";
else return " LIMIT $limit ";
}
/**
\brief Formatage (par la base de donnes) d'un champ de la base au format TMS ou Date (YYYY-MM-DD HH:MM:SS)
afin de retourner une donne toujours au format universel date TMS unix.
Fonction utiliser pour gnrer les SELECT.
\param param Nom champ base de type date ou chaine 'YYYY-MM-DD HH:MM:SS'
\return date Date au format TMS.
*/
function pdate($param)
{
return "dbo.unix_timestamp(".$param.")";
}
/**
\brief Formatage (par PHP) d'une date vers format texte pour insertion dans champ date.
Fonction utiliser pour gnrer les INSERT.
\param param Date TMS convertir
\return date Date au format texte YYYYMMDDHHMMSS.
*/
function idate($param)
{
return strftime("%Y%m%d%H%M%S",$param);
}
/**
\brief Formatage d'un if SQL
\param test chaine test
\param resok resultat si test egal
\param resko resultat si test non egal
\return string chaine format SQL
*/
function ifsql($test,$resok,$resko)
{
return 'IF('.$test.','.$resok.','.$resko.')';
}
/**
\brief Renvoie la derniere requete soumise par la methode query()
\return lastquery
*/
function lastquery()
{
return $this->lastquery;
}
/**
\brief Renvoie la derniere requete en erreur
\return string lastqueryerror
*/
function lastqueryerror()
{
return $this->lastqueryerror;
}
/**
\brief Renvoie le libelle derniere erreur
\return string lasterror
*/
function lasterror()
{
return $this->lasterror;
}
/**
\brief Renvoie le code derniere erreur
\return string lasterrno
*/
function lasterrno()
{
return $this->lasterrno;
}
/**
\brief Renvoie le code erreur generique de l'operation precedente.
\return error_num (Exemples: DB_ERROR_TABLE_ALREADY_EXISTS, DB_ERROR_RECORD_ALREADY_EXISTS...)
*/
function errno()
{
if (! $this->connected) {
// Si il y a eu echec de connection, $this->db n'est pas valide.
return 'DB_ERROR_FAILED_TO_CONNECT';
}
else {
if (isset($this->errorcode_map[$this->lasterrno])) {
return $this->errorcode_map[$this->lasterrno];
}
$errno=$this->lasterrno;
return ($errno?'DB_ERROR_'.$errno:'0');
}
}
/**
\brief Renvoie le texte de l'erreur mssql de l'operation precedente.
\return error_text
*/
function error()
{
if (! $this->connected) {
// Si il y a eu echec de connection, $this->db n'est pas valide pour mssql_get_last_message.
return 'Not connected. Check setup parameters in conf/conf.php file and your mssql client and server versions';
}
else {
return mssql_get_last_message($this->db);
}
}
/**
\brief Rcupre l'id genr par le dernier INSERT.
\param tab Nom de la table concerne par l'insert. Ne sert pas sous mssql mais requis pour compatibilit avec Postgresql
\return int id
*/
function last_insert_id($tab)
{
return query("SELECT @@identity ");
//return mssql_insert_id($this->db);
}
// Next function are not required. Only minor features use them.
//--------------------------------------------------------------
/**
\brief Renvoie l'id de la connection
\return string Id connection
*/
function getConnectId()
{
$resql=$this->query('SELECT CONNECTION_ID()');
$row=$this->fetch_row($resql);
return $row[0];
}
/**
\brief Renvoie la commande sql qui donne les droits user sur toutes les tables
\param databaseuser User autoriser
\return string Requete sql
*/
function getGrantForUserQuery($databaseuser)
{
/*
$query = "DECLARE @tables TABLE(ROWID int IDENTITY(1,1), SQLSTR varchar(500)) INSERT INTO @tables SELECT '"
$query .= "GRANT SELECT ON '+NAME+' TO ".$databaseuser." FROM sysobjects WHERE TYPE = 'U' AND "
$query .= "NAME NOT LIKE 'SYNC%' DECLARE @rowid int, @sqlstr varchar(500) SET @rowid = 0 "
$query .= "SET @sqlstr = '' DECLARE grant_tbl_cursor CURSOR FOR SELECT ROWID, SQLSTR FROM @tables ORDER BY ROWID"
$query .= "OPEN grant_tbl_cursor FETCH NEXT FROM grant_tbl_cursor INTO @rowid,@sqlstr WHILE @@FETCH_STATUS = 0 "
$query .= "BEGIN EXECUTE (@sqlstr) FETCH NEXT FROM grant_tbl_cursor INTO @rowid,@sqlstr END CLOSE grant_tbl_cursor "
$query .= "DEALLOCATE grant_tbl_cursor"
*/
return '';
}
/**
\brief Retourne le dsn pear
\return dsn
*/
function getDSN($db_type,$db_user,$db_pass,$db_host,$db_name)
{
return $db_type.'://'.$db_user.':'.$db_pass.'@'.$db_host.'/'.$db_name;
}
/**
\brief Cration d'une nouvelle base de donne
\param database nom de la database crer
\return resource resource dfinie si ok, null si ko
\remarks Ne pas utiliser les fonctions xxx_create_db (xxx=mssql, ...) car elles sont deprecated
On force creation de la base avec le charset forcecharset
*/
function DDLCreateDb($database)
{
// ALTER DATABASE dolibarr_db DEFAULT CHARACTER SET latin DEFAULT COLLATE latin1_swedish_ci
$sql = 'CREATE DATABASE '.$database;
$sql.= ' DEFAULT CHARACTER SET '.$this->forcecharset.' DEFAULT COLLATE '.$this->forcecollate;
$ret=$this->query($sql);
if (! $ret)
{
// On ressaie pour compatibilit avec mssql < 5.0
$sql = 'CREATE DATABASE '.$database;
$ret=$this->query($sql);
}
return $ret;
}
/**
\brief Liste des tables dans une database.
\param database Nom de la database
\return resource
*/
function DDLListTables($database)
{
$this->results = mssql_list_tables($database, $this->db);
return $this->results;
}
/**
\brief Cre une table
\param table Nom de la table
\param fields Tableau associatif [nom champ][tableau des descriptions]
\param primary_key Nom du champ qui sera la clef primaire
\param unique_keys Tableau associatifs Nom de champs qui seront clef unique => valeur
\param fulltext Tableau des Nom de champs qui seront indexs en fulltext
\param key Tableau des champs cls noms => valeur
\param type Type de la table
\return int <0 si KO, >=0 si OK
*/
function DDLCreateTable($table,$fields,$primary_key,$type,$unique_keys="",$fulltext_keys="",$keys="")
{
// cls recherches dans le tableau des descriptions (fields) : type,value,attribute,null,default,extra
// ex. : $fields['rowid'] = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
$sql = "create table ".$table."(";
$i=0;
foreach($fields as $field_name => $field_desc)
{
$sqlfields[$i] = $field_name." ";
$sqlfields[$i] .= $field_desc['type'];
if( eregi("^[^ ]",$field_desc['value']))
$sqlfields[$i] .= "(".$field_desc['value'].")";
else if( eregi("^[^ ]",$field_desc['attribute']))
$sqlfields[$i] .= " ".$field_desc['attribute'];
else if( eregi("^[^ ]",$field_desc['default']))
{
if(eregi("null",$field_desc['default']))
$sqlfields[$i] .= " default ".$field_desc['default'];
else
$sqlfields[$i] .= " default '".$field_desc['default']."'";
}
else if( eregi("^[^ ]",$field_desc['null']))
$sqlfields[$i] .= " ".$field_desc['null'];
else if( eregi("^[^ ]",$field_desc['extra']))
$sqlfields[$i] .= " ".$field_desc['extra'];
$i++;
}
if($primary_key != "")
$pk = "primary key(".$primary_key.")";
if($unique_keys != "")
{
$i = 0;
foreach($unique_keys as $key => $value)
{
$sqluq[$i] = "UNIQUE KEY '".$key."' ('".$value."')";
$i++;
}
}
if($keys != "")
{
$i = 0;
foreach($keys as $key => $value)
{
$sqlk[$i] = "KEY ".$key." (".$value.")";
$i++;
}
}
$sql .= implode(',',$sqlfields);
if($primary_key != "")
$sql .= ",".$pk;
if($unique_keys != "")
$sql .= ",".implode(',',$sqluq);
if($keys != "")
$sql .= ",".implode(',',$sqlk);
$sql .=") type=".$type;
dolibarr_syslog($sql);
if(! $this -> query($sql))
return -1;
else
return 1;
}
/**
\brief dcrit une table dans une database.
\param table Nom de la table
\param field Optionnel : Nom du champ si l'on veut la desc d'un champ
\return resource
*/
function DDLDescTable($table,$field="")
{
$sql="DESC ".$table." ".$field;
dolibarr_syslog($sql);
$this->results = $this->query($sql);
return $this->results;
}
/**
\brief Insre un nouveau champ dans une table
\param table Nom de la table
\param field_name Nom du champ insrer
\param field_desc Tableau associatif de description duchamp insrer[nom du paramtre][valeur du paramtre]
\param field_position Optionnel ex.: "after champtruc"
\return int <0 si KO, >0 si OK
*/
function DDLAddField($table,$field_name,$field_desc,$field_position="")
{
// cls recherches dans le tableau des descriptions (field_desc) : type,value,attribute,null,default,extra
// ex. : $field_desc = array('type'=>'int','value'=>'11','null'=>'not null','extra'=> 'auto_increment');
$sql= "ALTER TABLE ".$table." ADD ".$field_name." ";
$sql .= $field_desc['type'];
if( eregi("^[^ ]",$field_desc['value']))
$sql .= "(".$field_desc['value'].")";
if( eregi("^[^ ]",$field_desc['attribute']))
$sql .= " ".$field_desc['attribute'];
if( eregi("^[^ ]",$field_desc['null']))
$sql .= " ".$field_desc['null'];
if( eregi("^[^ ]",$field_desc['default']))
if(eregi("null",$field_desc['default']))
$sql .= " default ".$field_desc['default'];
else
$sql .= " default '".$field_desc['default']."'";
if( eregi("^[^ ]",$field_desc['extra']))
$sql .= " ".$field_desc['extra'];
$sql .= " ".$field_position;
if(! $this -> query($sql))
return -1;
else
return 1;
}
function getDefaultCharacterSetDatabase(){
$resql=$this->query('SHOW VARIABLES LIKE \'character_set_database\'');
if (!$resql)
{
// version Mysql < 4.1.1
return $this->forcecharset;
}
$liste=$this->fetch_array($resql);
return $liste['Value'];
}
function getListOfCharacterSet(){
$resql=$this->query('SHOW CHARSET');
$liste = array();
if ($resql)
{
$i = 0;
while ($obj = $this->fetch_object($resql) )
{
$liste[$i]['charset'] = $obj->Charset;
$liste[$i]['description'] = $obj->Description;
$i++;
}
$this->free($resql);
} else {
// version Mysql < 4.1.1
return null;
}
return $liste;
}
function getDefaultCollationConnection(){
$resql=$this->query("SELECT SERVERPROPERTY('collation')");
if (!$resql)
{
// version Mysql < 4.1.1
return $this->forcecollate;
}
$liste=$this->fetch_array($resql);
return $liste['Value'];
}
function getListOfCollation(){
$resql=$this->query('SHOW COLLATION');
$liste = array();
if ($resql)
{
$i = 0;
while ($obj = $this->fetch_object($resql) )
{
$liste[$i]['collation'] = $obj->Collation;
$i++;
}
$this->free($resql);
} else {
// version Mysql < 4.1.1
return null;
}
return $liste;
}
}
?>