|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* EGroupware - Setup - db-schema-processor |
|
4
|
|
|
* |
|
5
|
|
|
* Originaly written by |
|
6
|
|
|
* - Michael Dean <[email protected]> |
|
7
|
|
|
* - Miles Lott<[email protected]> |
|
8
|
|
|
* Rewritten and adapted to ADOdb's schema processor by Ralf Becker. |
|
9
|
|
|
* |
|
10
|
|
|
* @link http://www.egroupware.org |
|
11
|
|
|
* @author Ralf Becker <[email protected]> |
|
12
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
|
13
|
|
|
* @package api |
|
14
|
|
|
* @subpackage db |
|
15
|
|
|
*/ |
|
16
|
|
|
|
|
17
|
|
|
namespace EGroupware\Api\Db; |
|
18
|
|
|
|
|
19
|
|
|
use EGroupware\Api; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* EGroupware's ADOdb based schema-processor |
|
23
|
|
|
*/ |
|
24
|
|
|
class Schema |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* @deprecated formerly used translator class, now a reference to ourself |
|
28
|
|
|
*/ |
|
29
|
|
|
var $m_oTranslator; |
|
30
|
|
|
/** |
|
31
|
|
|
* db-object |
|
32
|
|
|
* |
|
33
|
|
|
* @var EGroupware\Api\Db\Deprecated |
|
|
|
|
|
|
34
|
|
|
*/ |
|
35
|
|
|
var $m_odb; |
|
36
|
|
|
/** |
|
37
|
|
|
* reference to the global ADOdb object |
|
38
|
|
|
* |
|
39
|
|
|
* @var ADOConnection |
|
|
|
|
|
|
40
|
|
|
*/ |
|
41
|
|
|
var $adodb; |
|
42
|
|
|
/** |
|
43
|
|
|
* adodb's datadictionary object for the used db-type |
|
44
|
|
|
* |
|
45
|
|
|
* @var ADODB_DataDict |
|
|
|
|
|
|
46
|
|
|
*/ |
|
47
|
|
|
var $dict; |
|
48
|
|
|
/** |
|
49
|
|
|
* Debuglevel: 0=Off, 1=some, eg. primary function calls, 2=lots incl. the SQL used |
|
50
|
|
|
* |
|
51
|
|
|
* @var int |
|
52
|
|
|
*/ |
|
53
|
|
|
var $debug = 0; |
|
54
|
|
|
/** |
|
55
|
|
|
* Array with db => max. length of indexes pairs (if there is a considerable low limit for a db) |
|
56
|
|
|
* |
|
57
|
|
|
* @var array |
|
58
|
|
|
*/ |
|
59
|
|
|
var $max_index_length=array( |
|
60
|
|
|
'maxdb' => 32, |
|
61
|
|
|
'oracle' => 30, |
|
62
|
|
|
); |
|
63
|
|
|
/** |
|
64
|
|
|
* type of the database, set by the the constructor: 'mysql','pgsql','mssql','maxdb' |
|
65
|
|
|
* |
|
66
|
|
|
* @var string |
|
67
|
|
|
*/ |
|
68
|
|
|
var $sType; |
|
69
|
|
|
/** |
|
70
|
|
|
* maximum length of a varchar column, everything above get converted to text |
|
71
|
|
|
* |
|
72
|
|
|
* @var int |
|
73
|
|
|
*/ |
|
74
|
|
|
var $max_varchar_length = 255; |
|
75
|
|
|
/** |
|
76
|
|
|
* system-charset if set |
|
77
|
|
|
* |
|
78
|
|
|
* @var string |
|
79
|
|
|
*/ |
|
80
|
|
|
var $system_charset = 'utf8'; |
|
81
|
|
|
/** |
|
82
|
|
|
* reference to the capabilities array of the db-class |
|
83
|
|
|
* |
|
84
|
|
|
* @var array |
|
85
|
|
|
*/ |
|
86
|
|
|
var $capabilities; |
|
87
|
|
|
/** |
|
88
|
|
|
* preserve value of old sequences in PostgreSQL |
|
89
|
|
|
* |
|
90
|
|
|
* @var int |
|
91
|
|
|
*/ |
|
92
|
|
|
var $pgsql_old_seq; |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* Constructor of schema-processor |
|
96
|
|
|
* |
|
97
|
|
|
* @param string $dbms type of the database: 'mysql','pgsql','mssql','maxdb' |
|
98
|
|
|
* @param Db $db =null database class, if null we use $GLOBALS['egw']->db |
|
|
|
|
|
|
99
|
|
|
* @return schema_proc |
|
|
|
|
|
|
100
|
|
|
*/ |
|
101
|
|
|
function __construct($dbms=False, Api\Db $db=null) |
|
102
|
|
|
{ |
|
103
|
|
|
if(is_object($db)) |
|
104
|
|
|
{ |
|
105
|
|
|
$this->m_odb = $db; |
|
|
|
|
|
|
106
|
|
|
} |
|
107
|
|
|
else |
|
108
|
|
|
{ |
|
109
|
|
|
$this->m_odb = isset($GLOBALS['egw']->db) && is_object($GLOBALS['egw']->db) ? $GLOBALS['egw']->db : $GLOBALS['egw_setup']->db; |
|
110
|
|
|
} |
|
111
|
|
|
if (!($this->m_odb instanceof Api\Db)) |
|
112
|
|
|
{ |
|
113
|
|
|
throw new Api\Exception\AssertionFailed('no EGroupware\Api\Db object!'); |
|
114
|
|
|
} |
|
115
|
|
|
$this->m_odb->connect(); |
|
116
|
|
|
$this->capabilities =& $this->m_odb->capabilities; |
|
117
|
|
|
|
|
118
|
|
|
$this->sType = $dbms ? $dbms : $this->m_odb->Type; |
|
119
|
|
|
$this->adodb = &$this->m_odb->Link_ID; |
|
120
|
|
|
$this->dict = NewDataDictionary($this->adodb); |
|
|
|
|
|
|
121
|
|
|
|
|
122
|
|
|
// enable the debuging in ADOdb's datadictionary if the debug-level is greater then 1 |
|
123
|
|
|
if ($this->debug > 1) $this->dict->debug = True; |
|
124
|
|
|
|
|
125
|
|
|
// to allow some of the former translator-functions to be called, we assign ourself as the translator |
|
126
|
|
|
$this->m_oTranslator = &$this; |
|
|
|
|
|
|
127
|
|
|
|
|
128
|
|
|
switch($this->sType) |
|
129
|
|
|
{ |
|
130
|
|
|
case 'maxdb': |
|
131
|
|
|
$this->max_varchar_length = 8000; |
|
132
|
|
|
break; |
|
133
|
|
|
case 'mysql': |
|
134
|
|
|
// since MySQL 5.0 65535, but with utf8 and row-size-limit of 64k: |
|
135
|
|
|
// it's effective 65535/3 - size of other columns, so we use 20000 (mysql silently convert to text anyway) |
|
136
|
|
|
if ((float)$this->m_odb->ServerInfo['version'] >= 5.0) |
|
137
|
|
|
{ |
|
138
|
|
|
$this->max_varchar_length = 20000; |
|
139
|
|
|
} |
|
140
|
|
|
break; |
|
141
|
|
|
} |
|
142
|
|
|
if (is_object($GLOBALS['egw_setup'])) |
|
143
|
|
|
{ |
|
144
|
|
|
$this->system_charset =& $GLOBALS['egw_setup']->system_charset; |
|
145
|
|
|
} |
|
146
|
|
|
elseif (isset($GLOBALS['egw_info']['server']['system_charset'])) |
|
147
|
|
|
{ |
|
148
|
|
|
$this->system_charset = $GLOBALS['egw_info']['server']['system_charset']; |
|
149
|
|
|
} |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
/** |
|
153
|
|
|
* Check if the given $columns exist as index in the index array $indexes |
|
154
|
|
|
* |
|
155
|
|
|
* @param string|array $columns column-name as string or array of column-names plus optional options key |
|
156
|
|
|
* @param array $indexs array of indexes (column-name as string or array of column-names plus optional options key) |
|
157
|
|
|
* @param boolean $ignore_length_limit =false should we take lenght-limits of indexes into account or not |
|
158
|
|
|
* @return boolean true if index over $columns exist in the $indexes array |
|
159
|
|
|
*/ |
|
160
|
|
|
function _in_index($columns, $indexs, $ignore_length_limit=false) |
|
161
|
|
|
{ |
|
162
|
|
|
if (is_array($columns)) |
|
163
|
|
|
{ |
|
164
|
|
|
unset($columns['options']); |
|
165
|
|
|
$columns = implode('-',$columns); |
|
166
|
|
|
if ($ignore_length_limit) $columns = preg_replace('/\(\d+\)/', '', $columns); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
if(is_array($indexs)){ |
|
|
|
|
|
|
170
|
|
|
|
|
171
|
|
|
foreach($indexs as $index) |
|
172
|
|
|
{ |
|
173
|
|
|
if (is_array($index)) |
|
174
|
|
|
{ |
|
175
|
|
|
unset($index['options']); |
|
176
|
|
|
$index = implode('-',$index); |
|
177
|
|
|
} |
|
178
|
|
|
if ($ignore_length_limit) $index = preg_replace('/\(\d+\)/', '', $index); |
|
179
|
|
|
if ($columns == $index) return true; |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
return false; |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Created a table named $sTableName as defined in $aTableDef |
|
189
|
|
|
* |
|
190
|
|
|
* @param string $sTableName |
|
191
|
|
|
* @param array $aTableDef |
|
192
|
|
|
* @param bool $preserveSequence |
|
193
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
194
|
|
|
*/ |
|
195
|
|
|
function CreateTable($sTableName, $aTableDef, $preserveSequence=False) |
|
196
|
|
|
{ |
|
197
|
|
|
if ($this->debug) |
|
198
|
|
|
{ |
|
199
|
|
|
$this->debug_message('schema_proc::CreateTable(%1,%2)',False,$sTableName, $aTableDef); |
|
200
|
|
|
} |
|
201
|
|
|
// for mysql 4.0+ we set the charset for the table |
|
202
|
|
|
if ($this->system_charset && substr($this->sType,0,5) == 'mysql' && |
|
203
|
|
|
(float) $this->m_odb->ServerInfo['version'] >= 4.0) |
|
204
|
|
|
{ |
|
205
|
|
|
$set_table_charset = array($this->sType => 'CHARACTER SET utf8'); |
|
206
|
|
|
} |
|
207
|
|
|
// creating the table |
|
208
|
|
|
$aSql = $this->dict->CreateTableSQL($sTableName,$ado_cols = $this->_egw2adodb_columndef($aTableDef),$set_table_charset); |
|
209
|
|
|
if (!($retVal = $this->ExecuteSQLArray($aSql,2,'CreateTableSQL(%1,%2) sql=%3',False,$sTableName,$ado_cols,$aSql))) |
|
210
|
|
|
{ |
|
211
|
|
|
return $retVal; |
|
212
|
|
|
} |
|
213
|
|
|
// creating unique indices/constrains |
|
214
|
|
|
foreach ($aTableDef['uc'] as $name => $mFields) |
|
215
|
|
|
{ |
|
216
|
|
|
if (empty($mFields)) |
|
217
|
|
|
{ |
|
218
|
|
|
continue; // cant create an index without fields (was observed in broken backups) |
|
219
|
|
|
} |
|
220
|
|
|
if ($this->_in_index($mFields,array($aTableDef['pk']))) |
|
221
|
|
|
{ |
|
222
|
|
|
continue; // is already created as primary key |
|
223
|
|
|
} |
|
224
|
|
|
if (!($retVal = $this->CreateIndex($sTableName,$mFields,true,'',$name))) |
|
225
|
|
|
{ |
|
226
|
|
|
return $retVal; |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
// creation indices |
|
230
|
|
|
foreach ($aTableDef['ix'] as $name => $mFields) |
|
231
|
|
|
{ |
|
232
|
|
|
if (empty($mFields)) |
|
233
|
|
|
{ |
|
234
|
|
|
continue; // cant create an index without fields (was observed in broken backups) |
|
235
|
|
|
} |
|
236
|
|
|
if ($this->_in_index($mFields,array($aTableDef['pk'])) || |
|
237
|
|
|
$this->_in_index($mFields,$aTableDef['uc'])) |
|
238
|
|
|
{ |
|
239
|
|
|
continue; // is already created as primary key or unique index |
|
240
|
|
|
} |
|
241
|
|
|
$options = False; |
|
242
|
|
|
if (is_array($mFields)) |
|
243
|
|
|
{ |
|
244
|
|
|
if (isset($mFields['options'])) // array sets additional options |
|
245
|
|
|
{ |
|
246
|
|
|
if (isset($mFields['options'][$this->sType])) |
|
247
|
|
|
{ |
|
248
|
|
|
$options = $mFields['options'][$this->sType]; // db-specific options, eg. index-type |
|
249
|
|
|
if (!$options) continue; // no index for our db-type |
|
250
|
|
|
} |
|
251
|
|
|
unset($mFields['options']); |
|
252
|
|
|
} |
|
253
|
|
|
} |
|
254
|
|
|
foreach((array)$mFields as $k => $col) |
|
255
|
|
|
{ |
|
256
|
|
|
// only create indexes on text-columns, if (db-)specifiy options are given or FULLTEXT for mysql |
|
257
|
|
|
// most DB's cant do them and give errors |
|
258
|
|
|
if (in_array($aTableDef['fd'][$col]['type'],array('text','longtext'))) |
|
259
|
|
|
{ |
|
260
|
|
|
if (is_array($mFields)) // index over multiple columns including a text column |
|
261
|
|
|
{ |
|
262
|
|
|
$mFields[$k] .= '(32)'; // 32=limit of egw_addressbook_extra.extra_value to fix old backups |
|
263
|
|
|
} |
|
264
|
|
|
elseif (!$options) // index over a single text column and no options given |
|
265
|
|
|
{ |
|
266
|
|
|
if ($this->sType == 'mysql') |
|
267
|
|
|
{ |
|
268
|
|
|
$options = 'FULLTEXT'; |
|
269
|
|
|
} |
|
270
|
|
|
else |
|
271
|
|
|
{ |
|
272
|
|
|
continue 2; // ignore that index, 2=not just column but whole index! |
|
273
|
|
|
} |
|
274
|
|
|
} |
|
275
|
|
|
} |
|
276
|
|
|
} |
|
277
|
|
|
if (!($retVal = $this->CreateIndex($sTableName,$mFields,false,$options,$name))) |
|
278
|
|
|
{ |
|
279
|
|
|
return $retVal; |
|
280
|
|
|
} |
|
281
|
|
|
} |
|
282
|
|
|
// preserve last value of an old sequence |
|
283
|
|
|
if ($this->sType == 'pgsql' && $preserveSequence && $this->pgsql_old_seq) |
|
284
|
|
|
{ |
|
285
|
|
|
if (($seq = $this->_PostgresHasOldSequence($sTableName))) |
|
286
|
|
|
{ |
|
287
|
|
|
$this->pgsql_old_seq = $this->pgsql_old_seq + 1; |
|
288
|
|
|
$this->m_odb->query("ALTER SEQUENCE $seq RESTART WITH " . $this->pgsql_old_seq,__LINE__,__FILE__); |
|
289
|
|
|
} |
|
290
|
|
|
$this->pgsql_old_seq = 0; |
|
291
|
|
|
} |
|
292
|
|
|
return $retVal; |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* Drops all tables in $aTables |
|
297
|
|
|
* |
|
298
|
|
|
* @param array $aTables array of eGW table-definitions |
|
299
|
|
|
* @param boolean $bOutputHTML should we give diagnostics, default False |
|
300
|
|
|
* @return boolean True if no error, else False |
|
301
|
|
|
*/ |
|
302
|
|
|
function DropAllTables($aTables, $bOutputHTML=False) |
|
303
|
|
|
{ |
|
304
|
|
|
if(!is_array($aTables) || !isset($this->m_odb)) |
|
|
|
|
|
|
305
|
|
|
{ |
|
306
|
|
|
return False; |
|
307
|
|
|
} |
|
308
|
|
|
// set our debug-mode or $bOutputHTML is the other one is set |
|
309
|
|
|
if ($this->debug) $bOutputHTML = True; |
|
310
|
|
|
if ($bOutputHTML && !$this->debug) $this->debug = 2; |
|
311
|
|
|
|
|
312
|
|
|
foreach(array_keys($aTables) as $sTableName) |
|
313
|
|
|
{ |
|
314
|
|
|
if($this->DropTable($sTableName)) |
|
315
|
|
|
{ |
|
316
|
|
|
if($bOutputHTML) |
|
317
|
|
|
{ |
|
318
|
|
|
echo '<br>Drop Table <b>' . $sTableName . '</b>'; |
|
319
|
|
|
} |
|
320
|
|
|
} |
|
321
|
|
|
else |
|
322
|
|
|
{ |
|
323
|
|
|
return False; |
|
324
|
|
|
} |
|
325
|
|
|
} |
|
326
|
|
|
return True; |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
/** |
|
330
|
|
|
* Drops the table $sTableName |
|
331
|
|
|
* |
|
332
|
|
|
* @param string $sTableName |
|
333
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
334
|
|
|
*/ |
|
335
|
|
|
function DropTable($sTableName) |
|
336
|
|
|
{ |
|
337
|
|
|
if ($this->sType == 'pgsql') $this->_PostgresTestDropOldSequence($sTableName); |
|
338
|
|
|
|
|
339
|
|
|
$aSql = $this->dict->DropTableSql($sTableName); |
|
340
|
|
|
|
|
341
|
|
|
return $this->ExecuteSQLArray($aSql,2,'DropTable(%1) sql=%2',False,$sTableName,$aSql); |
|
342
|
|
|
} |
|
343
|
|
|
|
|
344
|
|
|
/** |
|
345
|
|
|
* Drops column $sColumnName from table $sTableName |
|
346
|
|
|
* |
|
347
|
|
|
* @param string $sTableName table-name |
|
348
|
|
|
* @param array $aTableDef eGW table-defintion |
|
349
|
|
|
* @param string $sColumnName column-name |
|
350
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
351
|
|
|
*/ |
|
352
|
|
|
function DropColumn($sTableName, $aTableDef, $sColumnName) |
|
353
|
|
|
{ |
|
354
|
|
|
unset($aTableDef); // not used, but required by function signature |
|
355
|
|
|
|
|
356
|
|
|
if (!($table_def = $this->GetTableDefinition($sTableName))) return 0; |
|
357
|
|
|
unset($table_def['fd'][$sColumnName]); |
|
358
|
|
|
|
|
359
|
|
|
$aSql = $this->dict->DropColumnSql($sTableName,$sColumnName,$ado_table=$this->_egw2adodb_columndef($table_def)); |
|
360
|
|
|
|
|
361
|
|
|
return $this->ExecuteSQLArray($aSql,2,'DropColumnSQL(%1,%2,%3) sql=%4',False,$sTableName,$sColumnName,$ado_table,$aSql); |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
/** |
|
365
|
|
|
* Renames table $sOldTableName to $sNewTableName |
|
366
|
|
|
* |
|
367
|
|
|
* @param string $sOldTableName old (existing) table-name |
|
368
|
|
|
* @param string $sNewTableName new table-name |
|
369
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
370
|
|
|
*/ |
|
371
|
|
|
function RenameTable($sOldTableName, $sNewTableName) |
|
372
|
|
|
{ |
|
373
|
|
|
// if we have an old postgres sequence or index (the ones not linked to the table), |
|
374
|
|
|
// we create a new table, copy the content and drop the old one |
|
375
|
|
|
if ($this->sType == 'pgsql') |
|
376
|
|
|
{ |
|
377
|
|
|
if (!($table_def = $this->GetTableDefinition($sOldTableName))) return 0; |
|
378
|
|
|
|
|
379
|
|
|
// only use old PostgreSQL stuff, if we have an old sequence, otherwise rely on it being new enough |
|
380
|
|
|
if ($this->_PostgresHasOldSequence($sOldTableName,True) && |
|
381
|
|
|
(count($table_def['pk']) || count($table_def['ix']) || count($table_def['uc']))) |
|
382
|
|
|
{ |
|
383
|
|
|
if ($this->adodb->BeginTrans() && |
|
384
|
|
|
$this->CreateTable($sNewTableName,$table_def,True) && |
|
385
|
|
|
$this->m_odb->query("INSERT INTO $sNewTableName SELECT * FROM $sOldTableName",__LINE__,__FILE__) && |
|
386
|
|
|
// sequence must be updated, after inserts containing pkey, otherwise new inserst will fail! |
|
387
|
|
|
(count($table_def['pk']) !== 1 || $this->UpdateSequence($sNewTableName, $table_def['pk'][0])) && |
|
388
|
|
|
$this->DropTable($sOldTableName)) |
|
389
|
|
|
{ |
|
390
|
|
|
$this->adodb->CommitTrans(); |
|
391
|
|
|
return 2; |
|
392
|
|
|
} |
|
393
|
|
|
$this->adodb->RollbackTrans(); |
|
394
|
|
|
return 0; |
|
395
|
|
|
} |
|
396
|
|
|
} |
|
397
|
|
|
$aSql = $this->dict->RenameTableSQL($sOldTableName, $sNewTableName); |
|
398
|
|
|
|
|
399
|
|
|
return $this->ExecuteSQLArray($aSql,2,'RenameTableSQL(%1,%2) sql=%3',False,$sOldTableName,$sNewTableName,$aSql); |
|
400
|
|
|
} |
|
401
|
|
|
|
|
402
|
|
|
/** |
|
403
|
|
|
* Check if we have an old, not automaticaly droped sequence |
|
404
|
|
|
* |
|
405
|
|
|
* @param string $sTableName |
|
406
|
|
|
* @param bool $preserveValue |
|
407
|
|
|
* @return boolean|string sequence-name or false |
|
408
|
|
|
*/ |
|
409
|
|
|
function _PostgresHasOldSequence($sTableName,$preserveValue=False) |
|
410
|
|
|
{ |
|
411
|
|
|
if ($this->sType != 'pgsql') return false; |
|
412
|
|
|
|
|
413
|
|
|
$seq = $this->adodb->GetOne("SELECT d.adsrc FROM pg_attribute a, pg_class c, pg_attrdef d WHERE c.relname='$sTableName' AND c.oid=d.adrelid AND d.adsrc LIKE '%seq_$sTableName''::text)' AND a.attrelid=c.oid AND d.adnum=a.attnum"); |
|
414
|
|
|
$seq2 = $this->adodb->GetOne("SELECT d.adsrc FROM pg_attribute a, pg_class c, pg_attrdef d WHERE c.relname='$sTableName' AND c.oid=d.adrelid AND d.adsrc LIKE '%$sTableName%_seq''::text)' AND a.attrelid=c.oid AND d.adnum=a.attnum"); |
|
415
|
|
|
|
|
416
|
|
|
$matches = null; |
|
417
|
|
|
if ($seq && preg_match('/^nextval\(\'(.*)\'/',$seq,$matches)) |
|
418
|
|
|
{ |
|
419
|
|
|
if ($preserveValue) $this->pgsql_old_seq = $this->adodb->GetOne("SELECT last_value FROM " . $matches[1]); |
|
420
|
|
|
return $matches[1]; |
|
421
|
|
|
} |
|
422
|
|
|
if ($seq2 && preg_match('/^nextval\(\'public\.(.*)\'/',$seq2,$matches)) |
|
423
|
|
|
{ |
|
424
|
|
|
if ($preserveValue) $this->pgsql_old_seq = $this->adodb->GetOne("SELECT last_value FROM " . $matches[1]); |
|
425
|
|
|
return $matches[1]; |
|
426
|
|
|
} |
|
427
|
|
|
return false; |
|
428
|
|
|
} |
|
429
|
|
|
|
|
430
|
|
|
/** |
|
431
|
|
|
* Check if we have an old, not automaticaly droped sequence and drop it |
|
432
|
|
|
* |
|
433
|
|
|
* @param $sTableName |
|
434
|
|
|
*/ |
|
435
|
|
|
function _PostgresTestDropOldSequence($sTableName) |
|
436
|
|
|
{ |
|
437
|
|
|
$this->pgsql_old_seq = 0; |
|
438
|
|
|
if ($this->sType == 'pgsql' && ($seq = $this->_PostgresHasOldSequence($sTableName))) |
|
439
|
|
|
{ |
|
440
|
|
|
// only drop sequence, if there is no dependency on it |
|
441
|
|
|
if (!$this->adodb->GetOne("SELECT relname FROM pg_class JOIN pg_depend ON pg_class.relfilenode=pg_depend.objid WHERE relname='$seq' AND relkind='S' AND deptype='i'")) |
|
442
|
|
|
{ |
|
443
|
|
|
$this->query('DROP SEQUENCE '.$seq,__LINE__,__FILE__); |
|
|
|
|
|
|
444
|
|
|
} |
|
445
|
|
|
} |
|
446
|
|
|
} |
|
447
|
|
|
|
|
448
|
|
|
/** |
|
449
|
|
|
* Changes one (exiting) column in a table |
|
450
|
|
|
* |
|
451
|
|
|
* @param string $sTableName table-name |
|
452
|
|
|
* @param string $sColumnName column-name |
|
453
|
|
|
* @param array $aColumnDef new column-definition |
|
454
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
455
|
|
|
*/ |
|
456
|
|
|
function AlterColumn($sTableName, $sColumnName, $aColumnDef) |
|
457
|
|
|
{ |
|
458
|
|
|
if (!($table_def = $this->GetTableDefinition($sTableName))) return 0; |
|
459
|
|
|
|
|
460
|
|
|
// PostgreSQL: varchar or ascii column shortened, use substring to avoid error if current content is to long |
|
461
|
|
|
if($this->sType == 'pgsql' && in_array($table_def['fd'][$sColumnName]['type'], array('varchar', 'ascii')) && |
|
462
|
|
|
in_array($aColumnDef['type'], array('varchar', 'ascii')) && |
|
463
|
|
|
$table_def['fd'][$sColumnName]['precision'] > $aColumnDef['precision']) |
|
464
|
|
|
{ |
|
465
|
|
|
$this->m_odb->update($sTableName, array( |
|
466
|
|
|
"$sColumnName=SUBSTRING($sColumnName FROM 1 FOR ".(int)$aColumnDef['precision'].')', |
|
467
|
|
|
), "LENGTH($sColumnName) > ".(int)$aColumnDef['precision'], __LINE__, __FILE__); |
|
468
|
|
|
|
|
469
|
|
|
if (($shortend = $this->m_odb->affected_rows())) |
|
470
|
|
|
{ |
|
471
|
|
|
error_log(__METHOD__."('$sTableName', '$sColumnName', ".array2string($aColumnDef).") $shortend values shortened"); |
|
472
|
|
|
} |
|
473
|
|
|
} |
|
474
|
|
|
$table_def['fd'][$sColumnName] = $aColumnDef; |
|
475
|
|
|
|
|
476
|
|
|
$aSql = $this->dict->AlterColumnSQL($sTableName,$ado_col = $this->_egw2adodb_columndef(array( |
|
477
|
|
|
'fd' => array($sColumnName => $aColumnDef), |
|
478
|
|
|
'pk' => array(), |
|
479
|
|
|
)),$ado_table=$this->_egw2adodb_columndef($table_def)); |
|
480
|
|
|
|
|
481
|
|
|
return $this->ExecuteSQLArray($aSql,2,'AlterColumnSQL(%1,%2,%3) sql=%4',False,$sTableName,$ado_col,$ado_table,$aSql); |
|
482
|
|
|
} |
|
483
|
|
|
|
|
484
|
|
|
/** |
|
485
|
|
|
* Renames column $sOldColumnName to $sNewColumnName in table $sTableName |
|
486
|
|
|
* |
|
487
|
|
|
* @param string $sTableName table-name |
|
488
|
|
|
* @param string $sOldColumnName old (existing) column-name |
|
489
|
|
|
* @param string $sNewColumnName new column-name |
|
490
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
491
|
|
|
*/ |
|
492
|
|
|
function RenameColumn($sTableName, $sOldColumnName, $sNewColumnName) |
|
493
|
|
|
{ |
|
494
|
|
|
$table_def = $this->GetTableDefinition($sTableName); |
|
495
|
|
|
$old_def = array(); |
|
496
|
|
|
|
|
497
|
|
|
if (isset($table_def['fd'][$sOldColumnName])) |
|
498
|
|
|
{ |
|
499
|
|
|
$old_def = $table_def['fd'][$sOldColumnName]; |
|
500
|
|
|
} |
|
501
|
|
|
else |
|
502
|
|
|
{ |
|
503
|
|
|
foreach($table_def['fd'] as $col => $def) |
|
504
|
|
|
{ |
|
505
|
|
|
if (strtolower($col) == strtolower($sOldColumnName)) |
|
506
|
|
|
{ |
|
507
|
|
|
$old_def = $def; |
|
508
|
|
|
break; |
|
509
|
|
|
} |
|
510
|
|
|
} |
|
511
|
|
|
} |
|
512
|
|
|
$col_def = $this->_egw2adodb_columndef(array( |
|
513
|
|
|
'fd' => array($sNewColumnName => $old_def), |
|
514
|
|
|
'pk' => array(), |
|
515
|
|
|
)); |
|
516
|
|
|
|
|
517
|
|
|
$aSql = $this->dict->RenameColumnSQL($sTableName,$sOldColumnName,$sNewColumnName,$col_def); |
|
518
|
|
|
|
|
519
|
|
|
return $this->ExecuteSQLArray($aSql,2,'RenameColumnSQL(%1,%2,%3) sql=%4',False,$sTableName,$sOldColumnName, $sNewColumnName,$aSql); |
|
520
|
|
|
} |
|
521
|
|
|
|
|
522
|
|
|
/** |
|
523
|
|
|
* Add one (new) column to a table |
|
524
|
|
|
* |
|
525
|
|
|
* @param string $sTableName table-name |
|
526
|
|
|
* @param string $sColumnName column-name |
|
527
|
|
|
* @param array $aColumnDef column-definition |
|
528
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
529
|
|
|
*/ |
|
530
|
|
|
function AddColumn($sTableName, $sColumnName, $aColumnDef) |
|
531
|
|
|
{ |
|
532
|
|
|
$aSql = $this->dict->AddColumnSQL($sTableName,$ado_cols = $this->_egw2adodb_columndef(array( |
|
533
|
|
|
'fd' => array($sColumnName => $aColumnDef), |
|
534
|
|
|
'pk' => array(), |
|
535
|
|
|
))); |
|
536
|
|
|
|
|
537
|
|
|
return $this->ExecuteSQLArray($aSql,2,'AlterColumnSQL(%1,%2,%3) sql=%4',False,$sTableName,$sColumnName, $aColumnDef,$aSql); |
|
538
|
|
|
} |
|
539
|
|
|
|
|
540
|
|
|
/** |
|
541
|
|
|
* Create an (unique) Index over one or more columns |
|
542
|
|
|
* |
|
543
|
|
|
* @param string $sTableName table-name |
|
544
|
|
|
* @param string|array $aColumnNames column(s) for the index |
|
545
|
|
|
* @param boolean $bUnique =false true for a unique index, default false |
|
546
|
|
|
* @param array|string $options ='' db-sepecific options, default '' = none |
|
547
|
|
|
* @param string $sIdxName ='' name of the index, if not given (default) its created automaticaly |
|
548
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
549
|
|
|
*/ |
|
550
|
|
|
function CreateIndex($sTableName,$aColumnNames,$bUnique=false,$options='',$sIdxName='') |
|
551
|
|
|
{ |
|
552
|
|
|
// remove length limits from column names, if DB type is NOT MySQL |
|
553
|
|
|
if ($this->sType != 'mysql') |
|
554
|
|
|
{ |
|
555
|
|
|
$aColumnNames = preg_replace('/ *\(\d+\)$/','',$aColumnNames); |
|
556
|
|
|
} |
|
557
|
|
|
if (!$sIdxName || is_numeric($sIdxName)) |
|
558
|
|
|
{ |
|
559
|
|
|
$sIdxName = $this->_index_name($sTableName,$aColumnNames); |
|
560
|
|
|
} |
|
561
|
|
|
if (!is_array($options)) $options = $options ? array($options) : array(); |
|
562
|
|
|
if ($bUnique) $options[] = 'UNIQUE'; |
|
563
|
|
|
|
|
564
|
|
|
// if index already exists drop it first |
|
565
|
|
|
$definition = array(); |
|
566
|
|
|
$this->GetIndexes($sTableName, $definition); |
|
567
|
|
|
$type = $bUnique ? 'uc' : 'ix'; |
|
568
|
|
|
if ($this->_in_index($aColumnNames, $definition[$type], true) || |
|
569
|
|
|
// sometimes index is listed as unique index too --> ignore that |
|
570
|
|
|
($type == 'ix' && $this->_in_index($aColumnNames, $definition['uc'], true))) |
|
571
|
|
|
{ |
|
572
|
|
|
//error_log(__METHOD__."('$sTableName', ['".implode("','", (array)$aColumnNames)."'], $bUnique, ...) already exists --> droping it first"); |
|
573
|
|
|
$this->DropIndex($sTableName, (array)$aColumnNames); |
|
574
|
|
|
} |
|
575
|
|
|
|
|
576
|
|
|
$aSql = $this->dict->CreateIndexSQL($sIdxName,$sTableName,$aColumnNames,$options); |
|
577
|
|
|
|
|
578
|
|
|
return $this->ExecuteSQLArray($aSql,2,'CreateIndexSQL(%1,%2,%3,%4) sql=%5',False,$sTableName,$aColumnNames,$options,$sIdxName,$aSql); |
|
579
|
|
|
} |
|
580
|
|
|
|
|
581
|
|
|
/** |
|
582
|
|
|
* Drop an Index |
|
583
|
|
|
* |
|
584
|
|
|
* @param string $sTableName table-name |
|
585
|
|
|
* @param array|string $aColumnNames columns of the index or the name of the index |
|
586
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
587
|
|
|
*/ |
|
588
|
|
|
function DropIndex($sTableName,$aColumnNames) |
|
589
|
|
|
{ |
|
590
|
|
|
if (is_array($aColumnNames)) |
|
591
|
|
|
{ |
|
592
|
|
|
$indexes = $this->dict->MetaIndexes($sTableName); |
|
593
|
|
|
|
|
594
|
|
|
if ($indexes === False) |
|
595
|
|
|
{ |
|
596
|
|
|
// if MetaIndexes is not availible for the DB, we try the name the index was created with |
|
597
|
|
|
// this fails if one of the columns have been renamed |
|
598
|
|
|
$sIdxName = $this->_index_name($sTableName,$aColumnNames); |
|
599
|
|
|
} |
|
600
|
|
|
else |
|
601
|
|
|
{ |
|
602
|
|
|
foreach($indexes as $idx => $idx_data) |
|
603
|
|
|
{ |
|
604
|
|
|
if (strtolower(implode(':',$idx_data['columns'])) == implode(':',$aColumnNames)) |
|
605
|
|
|
{ |
|
606
|
|
|
$sIdxName = $idx; |
|
607
|
|
|
break; |
|
608
|
|
|
} |
|
609
|
|
|
} |
|
610
|
|
|
} |
|
611
|
|
|
} |
|
612
|
|
|
else |
|
613
|
|
|
{ |
|
614
|
|
|
$sIdxName = $aColumnNames; |
|
615
|
|
|
} |
|
616
|
|
|
if(!$sIdxName) |
|
617
|
|
|
{ |
|
618
|
|
|
return True; |
|
619
|
|
|
} |
|
620
|
|
|
$aSql = $this->dict->DropIndexSQL($sIdxName,$sTableName); |
|
621
|
|
|
|
|
622
|
|
|
return $this->ExecuteSQLArray($aSql,2,'DropIndexSQL(%1(%2),%3) sql=%4',False,$sIdxName,$aColumnNames,$sTableName,$aSql); |
|
623
|
|
|
} |
|
624
|
|
|
|
|
625
|
|
|
/** |
|
626
|
|
|
* Updating the sequence-value, after eg. copying data via RefreshTable |
|
627
|
|
|
* @param string $sTableName table-name |
|
628
|
|
|
* @param string $sColumnName column-name, which default is set to nextval() |
|
629
|
|
|
*/ |
|
630
|
|
|
function UpdateSequence($sTableName,$sColumnName) |
|
631
|
|
|
{ |
|
632
|
|
|
switch($this->sType) |
|
633
|
|
|
{ |
|
634
|
|
|
case 'pgsql': |
|
635
|
|
|
// identify the sequence name, ADOdb uses a different name or it might be renamed |
|
636
|
|
|
$columns = $this->dict->MetaColumns($sTableName); |
|
637
|
|
|
$seq_name = 'seq_'.$sTableName; |
|
638
|
|
|
$matches = null; |
|
639
|
|
|
if (preg_match("/nextval\('([^']+)'::(text|regclass)\)/",$columns[strtoupper($sColumnName)]->default_value,$matches)) |
|
640
|
|
|
{ |
|
641
|
|
|
$seq_name = $matches[1]; |
|
642
|
|
|
} |
|
643
|
|
|
$sql = "SELECT setval('$seq_name',MAX($sColumnName)) FROM $sTableName"; |
|
644
|
|
|
if($this->debug) { echo "<br>Updating sequence '$seq_name using: $sql"; } |
|
645
|
|
|
return $this->query($sql,__LINE__,__FILE__); |
|
646
|
|
|
} |
|
647
|
|
|
return True; |
|
648
|
|
|
} |
|
649
|
|
|
|
|
650
|
|
|
/** |
|
651
|
|
|
* This function manually re-created the table incl. primary key and all other indices |
|
652
|
|
|
* |
|
653
|
|
|
* It is meant to use if the primary key, existing indices or column-order changes or |
|
654
|
|
|
* columns are not longer used or new columns need to be created (with there default value or NULL) |
|
655
|
|
|
* Beside the default-value in the schema, one can give extra defaults via $aDefaults to eg. use an |
|
656
|
|
|
* other colum or function to set the value of a new or changed column |
|
657
|
|
|
* |
|
658
|
|
|
* @param string $sTableName table-name |
|
659
|
|
|
* @param array $aTableDef eGW table-defintion |
|
660
|
|
|
* @param array|boolean $aDefaults array with default for the colums during copying, values are either (old) column-names or quoted string-literals |
|
661
|
|
|
*/ |
|
662
|
|
|
function RefreshTable($sTableName, $aTableDef, $aDefaults=False) |
|
663
|
|
|
{ |
|
664
|
|
|
if($this->debug) { echo "<p>schema_proc::RefreshTable('$sTableName',"._debug_array($aTableDef,False).")\n"; } |
|
665
|
|
|
|
|
666
|
|
|
$old_table_def = $this->GetTableDefinition($sTableName); |
|
667
|
|
|
|
|
668
|
|
|
$tmp_name = 'tmp_'.$sTableName; |
|
669
|
|
|
$this->m_odb->transaction_begin(); |
|
670
|
|
|
|
|
671
|
|
|
$select = array(); |
|
672
|
|
|
$blob_column_included = $auto_column_included = False; |
|
673
|
|
|
foreach($aTableDef['fd'] as $name => $data) |
|
674
|
|
|
{ |
|
675
|
|
|
// new auto column with no default or explicit NULL as default (can be an existing column too!) |
|
676
|
|
|
if ($data['type'] == 'auto' && |
|
677
|
|
|
(!isset($old_table_def['fd'][$name]) && (!$aDefaults || !isset($aDefaults[$name])) || |
|
678
|
|
|
$aDefaults && strtoupper($aDefaults[$name]) == 'NULL')) |
|
679
|
|
|
{ |
|
680
|
|
|
$sequence_name = $sTableName.'_'.$name.'_seq'; |
|
681
|
|
|
switch($GLOBALS['egw_setup']->db->Type) |
|
682
|
|
|
{ |
|
683
|
|
|
case 'mysql': |
|
684
|
|
|
$value = 'NULL'; break; |
|
685
|
|
|
case 'pgsql': |
|
686
|
|
|
$value = "nextval('$sequence_name'::regclass)"; break; |
|
687
|
|
|
default: |
|
688
|
|
|
$value = "nextval('$sequence_name')"; break; |
|
689
|
|
|
} |
|
690
|
|
|
} |
|
691
|
|
|
elseif ($aDefaults && isset($aDefaults[$name])) // use given default |
|
692
|
|
|
{ |
|
693
|
|
|
$value = $aDefaults[$name]; |
|
694
|
|
|
} |
|
695
|
|
|
elseif (isset($old_table_def['fd'][$name])) // existing column, use its value => column-name in query |
|
696
|
|
|
{ |
|
697
|
|
|
$value = $name; |
|
698
|
|
|
|
|
699
|
|
|
// varchar or ascii column shortened, use substring to avoid error if current content is to long |
|
700
|
|
|
if(in_array($old_table_def['fd'][$name]['type'], array('varchar', 'ascii')) && |
|
701
|
|
|
in_array($data['type'], array('varchar', 'ascii')) && |
|
702
|
|
|
$old_table_def['fd'][$name]['precision'] > $data['precision']) |
|
703
|
|
|
{ |
|
704
|
|
|
$value = "SUBSTRING($value FROM 1 FOR ".(int)$data['precision'].')'; |
|
705
|
|
|
} |
|
706
|
|
|
if ($this->sType == 'pgsql') // some postgres specific code |
|
707
|
|
|
{ |
|
708
|
|
|
// this is eg. necessary to change a varchar into an int column under postgres |
|
709
|
|
|
if (in_array($old_table_def['fd'][$name]['type'],array('char','varchar','text','blob')) && |
|
710
|
|
|
in_array($data['type'],array('int','decimal'))) |
|
711
|
|
|
{ |
|
712
|
|
|
$value = "to_number($name,'S9999999999999D99')"; |
|
713
|
|
|
} |
|
714
|
|
|
// blobs cant be casted to text |
|
715
|
|
|
elseif($old_table_def['fd'][$name]['type'] == 'blob' && $data['type'] == 'text') |
|
716
|
|
|
{ |
|
717
|
|
|
$value = "ENCODE($value,'escape')"; |
|
718
|
|
|
} |
|
719
|
|
|
// cast everything which is a different type |
|
720
|
|
|
elseif($old_table_def['fd'][$name]['type'] != $data['type'] && ($type_translated = $this->TranslateType($data['type']))) |
|
721
|
|
|
{ |
|
722
|
|
|
$value = "CAST($value AS $type_translated)"; |
|
723
|
|
|
} |
|
724
|
|
|
} |
|
725
|
|
|
} |
|
726
|
|
|
else // new column => use default value or NULL |
|
727
|
|
|
{ |
|
728
|
|
|
if (!isset($data['default']) && (!isset($data['nullable']) || $data['nullable'])) |
|
729
|
|
|
{ |
|
730
|
|
|
$value = 'NULL'; |
|
731
|
|
|
} |
|
732
|
|
|
// some stuff is NOT to be quoted |
|
733
|
|
|
elseif (in_array(strtoupper($data['default']),array('CURRENT_TIMESTAMP','CURRENT_DATE','NULL','NOW()'))) |
|
734
|
|
|
{ |
|
735
|
|
|
$value = $data['default']; |
|
736
|
|
|
} |
|
737
|
|
|
else |
|
738
|
|
|
{ |
|
739
|
|
|
$value = $this->m_odb->quote(isset($data['default']) ? $data['default'] : '',$data['type']); |
|
740
|
|
|
} |
|
741
|
|
|
if ($this->sType == 'pgsql') |
|
742
|
|
|
{ |
|
743
|
|
|
// fix for postgres error "no '<' operator for type 'unknown'" |
|
744
|
|
|
if(($type_translated = $this->TranslateType($data['type']))) |
|
745
|
|
|
{ |
|
746
|
|
|
$value = "CAST($value AS $type_translated)"; |
|
747
|
|
|
} |
|
748
|
|
|
} |
|
749
|
|
|
} |
|
750
|
|
|
$blob_column_included = $blob_column_included || in_array($data['type'],array('blob','text','longtext')); |
|
751
|
|
|
$auto_column_included = $auto_column_included || $data['type'] == 'auto'; |
|
752
|
|
|
$select[] = $value; |
|
753
|
|
|
} |
|
754
|
|
|
|
|
755
|
|
|
$extra = ''; |
|
756
|
|
|
$distinct = 'DISTINCT'; |
|
757
|
|
|
switch($this->sType) |
|
758
|
|
|
{ |
|
759
|
|
|
case 'mssql': |
|
760
|
|
|
if ($auto_column_included) $extra = "SET IDENTITY_INSERT $sTableName ON\n"; |
|
761
|
|
|
if ($blob_column_included) $distinct = ''; // no distinct on blob-columns |
|
762
|
|
|
break; |
|
763
|
|
|
} |
|
764
|
|
|
// because of all the trouble with sequences and indexes in the global namespace, |
|
765
|
|
|
// we use an additional temp. table for postgres and not rename the existing one, but drop it. |
|
766
|
|
|
if ($this->sType == 'pgsql') |
|
767
|
|
|
{ |
|
768
|
|
|
$Ok = $this->m_odb->query("SELEcT * INTO TEMPORARY TABLE $tmp_name FROM $sTableName",__LINE__,__FILE__) && |
|
769
|
|
|
$this->DropTable($sTableName); |
|
770
|
|
|
} |
|
771
|
|
|
else |
|
772
|
|
|
{ |
|
773
|
|
|
// drop evtl. existing temp. table eg. from a previous failed upgrade |
|
774
|
|
|
if (($tables = $this->m_odb->table_names(true)) && in_array($tmp_name, $tables)) |
|
775
|
|
|
{ |
|
776
|
|
|
$this->DropTable($tmp_name); |
|
777
|
|
|
} |
|
778
|
|
|
$Ok = $this->RenameTable($sTableName,$tmp_name); |
|
779
|
|
|
} |
|
780
|
|
|
$Ok = $Ok && $this->CreateTable($sTableName,$aTableDef) && |
|
781
|
|
|
$this->m_odb->query($sql_copy_data="$extra INSERT INTO $sTableName (". |
|
782
|
|
|
implode(',',array_keys($aTableDef['fd'])). |
|
783
|
|
|
") SELEcT $distinct ".implode(',',$select)." FROM $tmp_name",__LINE__,__FILE__) && |
|
784
|
|
|
$this->DropTable($tmp_name); |
|
785
|
|
|
//error_log($sql_copy_data); |
|
786
|
|
|
|
|
787
|
|
|
if (!$Ok) |
|
788
|
|
|
{ |
|
789
|
|
|
$this->m_odb->transaction_abort(); |
|
790
|
|
|
return False; |
|
791
|
|
|
} |
|
792
|
|
|
// do we need to update the new sequences value ? |
|
793
|
|
|
if (count($aTableDef['pk']) == 1 && $aTableDef['fd'][$aTableDef['pk'][0]]['type'] == 'auto') |
|
794
|
|
|
{ |
|
795
|
|
|
$this->UpdateSequence($sTableName,$aTableDef['pk'][0]); |
|
796
|
|
|
} |
|
797
|
|
|
$this->m_odb->transaction_commit(); |
|
798
|
|
|
|
|
799
|
|
|
return True; |
|
800
|
|
|
} |
|
801
|
|
|
|
|
802
|
|
|
/** |
|
803
|
|
|
* depricated Function does nothing any more |
|
804
|
|
|
* @depricated |
|
805
|
|
|
*/ |
|
806
|
|
|
function GenerateScripts() |
|
807
|
|
|
{ |
|
808
|
|
|
return True; |
|
809
|
|
|
} |
|
810
|
|
|
|
|
811
|
|
|
/** |
|
812
|
|
|
* Creates all tables for one application |
|
813
|
|
|
* |
|
814
|
|
|
* @param array $aTables array of eGW table-definitions |
|
815
|
|
|
* @param boolean $bOutputHTML =false should we give diagnostics, default False |
|
816
|
|
|
* @return boolean True on success, False if an (fatal) error occured |
|
817
|
|
|
*/ |
|
818
|
|
|
function ExecuteScripts($aTables, $bOutputHTML=False) |
|
819
|
|
|
{ |
|
820
|
|
|
if(!is_array($aTables) || !IsSet($this->m_odb)) |
|
|
|
|
|
|
821
|
|
|
{ |
|
822
|
|
|
return False; |
|
823
|
|
|
} |
|
824
|
|
|
// set our debug-mode or $bOutputHTML is the other one is set |
|
825
|
|
|
if ($this->debug) $bOutputHTML = True; |
|
826
|
|
|
if ($bOutputHTML && !$this->debug) $this->debug = 2; |
|
827
|
|
|
|
|
828
|
|
|
foreach($aTables as $sTableName => $aTableDef) |
|
829
|
|
|
{ |
|
830
|
|
|
if($this->CreateTable($sTableName, $aTableDef)) |
|
831
|
|
|
{ |
|
832
|
|
|
if($bOutputHTML) |
|
833
|
|
|
{ |
|
834
|
|
|
echo '<br>Create Table <b>' . $sTableName . '</b>'; |
|
835
|
|
|
} |
|
836
|
|
|
} |
|
837
|
|
|
else |
|
838
|
|
|
{ |
|
839
|
|
|
if($bOutputHTML) |
|
840
|
|
|
{ |
|
841
|
|
|
echo '<br>Create Table Failed For <b>' . $sTableName . '</b>'; |
|
842
|
|
|
} |
|
843
|
|
|
|
|
844
|
|
|
return False; |
|
845
|
|
|
} |
|
846
|
|
|
} |
|
847
|
|
|
return True; |
|
848
|
|
|
} |
|
849
|
|
|
|
|
850
|
|
|
/** |
|
851
|
|
|
* Return the value of a column |
|
852
|
|
|
* |
|
853
|
|
|
* @param string|integer $value name of field or positional index starting from 0 |
|
854
|
|
|
* @param bool $strip_slashes string escape chars from field(optional), default false |
|
855
|
|
|
* @deprecated use result-set returned by query/select |
|
856
|
|
|
* @return string the field value |
|
857
|
|
|
*/ |
|
858
|
|
|
function f($value,$strip_slashes=False) |
|
859
|
|
|
{ |
|
860
|
|
|
if (!($this->m_odb instanceof Deprecated)) |
|
861
|
|
|
{ |
|
862
|
|
|
throw new Api\Exception\AssertionFailed(__METHOD__.' requires an EGroupware\Api\Db\Deprecated object!'); |
|
863
|
|
|
} |
|
864
|
|
|
return $this->m_odb->f($value,$strip_slashes); |
|
|
|
|
|
|
865
|
|
|
} |
|
866
|
|
|
|
|
867
|
|
|
/** |
|
868
|
|
|
* Number of rows in current result set |
|
869
|
|
|
* |
|
870
|
|
|
* @deprecated use result-set returned by query/select |
|
871
|
|
|
* @return int number of rows |
|
872
|
|
|
*/ |
|
873
|
|
|
function num_rows() |
|
874
|
|
|
{ |
|
875
|
|
|
if (!($this->m_odb instanceof Deprecated)) |
|
876
|
|
|
{ |
|
877
|
|
|
throw new Api\Exception\AssertionFailed(__METHOD__.' requires an EGroupware\Api\Db\Deprecated object!'); |
|
878
|
|
|
} |
|
879
|
|
|
return $this->m_odb->num_rows(); |
|
|
|
|
|
|
880
|
|
|
} |
|
881
|
|
|
|
|
882
|
|
|
/** |
|
883
|
|
|
* Move to the next row in the results set |
|
884
|
|
|
* |
|
885
|
|
|
* @deprecated use result-set returned by query/select |
|
886
|
|
|
* @return bool was another row found? |
|
887
|
|
|
*/ |
|
888
|
|
|
function next_record() |
|
889
|
|
|
{ |
|
890
|
|
|
if (!($this->m_odb instanceof Deprecated)) |
|
891
|
|
|
{ |
|
892
|
|
|
throw new Api\Exception\AssertionFailed(__METHOD__.' requires an EGroupware\Api\Db\Deprecated object!'); |
|
893
|
|
|
} |
|
894
|
|
|
return $this->m_odb->next_record(); |
|
|
|
|
|
|
895
|
|
|
} |
|
896
|
|
|
|
|
897
|
|
|
/** |
|
898
|
|
|
* Execute a query |
|
899
|
|
|
* |
|
900
|
|
|
* @param string $Query_String the query to be executed |
|
901
|
|
|
* @param mixed $line the line method was called from - use __LINE__ |
|
902
|
|
|
* @param string $file the file method was called from - use __FILE__ |
|
903
|
|
|
* @param int $offset row to start from |
|
904
|
|
|
* @param int $num_rows number of rows to return (optional), if unset will use $GLOBALS['phpgw_info']['user']['preferences']['common']['maxmatchs'] |
|
905
|
|
|
* @return ADORecordSet or false, if the query fails |
|
|
|
|
|
|
906
|
|
|
*/ |
|
907
|
|
|
function query($sQuery, $line='', $file='') |
|
908
|
|
|
{ |
|
909
|
|
|
return $this->m_odb->query($sQuery, $line, $file); |
|
910
|
|
|
} |
|
911
|
|
|
|
|
912
|
|
|
/** |
|
913
|
|
|
* Insert a row of data into a table or updates it if $where is given, all data is quoted according to it's type |
|
914
|
|
|
* |
|
915
|
|
|
* @param string $table name of the table |
|
916
|
|
|
* @param array $data with column-name / value pairs |
|
917
|
|
|
* @param mixed $where string with where clause or array with column-name / values pairs to check if a row with that keys already exists, or false for an unconditional insert |
|
918
|
|
|
* if the row exists db::update is called else a new row with $date merged with $where gets inserted (data has precedence) |
|
919
|
|
|
* @param int $line line-number to pass to query |
|
920
|
|
|
* @param string $file file-name to pass to query |
|
921
|
|
|
* @param string $app=false string with name of app, this need to be set in setup anyway!!! |
|
922
|
|
|
* @return ADORecordSet or false, if the query fails |
|
923
|
|
|
*/ |
|
924
|
|
|
function insert($table,$data,$where,$line,$file,$app=False,$use_prepared_statement=false) |
|
925
|
|
|
{ |
|
926
|
|
|
return $this->m_odb->insert($table,$data,$where,$line,$file,$app,$use_prepared_statement); |
|
927
|
|
|
} |
|
928
|
|
|
|
|
929
|
|
|
/** |
|
930
|
|
|
* Execute the Sql statements in an array and give diagnostics, if any error occures |
|
931
|
|
|
* |
|
932
|
|
|
* @param array $aSql array of SQL strings to execute |
|
933
|
|
|
* @param int $debug_level for which debug_level (and higher) should the diagnostics always been printed |
|
934
|
|
|
* @param string $debug variable number of arguments for the debug_message functions in case of an error |
|
935
|
|
|
* @return int 2: no error, 1: errors, but continued, 0: errors aborted |
|
936
|
|
|
*/ |
|
937
|
|
|
function ExecuteSqlArray($aSql,$debug_level) |
|
938
|
|
|
{ |
|
939
|
|
|
if ($this->m_odb->query_log) // we use Db::query to log the queries |
|
940
|
|
|
{ |
|
941
|
|
|
$retval = 2; |
|
942
|
|
|
foreach($aSql as $sql) |
|
943
|
|
|
{ |
|
944
|
|
|
if (!$this->m_odb->query($sql,__LINE__,__FILE__)) |
|
945
|
|
|
{ |
|
946
|
|
|
$retval = 1; |
|
947
|
|
|
} |
|
948
|
|
|
} |
|
949
|
|
|
} |
|
950
|
|
|
else |
|
951
|
|
|
{ |
|
952
|
|
|
$retval = $this->dict->ExecuteSQLArray($aSql); |
|
953
|
|
|
} |
|
954
|
|
|
if ($retval < 2 || $this->debug >= $debug_level || $this->debug > 3) |
|
955
|
|
|
{ |
|
956
|
|
|
$debug_params = func_get_args(); |
|
957
|
|
|
array_shift($debug_params); |
|
958
|
|
|
array_shift($debug_params); |
|
959
|
|
|
call_user_func_array(array($this,'debug_message'),$debug_params); |
|
960
|
|
|
if ($retval < 2 && !$this->dict->debug) |
|
961
|
|
|
{ |
|
962
|
|
|
echo '<p><b>'.$this->adodb->ErrorMsg()."</b></p>\n"; |
|
963
|
|
|
} |
|
964
|
|
|
} |
|
965
|
|
|
return $retval; |
|
966
|
|
|
} |
|
967
|
|
|
|
|
968
|
|
|
/** |
|
969
|
|
|
* Created a (unique) name for an index |
|
970
|
|
|
* |
|
971
|
|
|
* As the length of the index name is limited on some databases, we use two algorithms: |
|
972
|
|
|
* a) we use just the first column-name with and added _2, _3, ... if more indexes uses that column |
|
973
|
|
|
* b) we use the table-names plus all column-names and remove dublicate parts |
|
974
|
|
|
* |
|
975
|
|
|
* @internal |
|
976
|
|
|
* @param $sTableName string name of the table |
|
977
|
|
|
* @param $aColumnNames array of column-names or string with a single column-name |
|
978
|
|
|
* @return string the index-name |
|
979
|
|
|
*/ |
|
980
|
|
|
function _index_name($sTableName,$aColumnNames) |
|
981
|
|
|
{ |
|
982
|
|
|
// this code creates extrem short index-names, eg. for MaxDB |
|
983
|
|
|
// if (isset($this->max_index_length[$this->sType]) && $this->max_index_length[$this->sType] <= 32) |
|
984
|
|
|
// { |
|
985
|
|
|
// static $existing_indexes=array(); |
|
986
|
|
|
// |
|
987
|
|
|
// if (!isset($existing_indexes[$sTableName]) && method_exists($this->adodb,'MetaIndexes')) |
|
988
|
|
|
// { |
|
989
|
|
|
// $existing_indexes[$sTableName] = $this->adodb->MetaIndexes($sTableName); |
|
990
|
|
|
// } |
|
991
|
|
|
// $i = 0; |
|
992
|
|
|
// $firstCol = is_array($aColumnNames) ? $aColumnNames[0] : $aColumnNames; |
|
993
|
|
|
// do |
|
994
|
|
|
// { |
|
995
|
|
|
// ++$i; |
|
996
|
|
|
// $name = $firstCol . ($i > 1 ? '_'.$i : ''); |
|
997
|
|
|
// } |
|
998
|
|
|
// while (isset($existing_indexes[$sTableName][$name]) || isset($existing_indexes[strtoupper($sTableName)][strtoupper($name)])); |
|
999
|
|
|
// |
|
1000
|
|
|
// $existing_indexes[$sTableName][$name] = True; // mark it as existing now |
|
1001
|
|
|
// |
|
1002
|
|
|
// return $name; |
|
1003
|
|
|
// } |
|
1004
|
|
|
// This code creates longer index-names incl. the table-names and the used columns |
|
1005
|
|
|
$table = str_replace(array('phpgw_','egw_'),'',$sTableName); |
|
1006
|
|
|
// if the table-name or a part of it is repeated in the column-name, remove it there |
|
1007
|
|
|
$remove[] = $table.'_'; |
|
|
|
|
|
|
1008
|
|
|
// also remove 3 or 4 letter shortcuts of the table- or app-name |
|
1009
|
|
|
$remove[] = substr($table,0,3).'_'; |
|
1010
|
|
|
$remove[] = substr($table,0,4).'_'; |
|
1011
|
|
|
// if the table-name consists of '_' limtied parts, remove occurences of these parts too |
|
1012
|
|
|
foreach (explode('_',$table) as $part) |
|
1013
|
|
|
{ |
|
1014
|
|
|
$remove[] = $part.'_'; |
|
1015
|
|
|
} |
|
1016
|
|
|
$cols = str_replace($remove,'',$aColumnNames); |
|
1017
|
|
|
|
|
1018
|
|
|
$name = $sTableName.'_'.(is_array($cols) ? implode('_',$cols) : $cols); |
|
1019
|
|
|
// remove length limits from column names |
|
1020
|
|
|
$name = preg_replace('/ *\(\d+\)/','',$name); |
|
1021
|
|
|
|
|
1022
|
|
|
// this code creates a fixed short index-names (30 chars) from the long and unique name, eg. for MaxDB or Oracle |
|
1023
|
|
|
if (isset($this->max_index_length[$this->sType]) && $this->max_index_length[$this->sType] <= 32 && strlen($name) > 30 || |
|
|
|
|
|
|
1024
|
|
|
strlen($name) >= 64) // even mysql has a limit here ;-) |
|
1025
|
|
|
{ |
|
1026
|
|
|
$name = "i".substr(md5($name),0,29); |
|
1027
|
|
|
} |
|
1028
|
|
|
return $name; |
|
1029
|
|
|
} |
|
1030
|
|
|
|
|
1031
|
|
|
/** |
|
1032
|
|
|
* Giving a non-fatal error-message |
|
1033
|
|
|
*/ |
|
1034
|
|
|
function error($str) |
|
1035
|
|
|
{ |
|
1036
|
|
|
echo "<p><b>Error:</b> $str</p>"; |
|
1037
|
|
|
} |
|
1038
|
|
|
|
|
1039
|
|
|
/** |
|
1040
|
|
|
* Giving a fatal error-message and exiting |
|
1041
|
|
|
*/ |
|
1042
|
|
|
function fatal($str) |
|
1043
|
|
|
{ |
|
1044
|
|
|
echo "<p><b>Fatal Error:</b> $str</p>"; |
|
1045
|
|
|
exit; |
|
|
|
|
|
|
1046
|
|
|
} |
|
1047
|
|
|
|
|
1048
|
|
|
/** |
|
1049
|
|
|
* Gives out a debug-message with certain parameters |
|
1050
|
|
|
* |
|
1051
|
|
|
* All permanent debug-messages in the calendar should be done by this function !!! |
|
1052
|
|
|
* (In future they may be logged or sent as xmlrpc-faults back.) |
|
1053
|
|
|
* |
|
1054
|
|
|
* Permanent debug-message need to make sure NOT to give secret information like passwords !!! |
|
1055
|
|
|
* |
|
1056
|
|
|
* This function do NOT honor the setting of the debug variable, you may use it like |
|
1057
|
|
|
* if ($this->debug > N) $this->debug_message('Error ;-)'); |
|
1058
|
|
|
* |
|
1059
|
|
|
* The parameters get formated depending on their type. |
|
1060
|
|
|
* |
|
1061
|
|
|
* @param $msg string message with parameters/variables like lang(), eg. '%1' |
|
1062
|
|
|
* @param $backtrace include a function-backtrace, default True=On |
|
|
|
|
|
|
1063
|
|
|
* should only be set to False=Off, if your code ensures a call with backtrace=On was made before !!! |
|
1064
|
|
|
* @param $param mixed a variable number of parameters, to be inserted in $msg |
|
1065
|
|
|
* arrays get serialized with print_r() ! |
|
1066
|
|
|
*/ |
|
1067
|
|
|
function debug_message($msg,$backtrace=True) |
|
1068
|
|
|
{ |
|
1069
|
|
|
for($i = 2; $i < func_num_args(); ++$i) |
|
1070
|
|
|
{ |
|
1071
|
|
|
$param = func_get_arg($i); |
|
1072
|
|
|
|
|
1073
|
|
|
if (is_null($param)) |
|
1074
|
|
|
{ |
|
1075
|
|
|
$param='NULL'; |
|
1076
|
|
|
} |
|
1077
|
|
|
else |
|
1078
|
|
|
{ |
|
1079
|
|
|
switch(gettype($param)) |
|
1080
|
|
|
{ |
|
1081
|
|
|
case 'string': |
|
1082
|
|
|
$param = "'$param'"; |
|
1083
|
|
|
break; |
|
1084
|
|
|
case 'array': |
|
1085
|
|
|
case 'object': |
|
1086
|
|
|
$content = @current($param); |
|
1087
|
|
|
$do_pre = is_array($param) ? count($param) > 6 || is_array($content)&&count($content) : True; |
|
1088
|
|
|
$param = ($do_pre ? '<pre>' : '').print_r($param,True).($do_pre ? '</pre>' : ''); |
|
1089
|
|
|
break; |
|
1090
|
|
|
case 'boolean': |
|
1091
|
|
|
$param = $param ? 'True' : 'False'; |
|
1092
|
|
|
break; |
|
1093
|
|
|
} |
|
1094
|
|
|
} |
|
1095
|
|
|
$msg = str_replace('%'.($i-1),$param,$msg); |
|
1096
|
|
|
} |
|
1097
|
|
|
echo '<p>'.$msg."<br>\n".($backtrace ? 'Backtrace: '.function_backtrace(1)."</p>\n" : ''); |
|
1098
|
|
|
} |
|
1099
|
|
|
|
|
1100
|
|
|
/** |
|
1101
|
|
|
* Converts an eGW table-definition array into an ADOdb column-definition string |
|
1102
|
|
|
* |
|
1103
|
|
|
* @internal |
|
1104
|
|
|
* @param array $aTableDef eGW table-defintion |
|
1105
|
|
|
* @return string ADOdb column-definition string (comma separated) |
|
1106
|
|
|
*/ |
|
1107
|
|
|
function _egw2adodb_columndef($aTableDef) |
|
1108
|
|
|
{ |
|
1109
|
|
|
$ado_defs = array(); |
|
1110
|
|
|
foreach($aTableDef['fd'] as $col => $col_data) |
|
1111
|
|
|
{ |
|
1112
|
|
|
$ado_col = False; |
|
1113
|
|
|
|
|
1114
|
|
|
switch($col_data['type']) |
|
1115
|
|
|
{ |
|
1116
|
|
|
case 'auto': |
|
1117
|
|
|
$ado_col = 'I AUTOINCREMENT NOTNULL'; |
|
1118
|
|
|
unset($col_data['nullable']); // else we set it twice |
|
1119
|
|
|
break; |
|
1120
|
|
|
case 'binary': // varbinary column for MySQL/MariaDB |
|
1121
|
|
|
if ($this->sType == 'mysql' && $col_data['precision'] <= $this->max_varchar_length) |
|
1122
|
|
|
{ |
|
1123
|
|
|
$ado_col = 'C('.$col_data['precision'].') CONSTRAINT "CHARACTER SET binary"'; |
|
1124
|
|
|
break; |
|
1125
|
|
|
} |
|
1126
|
|
|
// fall through to blob |
|
1127
|
|
|
case 'blob': |
|
1128
|
|
|
$ado_col = 'B'; |
|
1129
|
|
|
break; |
|
1130
|
|
|
case 'bool': |
|
1131
|
|
|
$ado_col = 'L'; |
|
1132
|
|
|
break; |
|
1133
|
|
|
case 'char': |
|
1134
|
|
|
// ADOdb does not differ between char and varchar |
|
1135
|
|
|
case 'ascii': |
|
1136
|
|
|
case 'varchar': |
|
1137
|
|
|
$ado_col = "C"; |
|
1138
|
|
|
if(0 < $col_data['precision'] && $col_data['precision'] <= $this->max_varchar_length) |
|
1139
|
|
|
{ |
|
1140
|
|
|
$ado_col .= "($col_data[precision])"; |
|
1141
|
|
|
} |
|
1142
|
|
|
if($col_data['precision'] > $this->max_varchar_length) |
|
1143
|
|
|
{ |
|
1144
|
|
|
$ado_col = 'X'; |
|
1145
|
|
|
} |
|
1146
|
|
|
if ($col_data['type'] == 'ascii' && $this->sType == 'mysql') |
|
1147
|
|
|
{ |
|
1148
|
|
|
$ado_col .= ' CONSTRAINT "CHARACTER SET ascii"'; |
|
1149
|
|
|
} |
|
1150
|
|
|
break; |
|
1151
|
|
|
case 'date': |
|
1152
|
|
|
$ado_col = 'D'; |
|
1153
|
|
|
// allow to use now() beside current_date, as Postgres backups contain it and it's easier to remember anyway |
|
1154
|
|
|
if (in_array($col_data['default'],array('current_date','now()'))) |
|
1155
|
|
|
{ |
|
1156
|
|
|
$ado_col .= ' DEFDATE'; |
|
1157
|
|
|
unset($col_data['default']); |
|
1158
|
|
|
} |
|
1159
|
|
|
break; |
|
1160
|
|
|
case 'decimal': |
|
1161
|
|
|
$ado_col = "N($col_data[precision].$col_data[scale])"; |
|
1162
|
|
|
break; |
|
1163
|
|
|
case 'double': |
|
1164
|
|
|
case 'float': |
|
1165
|
|
|
// ADOdb does not differ between float and double |
|
1166
|
|
|
$ado_col = 'F'; |
|
1167
|
|
|
break; |
|
1168
|
|
|
case 'int': |
|
1169
|
|
|
$ado_col = 'I'; |
|
1170
|
|
|
switch($col_data['precision']) |
|
1171
|
|
|
{ |
|
1172
|
|
|
case 1: |
|
1173
|
|
|
case 2: |
|
1174
|
|
|
case 4: |
|
1175
|
|
|
case 8: |
|
1176
|
|
|
$ado_col .= $col_data['precision']; |
|
1177
|
|
|
break; |
|
1178
|
|
|
} |
|
1179
|
|
|
break; |
|
1180
|
|
|
case 'longtext': |
|
1181
|
|
|
$ado_col = 'XL'; |
|
1182
|
|
|
break; |
|
1183
|
|
|
case 'text': |
|
1184
|
|
|
$ado_col = 'X'; |
|
1185
|
|
|
break; |
|
1186
|
|
|
case 'timestamp': |
|
1187
|
|
|
$ado_col = 'T'; |
|
1188
|
|
|
// allow to use now() beside current_timestamp, as Postgres backups contain it and it's easier to remember anyway |
|
1189
|
|
|
if (in_array($col_data['default'],array('current_timestamp','now()'))) |
|
1190
|
|
|
{ |
|
1191
|
|
|
$ado_col .= ' DEFTIMESTAMP'; |
|
1192
|
|
|
unset($col_data['default']); |
|
1193
|
|
|
} |
|
1194
|
|
|
break; |
|
1195
|
|
|
} |
|
1196
|
|
|
if (!$ado_col) |
|
1197
|
|
|
{ |
|
1198
|
|
|
$this->error("Ignoring unknown column-type '$col_data[type]($col_data[precision])' !!!<br>".function_backtrace()); |
|
1199
|
|
|
continue; |
|
1200
|
|
|
} |
|
1201
|
|
|
if (isset($col_data['nullable']) && !$col_data['nullable']) |
|
1202
|
|
|
{ |
|
1203
|
|
|
$ado_col .= ' NOTNULL'; |
|
1204
|
|
|
} |
|
1205
|
|
|
if (isset($col_data['default'])) |
|
1206
|
|
|
{ |
|
1207
|
|
|
$ado_col .= (in_array($col_data['type'],array('bool','int','decimal','float','double')) && $col_data['default'] != 'NULL' ? ' NOQUOTE' : ''). |
|
1208
|
|
|
' DEFAULT '.$this->m_odb->quote($col_data['default'],$col_data['type']); |
|
1209
|
|
|
} |
|
1210
|
|
|
if (in_array($col,$aTableDef['pk'])) |
|
1211
|
|
|
{ |
|
1212
|
|
|
$ado_col .= ' PRIMARY'; |
|
1213
|
|
|
} |
|
1214
|
|
|
$ado_defs[] = $col . ' ' . $ado_col; |
|
1215
|
|
|
} |
|
1216
|
|
|
//print_r($aTableDef); echo implode(",\n",$ado_defs)."\n"; |
|
1217
|
|
|
return implode(",\n",$ado_defs); |
|
1218
|
|
|
} |
|
1219
|
|
|
|
|
1220
|
|
|
/** |
|
1221
|
|
|
* Translates an eGW type into the DB's native type |
|
1222
|
|
|
* |
|
1223
|
|
|
* @param string $egw_type eGW name of type |
|
1224
|
|
|
* @param string|boolean DB's name of the type or false if the type could not be identified (should not happen) |
|
|
|
|
|
|
1225
|
|
|
*/ |
|
1226
|
|
|
function TranslateType($egw_type) |
|
1227
|
|
|
{ |
|
1228
|
|
|
$ado_col = $this->_egw2adodb_columndef(array( |
|
1229
|
|
|
'fd' => array('test' => array('type' => $egw_type)), |
|
1230
|
|
|
'pk' => array(), |
|
1231
|
|
|
)); |
|
1232
|
|
|
$matches = null; |
|
1233
|
|
|
return preg_match('/test ([A-Z0-9]+)/i',$ado_col,$matches) ? $this->dict->ActualType($matches[1]) : false; |
|
1234
|
|
|
} |
|
1235
|
|
|
|
|
1236
|
|
|
/** |
|
1237
|
|
|
* Read the table-definition direct from the database |
|
1238
|
|
|
* |
|
1239
|
|
|
* The definition might not be as accurate, depending on the DB! |
|
1240
|
|
|
* |
|
1241
|
|
|
* @param string $sTableName table-name |
|
1242
|
|
|
* @return array|boolean table-defition, like $phpgw_baseline[$sTableName] after including tables_current, or false on error |
|
1243
|
|
|
*/ |
|
1244
|
|
|
function GetTableDefinition($sTableName) |
|
1245
|
|
|
{ |
|
1246
|
|
|
// MetaType returns all varchar >= blobSize as blob, it's by default 100, which is wrong |
|
1247
|
|
|
$this->dict->blobSize = $this->max_varchar_length; |
|
1248
|
|
|
|
|
1249
|
|
|
if (!method_exists($this->dict,'MetaColumns') || |
|
1250
|
|
|
!($columns = $this->dict->MetaColumns($sTableName))) |
|
1251
|
|
|
{ |
|
1252
|
|
|
return False; |
|
1253
|
|
|
} |
|
1254
|
|
|
$definition = array( |
|
1255
|
|
|
'fd' => array(), |
|
1256
|
|
|
'pk' => array(), |
|
1257
|
|
|
'fk' => array(), |
|
1258
|
|
|
'ix' => array(), |
|
1259
|
|
|
'uc' => array(), |
|
1260
|
|
|
); |
|
1261
|
|
|
//echo "$sTableName: <pre>".print_r($columns,true)."</pre>"; |
|
1262
|
|
|
foreach($columns as $column) |
|
1263
|
|
|
{ |
|
1264
|
|
|
$name = $this->capabilities['name_case'] == 'upper' ? strtolower($column->name) : $column->name; |
|
1265
|
|
|
|
|
1266
|
|
|
$type = method_exists($this->dict,'MetaType') ? $this->dict->MetaType($column) : strtoupper($column->type); |
|
1267
|
|
|
|
|
1268
|
|
|
// fix longtext not correctly handled by ADOdb |
|
1269
|
|
|
if ($type == 'X' && $column->type == 'longtext') $type = 'XL'; |
|
1270
|
|
|
|
|
1271
|
|
|
static $ado_type2egw = array( |
|
1272
|
|
|
'C' => 'varchar', |
|
1273
|
|
|
'C2' => 'varchar', |
|
1274
|
|
|
'X' => 'text', |
|
1275
|
|
|
'X2' => 'text', |
|
1276
|
|
|
'XL' => 'longtext', |
|
1277
|
|
|
'B' => 'blob', |
|
1278
|
|
|
'I' => 'int', |
|
1279
|
|
|
'T' => 'timestamp', |
|
1280
|
|
|
'D' => 'date', |
|
1281
|
|
|
'F' => 'float', |
|
1282
|
|
|
'N' => 'decimal', |
|
1283
|
|
|
'R' => 'auto', |
|
1284
|
|
|
'L' => 'bool', |
|
1285
|
|
|
); |
|
1286
|
|
|
$definition['fd'][$name]['type'] = $ado_type2egw[$type]; |
|
1287
|
|
|
|
|
1288
|
|
|
switch($type) |
|
1289
|
|
|
{ |
|
1290
|
|
|
case 'D': case 'T': |
|
1291
|
|
|
// detecting the automatic timestamps again |
|
1292
|
|
|
if ($column->has_default && preg_match('/(0000-00-00|timestamp)/i',$column->default_value)) |
|
1293
|
|
|
{ |
|
1294
|
|
|
$column->default_value = $type == 'D' ? 'current_date' : 'current_timestamp'; |
|
1295
|
|
|
} |
|
1296
|
|
|
break; |
|
1297
|
|
|
case 'C': case 'C2': |
|
1298
|
|
|
// ascii columns are reported as varchar |
|
1299
|
|
|
$definition['fd'][$name]['type'] = $this->m_odb->get_column_attribute($name, $sTableName, true, 'type') === 'ascii' ? |
|
1300
|
|
|
'ascii' : 'varchar'; |
|
1301
|
|
|
$definition['fd'][$name]['precision'] = $column->max_length; |
|
1302
|
|
|
break; |
|
1303
|
|
|
case 'B': |
|
1304
|
|
|
case 'X': case 'XL': case 'X2': |
|
1305
|
|
|
// text or blob's need to be nullable for most databases |
|
1306
|
|
|
$column->not_null = false; |
|
1307
|
|
|
break; |
|
1308
|
|
|
case 'F': |
|
1309
|
|
|
$definition['fd'][$name]['precision'] = $column->max_length; |
|
1310
|
|
|
break; |
|
1311
|
|
|
case 'N': |
|
1312
|
|
|
$definition['fd'][$name]['precision'] = $column->max_length; |
|
1313
|
|
|
$definition['fd'][$name]['scale'] = $column->scale; |
|
1314
|
|
|
break; |
|
1315
|
|
|
case 'R': |
|
1316
|
|
|
$column->auto_increment = true; |
|
1317
|
|
|
// fall-through |
|
1318
|
|
|
case 'I': case 'I1': case 'I2': case 'I4': case 'I8': |
|
1319
|
|
|
switch($type) |
|
1320
|
|
|
{ |
|
1321
|
|
|
case 'I1': case 'I2': case 'I4': case 'I8': |
|
1322
|
|
|
$definition['fd'][$name]['precision'] = (int) $type[1]; |
|
1323
|
|
|
break; |
|
1324
|
|
|
default: |
|
1325
|
|
|
if ($column->max_length > 11) |
|
1326
|
|
|
{ |
|
1327
|
|
|
$definition['fd'][$name]['precision'] = 8; |
|
1328
|
|
|
} |
|
1329
|
|
|
elseif ($column->max_length > 6 || !$column->max_length) |
|
1330
|
|
|
{ |
|
1331
|
|
|
$definition['fd'][$name]['precision'] = 4; |
|
1332
|
|
|
} |
|
1333
|
|
|
elseif ($column->max_length > 2) |
|
1334
|
|
|
{ |
|
1335
|
|
|
$definition['fd'][$name]['precision'] = 2; |
|
1336
|
|
|
} |
|
1337
|
|
|
else |
|
1338
|
|
|
{ |
|
1339
|
|
|
$definition['fd'][$name]['precision'] = 1; |
|
1340
|
|
|
} |
|
1341
|
|
|
break; |
|
1342
|
|
|
} |
|
1343
|
|
|
if ($column->auto_increment) |
|
1344
|
|
|
{ |
|
1345
|
|
|
// no precision for auto! |
|
1346
|
|
|
$definition['fd'][$name] = array( |
|
1347
|
|
|
'type' => 'auto', |
|
1348
|
|
|
'nullable' => False, |
|
1349
|
|
|
); |
|
1350
|
|
|
$column->has_default = False; |
|
1351
|
|
|
$definition['pk'][] = $name; |
|
1352
|
|
|
} |
|
1353
|
|
|
else |
|
1354
|
|
|
{ |
|
1355
|
|
|
$definition['fd'][$name]['type'] = 'int'; |
|
1356
|
|
|
// detect postgres type-spec and remove it |
|
1357
|
|
|
$matches = null; |
|
1358
|
|
|
if ($this->sType == 'pgsql' && $column->has_default && preg_match('/\(([^)])\)::/',$column->default_value,$matches)) |
|
1359
|
|
|
{ |
|
1360
|
|
|
$definition['fd'][$name]['default'] = $matches[1]; |
|
1361
|
|
|
$column->has_default = False; |
|
1362
|
|
|
} |
|
1363
|
|
|
} |
|
1364
|
|
|
// fix MySQL stores bool columns as smallint |
|
1365
|
|
|
if ($this->sType == 'mysql' && $definition['fd'][$name]['precision'] == 1 && |
|
1366
|
|
|
$this->m_odb->get_column_attribute($name, $sTableName, true, 'type') === 'bool') |
|
1367
|
|
|
{ |
|
1368
|
|
|
$definition['fd'][$name]['type'] = 'bool'; |
|
1369
|
|
|
unset($definition['fd'][$name]['precision']); |
|
1370
|
|
|
$column->default_value = (bool)$column->default_value; |
|
1371
|
|
|
} |
|
1372
|
|
|
break; |
|
1373
|
|
|
} |
|
1374
|
|
|
if ($column->has_default) |
|
1375
|
|
|
{ |
|
1376
|
|
|
if (preg_match("/^'(.*)'::.*$/",$column->default_value,$matches)) // postgres |
|
1377
|
|
|
{ |
|
1378
|
|
|
$column->default_value = $matches[1]; |
|
1379
|
|
|
} |
|
1380
|
|
|
$definition['fd'][$name]['default'] = $column->default_value; |
|
1381
|
|
|
} |
|
1382
|
|
|
if ($column->not_null) |
|
1383
|
|
|
{ |
|
1384
|
|
|
$definition['fd'][$name]['nullable'] = False; |
|
1385
|
|
|
} |
|
1386
|
|
|
if ($column->primary_key && !in_array($name,$definition['pk'])) |
|
1387
|
|
|
{ |
|
1388
|
|
|
$definition['pk'][] = $name; |
|
1389
|
|
|
} |
|
1390
|
|
|
} |
|
1391
|
|
|
if ($this->debug > 2) $this->debug_message("schema_proc::GetTableDefintion: MetaColumns(%1) = %2",False,$sTableName,$columns); |
|
1392
|
|
|
|
|
1393
|
|
|
// not all DB's (odbc) return the primary keys via MetaColumns |
|
1394
|
|
|
if (!count($definition['pk']) && method_exists($this->dict,'MetaPrimaryKeys') && |
|
1395
|
|
|
is_array($primary = $this->dict->MetaPrimaryKeys($sTableName)) && count($primary)) |
|
1396
|
|
|
{ |
|
1397
|
|
|
if($this->capabilities['name_case'] == 'upper') |
|
1398
|
|
|
{ |
|
1399
|
|
|
array_walk($primary, function(&$s) |
|
1400
|
|
|
{ |
|
1401
|
|
|
$s = strtolower($s); |
|
1402
|
|
|
}); |
|
1403
|
|
|
} |
|
1404
|
|
|
$definition['pk'] = $primary; |
|
1405
|
|
|
} |
|
1406
|
|
|
if ($this->debug > 1) $this->debug_message("schema_proc::GetTableDefintion: MetaPrimaryKeys(%1) = %2",False,$sTableName,$primary); |
|
1407
|
|
|
|
|
1408
|
|
|
$this->GetIndexes($sTableName, $definition); |
|
1409
|
|
|
if ($this->debug > 1) $this->debug_message("schema_proc::GetTableDefintion(%1) = %2",False,$sTableName,$definition); |
|
1410
|
|
|
|
|
1411
|
|
|
return $definition; |
|
1412
|
|
|
} |
|
1413
|
|
|
|
|
1414
|
|
|
/** |
|
1415
|
|
|
* Query indexes (not primary index) from database |
|
1416
|
|
|
* |
|
1417
|
|
|
* @param string $sTableName |
|
1418
|
|
|
* @param array& $definition=array() |
|
1419
|
|
|
* @return array of arrays with keys 'ix' and 'uc' |
|
1420
|
|
|
*/ |
|
1421
|
|
|
public function GetIndexes($sTableName, array &$definition=array()) |
|
1422
|
|
|
{ |
|
1423
|
|
|
if (method_exists($this->dict,'MetaIndexes') && |
|
1424
|
|
|
is_array($indexes = $this->dict->MetaIndexes($sTableName)) && count($indexes)) |
|
1425
|
|
|
{ |
|
1426
|
|
|
foreach($indexes as $index) |
|
1427
|
|
|
{ |
|
1428
|
|
|
// append (optional) length of index in brackets to column |
|
1429
|
|
|
foreach((array)$index['length'] as $col => $length) |
|
1430
|
|
|
{ |
|
1431
|
|
|
if (($key = array_search($col, $index['columns']))) $index['columns'][$key] .= '('.$length.')'; |
|
1432
|
|
|
} |
|
1433
|
|
|
if($this->capabilities['name_case'] == 'upper') |
|
1434
|
|
|
{ |
|
1435
|
|
|
array_walk($index['columns'], function(&$s) |
|
1436
|
|
|
{ |
|
1437
|
|
|
$s = strtolower($s); |
|
1438
|
|
|
}); |
|
1439
|
|
|
} |
|
1440
|
|
|
if (!empty($definition['pk']) && (implode(':',$definition['pk']) == implode(':',$index['columns']) || |
|
1441
|
|
|
$index['unique'] && count(array_intersect($definition['pk'],$index['columns'])) == count($definition['pk']))) |
|
1442
|
|
|
{ |
|
1443
|
|
|
continue; // is already the primary key => ignore it |
|
1444
|
|
|
} |
|
1445
|
|
|
$kind = $index['unique'] ? 'uc' : 'ix'; |
|
1446
|
|
|
|
|
1447
|
|
|
$definition[$kind][] = count($index['columns']) > 1 ? $index['columns'] : $index['columns'][0]; |
|
1448
|
|
|
} |
|
1449
|
|
|
if ($this->debug > 2) $this->debug_message("schema_proc::GetTableDefintion: MetaIndexes(%1) = %2",False,$sTableName,$indexes); |
|
1450
|
|
|
} |
|
1451
|
|
|
return $definition; |
|
1452
|
|
|
} |
|
1453
|
|
|
|
|
1454
|
|
|
/** |
|
1455
|
|
|
* Check if all indexes exist and create them if not |
|
1456
|
|
|
* |
|
1457
|
|
|
* Used eg. after restoring a backup to make sure all indexes are in place. |
|
1458
|
|
|
* |
|
1459
|
|
|
* Does not check index-type or length! |
|
1460
|
|
|
*/ |
|
1461
|
|
|
function CheckCreateIndexes() |
|
1462
|
|
|
{ |
|
1463
|
|
|
foreach($this->adodb->MetaTables('TABLES') as $table) |
|
1464
|
|
|
{ |
|
1465
|
|
|
if (!($table_def = $this->m_odb->get_table_definitions(true, $table))) continue; |
|
1466
|
|
|
|
|
1467
|
|
|
$definition = array(); |
|
1468
|
|
|
$this->GetIndexes($table, $definition); |
|
1469
|
|
|
$current = $this->m_odb->metadata($table, true); |
|
1470
|
|
|
|
|
1471
|
|
|
// iterate though indexes we should have according to tables_current |
|
1472
|
|
|
foreach(array('uc', 'ix') as $type) |
|
1473
|
|
|
{ |
|
1474
|
|
|
foreach($table_def[$type] as $columns) |
|
1475
|
|
|
{ |
|
1476
|
|
|
// sometimes primary key is listed as (unique) index too --> ignore it |
|
1477
|
|
|
if ($this->_in_index($columns, array($table_def['pk']), true)) continue; |
|
1478
|
|
|
|
|
1479
|
|
|
// current table does NOT contain all columns, eg. not yet updated --> ignore index |
|
1480
|
|
|
if (array_diff((array)$columns, array_keys($current['meta']))) |
|
1481
|
|
|
{ |
|
1482
|
|
|
//error_log(__METHOD__."() Can't create index over ", implode(',')." on table $table, as not all columns exist (yet)!"); |
|
1483
|
|
|
continue; |
|
1484
|
|
|
} |
|
1485
|
|
|
// check if they exist in real table and create them if not |
|
1486
|
|
|
if (!$this->_in_index($columns, $definition[$type]) && |
|
1487
|
|
|
// sometimes index is listed as unique index too --> ignore that |
|
1488
|
|
|
($type == 'uc' || !$this->_in_index($columns, $definition['uc'], true))) |
|
1489
|
|
|
{ |
|
1490
|
|
|
// check if index may exists, but without limit in column-name |
|
1491
|
|
|
if ($this->_in_index($columns, $definition[$type], true)) |
|
1492
|
|
|
{ |
|
1493
|
|
|
// for PostgreSQL we dont use length-limited indexes --> nothing to do |
|
1494
|
|
|
if ($this->m_odb->Type == 'pgsql') continue; |
|
1495
|
|
|
// for MySQL we drop current index and create it with correct length |
|
1496
|
|
|
$this->DropIndex($table, $columns); |
|
1497
|
|
|
} |
|
1498
|
|
|
$this->CreateIndex($table, $columns, $type == 'uc'); |
|
1499
|
|
|
} |
|
1500
|
|
|
} |
|
1501
|
|
|
} |
|
1502
|
|
|
} |
|
1503
|
|
|
} |
|
1504
|
|
|
|
|
1505
|
|
|
/** |
|
1506
|
|
|
* Get actual columnnames as a comma-separated string in $sColumns and set indices as class-vars pk,fk,ix,uc |
|
1507
|
|
|
* |
|
1508
|
|
|
* old translator function, use GetTableDefition() instead |
|
1509
|
|
|
* @depricated |
|
1510
|
|
|
*/ |
|
1511
|
|
|
function _GetColumns($oProc,$sTableName,&$sColumns) |
|
1512
|
|
|
{ |
|
1513
|
|
|
unset($oProc); // unused, but required by function signature |
|
1514
|
|
|
$this->sCol = $this->pk = $this->fk = $this->ix = $this->uc = array(); |
|
|
|
|
|
|
1515
|
|
|
|
|
1516
|
|
|
$tabledef = $this->GetTableDefinition($sTableName); |
|
1517
|
|
|
|
|
1518
|
|
|
$sColumns = implode(',',array_keys($tabledef['fd'])); |
|
1519
|
|
|
|
|
1520
|
|
|
foreach($tabledef['fd'] as $column => $data) |
|
1521
|
|
|
{ |
|
1522
|
|
|
$col_def = "'type' => '$data[type]'"; |
|
1523
|
|
|
unset($data['type']); |
|
1524
|
|
|
foreach($data as $key => $val) |
|
1525
|
|
|
{ |
|
1526
|
|
|
$col_def .= ", '$key' => ".(is_bool($val) ? ($val ? 'true' : 'false') : |
|
1527
|
|
|
(is_int($val) ? $val : "'$val'")); |
|
1528
|
|
|
} |
|
1529
|
|
|
$this->sCol[] = "\t\t\t\t'$column' => array($col_def),\n"; |
|
1530
|
|
|
} |
|
1531
|
|
|
foreach(array('pk','fk','ix','uc') as $kind) |
|
1532
|
|
|
{ |
|
1533
|
|
|
$this->$kind = $tabledef[$kind]; |
|
1534
|
|
|
} |
|
1535
|
|
|
} |
|
1536
|
|
|
} |
|
1537
|
|
|
|