From 0c0c442248c42717cd5bf806fe9e98d4609ebf47 Mon Sep 17 00:00:00 2001 From: KreizIT <KreizIT@users.noreply.github.com> Date: Sat, 14 Feb 2015 16:27:00 +0100 Subject: [PATCH] Make MS SQL working --- htdocs/core/db/mssql.class.php | 307 ++++++++++++++----- htdocs/install/etape1.php | 4 +- htdocs/install/etape2.php | 8 +- htdocs/install/mssql/functions/functions.sql | Bin 5010 -> 0 bytes 4 files changed, 242 insertions(+), 77 deletions(-) delete mode 100644 htdocs/install/mssql/functions/functions.sql diff --git a/htdocs/core/db/mssql.class.php b/htdocs/core/db/mssql.class.php index 0f86f01693c..55f5dbe1458 100644 --- a/htdocs/core/db/mssql.class.php +++ b/htdocs/core/db/mssql.class.php @@ -89,6 +89,7 @@ class DoliDBMssql extends DoliDB // (La base Dolibarr a ete forcee en this->forcecharset a l'install) $this->connected = 1; $this->ok = 1; + } else { @@ -146,7 +147,7 @@ class DoliDBMssql extends DoliDB */ function select_db($database) { - return mssql_select_db($database, $this->db); + return @mssql_select_db($database, $this->db); } /** @@ -171,6 +172,18 @@ class DoliDBMssql extends DoliDB // les nouvelles version de Dolibarr car force par l'install Dolibarr. //$this->query('SET NAMES '.$this->forcecharset); //print "Resultat fonction connect: ".$this->db; + $set_options=array('SET ANSI_PADDING ON;', + "SET ANSI_NULLS ON;", + "SET ANSI_WARNINGS ON;", + "SET ARITHABORT ON;", + "SET CONCAT_NULL_YIELDS_NULL ON;", + "SET QUOTED_IDENTIFIER ON;" + ); + mssql_query(implode(' ',$set_options),$this->db); + + + + return $this->db; } @@ -224,19 +237,22 @@ class DoliDBMssql extends DoliDB */ function begin() { - if (! $this->transaction_opened) + + $res=mssql_query('select @@TRANCOUNT'); + $this->transaction_opened=mssql_result($res, 0, 0); + + if ($this->transaction_opened == 0) { - $ret=$this->query("BEGIN TRANSACTION"); + //return 1; //There is a mess with auto_commit and 'SET IMPLICIT_TRANSACTIONS ON' generate also a mess + $ret=mssql_query("SET IMPLICIT_TRANSACTIONS OFF;BEGIN TRANSACTION;",$this->db); if ($ret) { - $this->transaction_opened++; dol_syslog("BEGIN Transaction",LOG_DEBUG); } return $ret; } else { - $this->transaction_opened++; return 1; } } @@ -249,12 +265,15 @@ class DoliDBMssql extends DoliDB */ function commit($log='') { - if ($this->transaction_opened <= 1) + $res=mssql_query('select @@TRANCOUNT'); + $this->transaction_opened=mssql_result($res, 0, 0); + + if ($this->transaction_opened == 1) { - $ret=$this->query("COMMIT TRANSACTION"); + //return 1; //There is a mess with auto_commit and 'SET IMPLICIT_TRANSACTION ON' generate also a mess + $ret=mssql_query("COMMIT TRANSACTION",$this->db); if ($ret) { - $this->transaction_opened=0; dol_syslog("COMMIT Transaction",LOG_DEBUG); return 1; } @@ -263,11 +282,11 @@ class DoliDBMssql extends DoliDB return 0; } } - else + elseif ($this->transaction_opened > 1) { - $this->transaction_opened--; return 1; - } + } else + trigger_error("Commit requested but no transaction remain"); } /** @@ -278,18 +297,20 @@ class DoliDBMssql extends DoliDB */ function rollback($log='') { - if ($this->transaction_opened<=1) + $res=mssql_query('select @@TRANCOUNT'); + $this->transaction_opened=mssql_result($res, 0, 0); + + if ($this->transaction_opened == 1) { - $ret=$this->query("ROLLBACK TRANSACTION"); - $this->transaction_opened=0; + $ret=mssql_query("ROLLBACK TRANSACTION",$this->db); dol_syslog("ROLLBACK Transaction".($log?' '.$log:''),LOG_DEBUG); return $ret; } - else + elseif ($this->transaction_opened > 1) { - $this->transaction_opened--; return 1; - } + } else + trigger_error("Rollback requested but no transaction remain"); } /** @@ -304,54 +325,98 @@ class DoliDBMssql extends DoliDB function query($query,$usesavepoint=0,$type='auto') { $query = trim($query); + + if (preg_match('/^--/',$query)) return true; // 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(" percent ", " [percent] ", $query); - $query = str_ireplace("percent,", "[percent],", $query); - $query = str_ireplace("percent=", "[percent]=", $query); - $query = str_ireplace("\'", "''", $query); - - - $itemfound = stripos($query, " limit "); - if ($itemfound !== false) { - // Extraire le nombre limite - $number = stristr($query, " limit "); - $number = substr($number, 7); - // Inserer l'instruction TOP et le nombre limite - $query = str_ireplace("select ", "select top ".$number." ", $query); - // Supprimer l'instruction MySql - $query = str_ireplace(" limit ".$number, "", $query); - } - $itemfound = stripos($query, " week("); - if ($itemfound !== false) { - // Recreer une requete sans instruction Mysql - $positionMySql = stripos($query, " week("); - $newquery = substr($query, 0, $positionMySql); - - // Recuperer la date passee en parametre - $extractvalue = stristr($query, " week("); - $extractvalue = substr($extractvalue, 6); - $positionMySql = stripos($extractvalue, ")"); - // Conserver la fin de la requete - $endofquery = substr($extractvalue, $positionMySql); - $extractvalue = substr($extractvalue, 0, $positionMySql); - - // Remplacer l'instruction MySql en Sql Server - // Inserer la date en parametre et le reste de la requete - $query = $newquery." DATEPART(week, ".$extractvalue.$endofquery; + $query=preg_replace("/([. ,\t(])(percent|file|public)([. ,=\t)])/","$1[$2]$3",$query); + + if ($type=="auto" || $type='dml') + { + $query=preg_replace('/AUTO_INCREMENT/i','IDENTITY',$query); + $query=preg_replace('/double/i','float',$query); + $query=preg_replace('/float\((.*)\)/','numeric($1)',$query); + $query=preg_replace('/([ \t])unsigned|IF NOT EXISTS[ \t]/i','$1',$query); + $query=preg_replace('/([ \t])(MEDIUM|TINY|LONG){0,1}TEXT([ \t,])/i',"$1VARCHAR(MAX)$3",$query); + + $matches=array(); + $original_query=''; + if (preg_match('/ALTER TABLE\h+(\w+?)\h+ADD\h+(?:(UNIQUE)|INDEX)\h+(?:INDEX)?\h*(\w+?)\h*\((.+)\)/is', $query,$matches)) + { + $original_query=$query; + $query="CREATE ".trim($matches[2])." INDEX [".trim($matches[3])."] ON [".trim($matches[1])."] (".trim($matches[4]).")"; + if ($matches[2]) { + //check if columun is nullable cause Sql server only allow 1 null value if unique index. + $fields=explode(",",trim($matches[4])); + $fields_clear=array_map('trim',$fields); + $infos=$this->GetFieldInformation(trim($matches[1]), $fields_clear); + $query_comp=array(); + foreach($infos as $fld) { + if ($fld->IS_NULLABLE == 'YES') { + $query_comp[]=$fld->COLUMN_NAME." IS NOT NULL"; + } + } + if ($query_comp) + $query.=" WHERE ".implode(" AND ",$query_comp); + } + } + else + { + if (preg_match('/ALTER TABLE\h+(\w+?)\h+ADD\h+PRIMARY\h+KEY\h+(\w+?)\h*\((.+)\)/is', $query, $matches)) + { + $original_query=$query; + $query="ALTER TABLE [".$matches[1]."] ADD CONSTRAINT [".$matches[2]."] PRIMARY KEY CLUSTERED (".$matches[3].")"; + } + } + } + if ($type=="auto" || $type='ddl') + { + $itemfound = stripos($query, " limit "); + if ($itemfound !== false) { + // Extraire le nombre limite + $number = stristr($query, " limit "); + $number = substr($number, 7); + // Inserer l'instruction TOP et le nombre limite + $query = str_ireplace("select ", "select top ".$number." ", $query); + // Supprimer l'instruction MySql + $query = str_ireplace(" limit ".$number, "", $query); + } + + $itemfound = stripos($query, " week("); + if ($itemfound !== false) { + // Recreer une requete sans instruction Mysql + $positionMySql = stripos($query, " week("); + $newquery = substr($query, 0, $positionMySql); + + // Recuperer la date passee en parametre + $extractvalue = stristr($query, " week("); + $extractvalue = substr($extractvalue, 6); + $positionMySql = stripos($extractvalue, ")"); + // Conserver la fin de la requete + $endofquery = substr($extractvalue, $positionMySql); + $extractvalue = substr($extractvalue, 0, $positionMySql); + + // Remplacer l'instruction MySql en Sql Server + // Inserer la date en parametre et le reste de la requete + $query = $newquery." DATEPART(week, ".$extractvalue.$endofquery; + } + if (preg_match('/^insert\h+(?:INTO)?\h*(\w+?)\h*\(.*\b(?:row)?id\b.*\)\h+VALUES/i',$query,$matches)) + { + //var_dump($query); + //var_dump($matches); + if (stripos($query,'llx_c_departements') !== false) var_dump($query); + $sql='SET IDENTITY_INSERT ['.trim($matches[1]).'] ON;'; + @mssql_query($sql, $this->db); + $post_query='SET IDENTITY_INSERT ['.trim($matches[1]).'] OFF;'; + + } + } //print "<!--".$query."-->"; if (! in_array($query,array('BEGIN','COMMIT','ROLLBACK'))) dol_syslog('sql='.$query, LOG_DEBUG); @@ -365,6 +430,11 @@ class DoliDBMssql extends DoliDB { $ret = mssql_query($query, $this->db); } + + if (!empty($post_query)) + { + @mssql_query($post_query, $this->db); + } if (! preg_match("/^COMMIT/i",$query) && ! preg_match("/^ROLLBACK/i",$query)) { @@ -379,12 +449,13 @@ class DoliDBMssql extends DoliDB $this->lasterrno = $row["code"]; dol_syslog(get_class($this)."::query SQL Error query: ".$query, LOG_ERR); + if ($original_query) dol_syslog(get_class($this)."::query SQL Original query: ".$original_query, LOG_ERR); dol_syslog(get_class($this)."::query SQL Error message: ".$this->lasterror." (".$this->lasterrno.")", LOG_ERR); } $this->lastquery=$query; $this->_results = $ret; } - + return $ret; } @@ -539,7 +610,8 @@ class DoliDBMssql extends DoliDB 1146 => 'DB_ERROR_NOSUCHTABLE', 1216 => 'DB_ERROR_NO_PARENT', 1217 => 'DB_ERROR_CHILD_EXISTS', - 1451 => 'DB_ERROR_CHILD_EXISTS' + 1451 => 'DB_ERROR_CHILD_EXISTS', + 1913 => 'DB_ERROR_KEY_NAME_ALREADY_EXISTS' ); if (isset($errorcode_map[$this->lasterrno])) @@ -563,7 +635,7 @@ class DoliDBMssql extends DoliDB 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); + return mssql_get_last_message(); } } @@ -655,20 +727,35 @@ class DoliDBMssql extends DoliDB */ function DDLCreateDb($database,$charset='',$collation='',$owner='') { - if (empty($charset)) $charset=$this->forcecharset; + /*if (empty($charset)) $charset=$this->forcecharset; if (empty($collation)) $collation=$this->forcecollate; + */ +<<<<<<< Updated upstream // ALTER DATABASE dolibarr_db DEFAULT CHARACTER SET latin DEFAULT COLLATE latin1_swedish_ci $sql = 'CREATE DATABASE '.$database; $sql.= ' DEFAULT CHARACTER SET '.$charset.' DEFAULT COLLATE '.$collation; +======= + $sql = 'CREATE DATABASE '.$this->EscapeFieldName($database); + //TODO: Check if we need to force a charset + //$sql.= ' DEFAULT CHARACTER SET '.$charset.' DEFAULT COLLATE '.$collation; +>>>>>>> Stashed changes $ret=$this->query($sql); - if (! $ret) - { - // On reessaie pour compatibilite avec mssql < 5.0 - $sql = 'CREATE DATABASE '.$database; - $ret=$this->query($sql); - } - + + + $this->select_db($database); + $sql="CREATE USER [$owner] FOR LOGIN [$owner]"; + mssql_query($sql,$this->db); + $sql="ALTER ROLE [db_owner] ADD MEMBER [$owner]"; + mssql_query($sql,$this->db); + + //For version >=2008 + $sql="ALTER DATABASE [$database] SET ANSI_NULL_DEFAULT ON;"; + @mssql_query($sql,$this->db); + $sql="ALTER DATABASE [$database] SET ANSI_NULL ON;"; + @mssql_query($sql,$this->db); + + return $ret; } @@ -884,12 +971,40 @@ class DoliDBMssql extends DoliDB */ function DDLCreateUser($dolibarr_main_db_host,$dolibarr_main_db_user,$dolibarr_main_db_pass,$dolibarr_main_db_name) { - // FIXME: Dummy method - // TODO: Implement - // May help: http://msdn.microsoft.com/fr-fr/library/ms173463.aspx - - // Always fail for now - return -1; + $sql = "CREATE LOGIN ".$this->EscapeFieldName($dolibarr_main_db_user)." WITH PASSWORD='$dolibarr_main_db_pass'"; + dol_syslog(get_class($this)."::DDLCreateUser", LOG_DEBUG); // No sql to avoid password in log + $resql=$this->query($sql); + if (! $resql) + { + if ($this->lasterrno != '15025') + { + return -1; + } + else + { + // If user already exists, we continue to set permissions + dol_syslog(get_class($this)."::DDLCreateUser sql=".$sql, LOG_WARNING); + } + } + $sql="SELECT name from sys.databases where name='".$dolibarr_main_db_name."'"; + $ressql=$this->query($sql); + if (! $ressql) + { + dol_syslog(get_class($this)."::DDLCreateUser sql=".$sql, LOG_WARNING); + return -1; + } + else + { + if ($num) + { + $this->select_db($dolibarr_main_db_name); + $sql="CREATE USER [$dolibarr_main_db_user] FOR LOGIN [$dolibarr_main_db_user]"; + $this->query($sql); + $sql="ALTER ROLE [db_owner] ADD MEMBER [$dolibarr_main_db_user]"; + $this->query($sql); + } + } + return 1; } /** @@ -1003,5 +1118,49 @@ class DoliDBMssql extends DoliDB return array(); } + + /** + * Escape a field name according to escape's syntax + * + * @param string $fieldname Field's name to escape + * @return string field's name escaped + */ + function EscapeFieldName($fieldname) { + return "[".$fieldname."]"; + } + + + /** + * Get information on field + * + * @param string $table Table name which contains fields + * @param mixed $fields String for one field or array of string for multiple field + * @return boolean|multitype:object + */ + function GetFieldInformation($table,$fields) { + $sql="SELECT * from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='".$this->escape($table)."' AND COLUMN_NAME"; + if (is_array($fields)) + { + $where=" IN ('".implode("','",$fields)."')"; + } + else + { + $where="='".$this->escape($fields)."'"; + } + $result=array(); + $ret=mssql_query($sql.$where,$this->db); + if ($ret) + { + while($obj=mssql_fetch_object($ret)) + { + $result[]=$obj; + } + } + else + return false; + + return $result; + } + } diff --git a/htdocs/install/etape1.php b/htdocs/install/etape1.php index 20450760127..50d5ee27b55 100644 --- a/htdocs/install/etape1.php +++ b/htdocs/install/etape1.php @@ -175,7 +175,7 @@ if (! $error) } else { - $databasefortest='mssql'; + $databasefortest='master'; } } //print $_POST["db_type"].",".$_POST["db_host"].",$userroot,$passroot,$databasefortest,".$_POST["db_port"]; @@ -486,7 +486,7 @@ if (! $error && $db->connected && $action == "set") } else if ($conf->db->type == 'mssql') { - $databasefortest='mssql'; + $databasefortest='master'; } // Creation handler de base, verification du support et connexion diff --git a/htdocs/install/etape2.php b/htdocs/install/etape2.php index f97f1afaa9c..1f6c5244d84 100644 --- a/htdocs/install/etape2.php +++ b/htdocs/install/etape2.php @@ -1,6 +1,7 @@ <?php /* Copyright (C) 2004 Rodolphe Quiedeville <rodolphe@quiedeville.org> * Copyright (C) 2004-2010 Laurent Destailleur <eldy@users.sourceforge.net> + * Copyright (C) 2015 Cedric GROSS <c.gross@kreiz-it.fr> * * 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 @@ -192,6 +193,11 @@ if ($action == "set") { $buffer=preg_replace('/type=innodb/i','ENGINE=innodb',$buffer); } + else if ($conf->db->type == 'mssql') + { + $buffer=preg_replace('/type=innodb/i','',$buffer); + $buffer=preg_replace('/ENGINE=innodb/i','',$buffer); + } // Replace the prefix tables if ($dolibarr_main_db_prefix != 'llx_') @@ -219,7 +225,7 @@ if ($action == "set") else { print "<tr><td>".$langs->trans("CreateTableAndPrimaryKey",$name); - print "<br>\n".$langs->trans("Request").' '.$requestnb.' : '.$buffer; + print "<br>\n".$langs->trans("Request").' '.$requestnb.' : '.$buffer.' <br>Executed query : '.$db->lastquery; print "\n</td>"; print '<td><font class="error">'.$langs->trans("ErrorSQL")." ".$db->errno()." ".$db->error().'</font></td></tr>'; $error++; diff --git a/htdocs/install/mssql/functions/functions.sql b/htdocs/install/mssql/functions/functions.sql deleted file mode 100644 index 1c2c76996dc312cc2440a2329d46530adb815d7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5010 zcmd^@T~8ZF6o%)P{S|wMX%i@|s;DiAaBNIn5uC<GG`+D5EZ_=k*K3FJ=i5H-IXhmj z<F=HjRiwx=o}D>!KHu}P{PFwK@HC|1`A7Om&`$a_!Z=LAeVB$}xC+-{#$JuxEq=cY zU+{Ux>P^Tv9fq9s_uQDX%6T5~yvN;mx*sk$z2W364A`HB?QqH87vbN@>%!p{PU7E$ zd(Jmwt<RoG_EX8r_)4=el4i&|XLY-f80Ge`&Gnz4&lqc_Xm|@h-<6gwBXYVk3$pMX zH22(?^Lx&obP}ygow1g+b7V`yp?OeL7h#86Q}(2*s4w9Y^>Mnn(Pc(8Lql;8pDB0b zYuTh7KRFD?;e_WyC`Lv#z}IPbjr23~`Z-TWNIEwPc`Zd_ahfb>a^rTv-r15qQAP{d z?uV{PpM~g$G_=@pjR)v7<Y{Ey$;@i$C~M!@uh8M~`-;7)9aRmV*!gtfnHm}kh3<24 z6hfms;Zokc=l8}a*Gb(jxJ7UOQ}oxxQXX=IN8enVCLW{fLQ9Y19h5f~i!<}N@*^X5 zbM~GrEOV5k;WV^Ek38xS%^p(f#Puz|r>x%a(Q}U-Ay1qoU!Ip&GV`x67`0NSOpr6= zOgekKgzgbE@{_zDJ@G0WAfbVDmr@Te(dNiBk`9j8mOURs+vTIkceo`@g*Iu~gQ9`{ zNA{%M6Lw!i*X7nWbSW!Gl4}|@cpQo^3(b^ELTHMu8TJj#^Encof0XhTI%TZTuGNkC zMo|+!J%g1cLwlCr27FaJR1H@V`xu?0CdyG|!WCR&G(CFj>XAIxwyYm<YX*8`gZpt0 zTV{MF;ah6U4&3zFhVuvux1pc1-Z3nc6(DEl#lk-^!jb30I97f8@%FUD(5IaG5j=K4 z=>h$tk4KxVlvQo8+-5B_ci2g&rXBtYwJ{gFQ1m$yx{o-iQi?;$sko=y`U&bTYthw= z&k?%~JN=Bk7P30w6{{R%38`Z$?=QskoLJ`UpOTHz=z#U|Ss!!?na}xl$kQJRzHUzq z9X5&k2&&)ER~7OWt~o1pwh5=Imdf)czp4cj&SO>c`z_-ll!=>c+ceDSsaJVwVOx3D z;#B;%@ZC%FI^?drnW2j;i~aBdTgTX(BfGzHnulM(Y8v(noz?kdf$El9EiDp!Dc{tQ zC=a!W%dz>ljaQD@`8yvZ#3TtD$Y|mf@6i{<Dtat2D<q?5RqWVe`3|ZnmZ)Z^3)RAI z_{J#foL8;hFnl$NYA7xD(OI!j<&ib&Br|SF7foIfZ>w~QbwpmIb;K=OAjFjzjP$Cw zj^cG(n}r9P7UdT9<QC~ov+J47db|AHEuvh?Q;em{vWL7@j*4zSgIm>K(OW`R$w^PU zHdk)Z-4e7FHpP8;UCqc(C6hIQYI5x_@Eq~^spRord!WqX1M0}wva-x6;d2?A%8mDP zd|lQq-%3HE*O)r1<|04#L1~mJ{BH75C8%bMVkAT_<G4ik|Apwpa=WYykLgbSMsvj^ zp~?C~`;(?(btiR!sm*Mf-UeX2Z&A$X0dscxHpARPC#kWsNPqQG#q-1ah`;fw|EsV6 z3}<y_@z$iM87j(G9IIWY2W^`!cWAft?(rDAR7JgKJ@meA)aiNZeVSR+WmV-hfeVc% z%-j*T36$@5Cbv3v2F)!EzUrZxUtF63KJYhb%?V@n^_CppHPpY<*M$sC$vP=Jn<h!0 zV`Bcsz7;c65xs;;Z_2B4u=FbDC7)fE=K!DVHx`|3o&KJ8>w2)qRFY+rzX`AQsjsc4 zyM$7c{Q?j1uKIHMKa!eN0sVKxKdK37_?)l)Lm(?^6+CGQQ&n0gdy6<m9ij&xjxYK8 zju}OIYPOT5ddtw<=x?v;^BHLMd9zAU$`h^Vrs%HsNbmV`a1bl$!*%jE7NKT2r+lKT N`Zf6vbziN%e*$;j{}%uN -- GitLab