Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]
1 <?php 2 /** 3 * ADOdb Data Dictionary base class. 4 * 5 * This file is part of ADOdb, a Database Abstraction Layer library for PHP. 6 * 7 * @package ADOdb 8 * @link https://adodb.org Project's web site and documentation 9 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker 10 * 11 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause 12 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option, 13 * any later version. This means you can use it in proprietary products. 14 * See the LICENSE.md file distributed with this source code for details. 15 * @license BSD-3-Clause 16 * @license LGPL-2.1-or-later 17 * 18 * @copyright 2000-2013 John Lim 19 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community 20 */ 21 22 // security - hide paths 23 if (!defined('ADODB_DIR')) die(); 24 25 /** 26 * Test script for parser 27 */ 28 function lens_ParseTest() 29 { 30 $str = "`zcol ACOL` NUMBER(32,2) DEFAULT 'The \"cow\" (and Jim''s dog) jumps over the moon' PRIMARY, INTI INT AUTO DEFAULT 0, zcol2\"afs ds"; 31 print "<p>$str</p>"; 32 $a= lens_ParseArgs($str); 33 print "<pre>"; 34 print_r($a); 35 print "</pre>"; 36 } 37 38 39 if (!function_exists('ctype_alnum')) { 40 function ctype_alnum($text) { 41 return preg_match('/^[a-z0-9]*$/i', $text); 42 } 43 } 44 45 //Lens_ParseTest(); 46 47 /** 48 Parse arguments, treat "text" (text) and 'text' as quotation marks. 49 To escape, use "" or '' or )) 50 51 Will read in "abc def" sans quotes, as: abc def 52 Same with 'abc def'. 53 However if `abc def`, then will read in as `abc def` 54 55 @param endstmtchar Character that indicates end of statement 56 @param tokenchars Include the following characters in tokens apart from A-Z and 0-9 57 @returns 2 dimensional array containing parsed tokens. 58 */ 59 function lens_ParseArgs($args,$endstmtchar=',',$tokenchars='_.-') 60 { 61 $pos = 0; 62 $intoken = false; 63 $stmtno = 0; 64 $endquote = false; 65 $tokens = array(); 66 $tokens[$stmtno] = array(); 67 $max = strlen($args); 68 $quoted = false; 69 $tokarr = array(); 70 71 while ($pos < $max) { 72 $ch = substr($args,$pos,1); 73 switch($ch) { 74 case ' ': 75 case "\t": 76 case "\n": 77 case "\r": 78 if (!$quoted) { 79 if ($intoken) { 80 $intoken = false; 81 $tokens[$stmtno][] = implode('',$tokarr); 82 } 83 break; 84 } 85 86 $tokarr[] = $ch; 87 break; 88 89 case '`': 90 if ($intoken) $tokarr[] = $ch; 91 case '(': 92 case ')': 93 case '"': 94 case "'": 95 96 if ($intoken) { 97 if (empty($endquote)) { 98 $tokens[$stmtno][] = implode('',$tokarr); 99 if ($ch == '(') $endquote = ')'; 100 else $endquote = $ch; 101 $quoted = true; 102 $intoken = true; 103 $tokarr = array(); 104 } else if ($endquote == $ch) { 105 $ch2 = substr($args,$pos+1,1); 106 if ($ch2 == $endquote) { 107 $pos += 1; 108 $tokarr[] = $ch2; 109 } else { 110 $quoted = false; 111 $intoken = false; 112 $tokens[$stmtno][] = implode('',$tokarr); 113 $endquote = ''; 114 } 115 } else 116 $tokarr[] = $ch; 117 118 }else { 119 120 if ($ch == '(') $endquote = ')'; 121 else $endquote = $ch; 122 $quoted = true; 123 $intoken = true; 124 $tokarr = array(); 125 if ($ch == '`') $tokarr[] = '`'; 126 } 127 break; 128 129 default: 130 131 if (!$intoken) { 132 if ($ch == $endstmtchar) { 133 $stmtno += 1; 134 $tokens[$stmtno] = array(); 135 break; 136 } 137 138 $intoken = true; 139 $quoted = false; 140 $endquote = false; 141 $tokarr = array(); 142 143 } 144 145 if ($quoted) $tokarr[] = $ch; 146 else if (ctype_alnum($ch) || strpos($tokenchars,$ch) !== false) $tokarr[] = $ch; 147 else { 148 if ($ch == $endstmtchar) { 149 $tokens[$stmtno][] = implode('',$tokarr); 150 $stmtno += 1; 151 $tokens[$stmtno] = array(); 152 $intoken = false; 153 $tokarr = array(); 154 break; 155 } 156 $tokens[$stmtno][] = implode('',$tokarr); 157 $tokens[$stmtno][] = $ch; 158 $intoken = false; 159 } 160 } 161 $pos += 1; 162 } 163 if ($intoken) $tokens[$stmtno][] = implode('',$tokarr); 164 165 return $tokens; 166 } 167 168 169 class ADODB_DataDict { 170 /** @var ADOConnection */ 171 var $connection; 172 var $debug = false; 173 var $dropTable = 'DROP TABLE %s'; 174 var $renameTable = 'RENAME TABLE %s TO %s'; 175 var $dropIndex = 'DROP INDEX %s'; 176 var $addCol = ' ADD'; 177 var $alterCol = ' ALTER COLUMN'; 178 var $dropCol = ' DROP COLUMN'; 179 var $renameColumn = 'ALTER TABLE %s RENAME COLUMN %s TO %s'; // table, old-column, new-column, column-definitions (not used by default) 180 var $nameRegex = '\w'; 181 var $nameRegexBrackets = 'a-zA-Z0-9_\(\)'; 182 var $schema = false; 183 var $serverInfo = array(); 184 var $autoIncrement = false; 185 var $dataProvider; 186 var $invalidResizeTypes4 = array('CLOB','BLOB','TEXT','DATE','TIME'); // for changeTableSQL 187 var $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob 188 /// in other words, we use a text area for editing. 189 /** @var string Uppercase driver name */ 190 var $upperName; 191 192 /* 193 * Indicates whether a BLOB/CLOB field will allow a NOT NULL setting 194 * The type is whatever is matched to an X or X2 or B type. We must 195 * explicitly set the value in the driver to switch the behaviour on 196 */ 197 public $blobAllowsNotNull; 198 /* 199 * Indicates whether a BLOB/CLOB field will allow a DEFAULT set 200 * The type is whatever is matched to an X or X2 or B type. We must 201 * explicitly set the value in the driver to switch the behaviour on 202 */ 203 public $blobAllowsDefaultValue; 204 205 /** 206 * @var string String to use to quote identifiers and names 207 */ 208 public $quote; 209 210 function getCommentSQL($table,$col) 211 { 212 return false; 213 } 214 215 function setCommentSQL($table,$col,$cmt) 216 { 217 return false; 218 } 219 220 function metaTables() 221 { 222 if (!$this->connection->isConnected()) return array(); 223 return $this->connection->metaTables(); 224 } 225 226 function metaColumns($tab, $upper=true, $schema=false) 227 { 228 if (!$this->connection->isConnected()) return array(); 229 return $this->connection->metaColumns($this->tableName($tab), $upper, $schema); 230 } 231 232 function metaPrimaryKeys($tab,$owner=false,$intkey=false) 233 { 234 if (!$this->connection->isConnected()) return array(); 235 return $this->connection->metaPrimaryKeys($this->tableName($tab), $owner, $intkey); 236 } 237 238 function metaIndexes($table, $primary = false, $owner = false) 239 { 240 if (!$this->connection->isConnected()) return array(); 241 return $this->connection->metaIndexes($this->tableName($table), $primary, $owner); 242 } 243 244 function metaType($t,$len=-1,$fieldobj=false) 245 { 246 static $typeMap = array( 247 'VARCHAR' => 'C', 248 'VARCHAR2' => 'C', 249 'CHAR' => 'C', 250 'C' => 'C', 251 'STRING' => 'C', 252 'NCHAR' => 'C', 253 'NVARCHAR' => 'C', 254 'VARYING' => 'C', 255 'BPCHAR' => 'C', 256 'CHARACTER' => 'C', 257 'INTERVAL' => 'C', # Postgres 258 'MACADDR' => 'C', # postgres 259 'VAR_STRING' => 'C', # mysql 260 ## 261 'LONGCHAR' => 'X', 262 'TEXT' => 'X', 263 'NTEXT' => 'X', 264 'M' => 'X', 265 'X' => 'X', 266 'CLOB' => 'X', 267 'NCLOB' => 'X', 268 'LVARCHAR' => 'X', 269 ## 270 'BLOB' => 'B', 271 'IMAGE' => 'B', 272 'BINARY' => 'B', 273 'VARBINARY' => 'B', 274 'LONGBINARY' => 'B', 275 'B' => 'B', 276 ## 277 'YEAR' => 'D', // mysql 278 'DATE' => 'D', 279 'D' => 'D', 280 ## 281 'UNIQUEIDENTIFIER' => 'C', # MS SQL Server 282 ## 283 'TIME' => 'T', 284 'TIMESTAMP' => 'T', 285 'DATETIME' => 'T', 286 'TIMESTAMPTZ' => 'T', 287 'SMALLDATETIME' => 'T', 288 'T' => 'T', 289 'TIMESTAMP WITHOUT TIME ZONE' => 'T', // postgresql 290 ## 291 'BOOL' => 'L', 292 'BOOLEAN' => 'L', 293 'BIT' => 'L', 294 'L' => 'L', 295 ## 296 'COUNTER' => 'R', 297 'R' => 'R', 298 'SERIAL' => 'R', // ifx 299 'INT IDENTITY' => 'R', 300 ## 301 'INT' => 'I', 302 'INT2' => 'I', 303 'INT4' => 'I', 304 'INT8' => 'I', 305 'INTEGER' => 'I', 306 'INTEGER UNSIGNED' => 'I', 307 'SHORT' => 'I', 308 'TINYINT' => 'I', 309 'SMALLINT' => 'I', 310 'I' => 'I', 311 ## 312 'LONG' => 'N', // interbase is numeric, oci8 is blob 313 'BIGINT' => 'N', // this is bigger than PHP 32-bit integers 314 'DECIMAL' => 'N', 315 'DEC' => 'N', 316 'REAL' => 'N', 317 'DOUBLE' => 'N', 318 'DOUBLE PRECISION' => 'N', 319 'SMALLFLOAT' => 'N', 320 'FLOAT' => 'N', 321 'NUMBER' => 'N', 322 'NUM' => 'N', 323 'NUMERIC' => 'N', 324 'MONEY' => 'N', 325 326 ## informix 9.2 327 'SQLINT' => 'I', 328 'SQLSERIAL' => 'I', 329 'SQLSMINT' => 'I', 330 'SQLSMFLOAT' => 'N', 331 'SQLFLOAT' => 'N', 332 'SQLMONEY' => 'N', 333 'SQLDECIMAL' => 'N', 334 'SQLDATE' => 'D', 335 'SQLVCHAR' => 'C', 336 'SQLCHAR' => 'C', 337 'SQLDTIME' => 'T', 338 'SQLINTERVAL' => 'N', 339 'SQLBYTES' => 'B', 340 'SQLTEXT' => 'X', 341 ## informix 10 342 "SQLINT8" => 'I8', 343 "SQLSERIAL8" => 'I8', 344 "SQLNCHAR" => 'C', 345 "SQLNVCHAR" => 'C', 346 "SQLLVARCHAR" => 'X', 347 "SQLBOOL" => 'L' 348 ); 349 350 if (!$this->connection->isConnected()) { 351 $t = strtoupper($t); 352 if (isset($typeMap[$t])) return $typeMap[$t]; 353 return ADODB_DEFAULT_METATYPE; 354 } 355 return $this->connection->metaType($t,$len,$fieldobj); 356 } 357 358 function nameQuote($name = NULL,$allowBrackets=false) 359 { 360 if (!is_string($name)) { 361 return false; 362 } 363 364 $name = trim($name); 365 366 if ( !is_object($this->connection) ) { 367 return $name; 368 } 369 370 $quote = $this->connection->nameQuote; 371 372 // if name is of the form `name`, quote it 373 if ( preg_match('/^`(.+)`$/', $name, $matches) ) { 374 return $quote . $matches[1] . $quote; 375 } 376 377 // if name contains special characters, quote it 378 $regex = ($allowBrackets) ? $this->nameRegexBrackets : $this->nameRegex; 379 380 if ( !preg_match('/^[' . $regex . ']+$/', $name) ) { 381 return $quote . $name . $quote; 382 } 383 384 return $name; 385 } 386 387 function tableName($name) 388 { 389 if ( $this->schema ) { 390 return $this->nameQuote($this->schema) .'.'. $this->nameQuote($name); 391 } 392 return $this->nameQuote($name); 393 } 394 395 // Executes the sql array returned by getTableSQL and getIndexSQL 396 function executeSQLArray($sql, $continueOnError = true) 397 { 398 $rez = 2; 399 $conn = $this->connection; 400 $saved = $conn->debug; 401 foreach($sql as $line) { 402 403 if ($this->debug) $conn->debug = true; 404 $ok = $conn->execute($line); 405 $conn->debug = $saved; 406 if (!$ok) { 407 if ($this->debug) ADOConnection::outp($conn->errorMsg()); 408 if (!$continueOnError) return 0; 409 $rez = 1; 410 } 411 } 412 return $rez; 413 } 414 415 /** 416 Returns the actual type given a character code. 417 418 C: varchar 419 X: CLOB (character large object) or largest varchar size if CLOB is not supported 420 C2: Multibyte varchar 421 X2: Multibyte CLOB 422 423 B: BLOB (binary large object) 424 425 D: Date 426 T: Date-time 427 L: Integer field suitable for storing booleans (0 or 1) 428 I: Integer 429 F: Floating point number 430 N: Numeric or decimal number 431 */ 432 433 function actualType($meta) 434 { 435 $meta = strtoupper($meta); 436 437 /* 438 * Add support for custom meta types. We do this 439 * first, that allows us to override existing types 440 */ 441 if (isset($this->connection->customMetaTypes[$meta])) 442 return $this->connection->customMetaTypes[$meta]['actual']; 443 444 return $meta; 445 } 446 447 function createDatabase($dbname,$options=false) 448 { 449 $options = $this->_options($options); 450 $sql = array(); 451 452 $s = 'CREATE DATABASE ' . $this->nameQuote($dbname); 453 if (isset($options[$this->upperName])) 454 $s .= ' '.$options[$this->upperName]; 455 456 $sql[] = $s; 457 return $sql; 458 } 459 460 /* 461 Generates the SQL to create index. Returns an array of sql strings. 462 */ 463 function createIndexSQL($idxname, $tabname, $flds, $idxoptions = false) 464 { 465 if (!is_array($flds)) { 466 $flds = explode(',',$flds); 467 } 468 469 foreach($flds as $key => $fld) { 470 # some indexes can use partial fields, eg. index first 32 chars of "name" with NAME(32) 471 $flds[$key] = $this->nameQuote($fld,$allowBrackets=true); 472 } 473 474 return $this->_indexSQL($this->nameQuote($idxname), $this->tableName($tabname), $flds, $this->_options($idxoptions)); 475 } 476 477 function dropIndexSQL ($idxname, $tabname = NULL) 478 { 479 return array(sprintf($this->dropIndex, $this->nameQuote($idxname), $this->tableName($tabname))); 480 } 481 482 function setSchema($schema) 483 { 484 $this->schema = $schema; 485 } 486 487 function addColumnSQL($tabname, $flds) 488 { 489 $tabname = $this->tableName($tabname); 490 $sql = array(); 491 list($lines,$pkey,$idxs) = $this->_genFields($flds); 492 // genfields can return FALSE at times 493 if ($lines == null) $lines = array(); 494 $alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' '; 495 foreach($lines as $v) { 496 $sql[] = $alter . $v; 497 } 498 if (is_array($idxs)) { 499 foreach($idxs as $idx => $idxdef) { 500 $sql_idxs = $this->createIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']); 501 $sql = array_merge($sql, $sql_idxs); 502 } 503 } 504 return $sql; 505 } 506 507 /** 508 * Change the definition of one column 509 * 510 * As some DBMs can't do that on their own, you need to supply the complete definition of the new table, 511 * to allow recreating the table and copying the content over to the new table 512 * @param string $tabname table-name 513 * @param string $flds column-name and type for the changed column 514 * @param string $tableflds='' complete definition of the new table, eg. for postgres, default '' 515 * @param array|string $tableoptions='' options for the new table see createTableSQL, default '' 516 * @return array with SQL strings 517 */ 518 function alterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') 519 { 520 $tabname = $this->tableName($tabname); 521 $sql = array(); 522 list($lines,$pkey,$idxs) = $this->_genFields($flds); 523 // genfields can return FALSE at times 524 if ($lines == null) $lines = array(); 525 $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' '; 526 foreach($lines as $v) { 527 $sql[] = $alter . $v; 528 } 529 if (is_array($idxs)) { 530 foreach($idxs as $idx => $idxdef) { 531 $sql_idxs = $this->createIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']); 532 $sql = array_merge($sql, $sql_idxs); 533 } 534 535 } 536 return $sql; 537 } 538 539 /** 540 * Rename one column 541 * 542 * Some DBMs can only do this together with changeing the type of the column (even if that stays the same, eg. mysql) 543 * @param string $tabname table-name 544 * @param string $oldcolumn column-name to be renamed 545 * @param string $newcolumn new column-name 546 * @param string $flds='' complete column-definition-string like for addColumnSQL, only used by mysql atm., default='' 547 * @return array with SQL strings 548 */ 549 function renameColumnSQL($tabname,$oldcolumn,$newcolumn,$flds='') 550 { 551 $tabname = $this->tableName($tabname); 552 if ($flds) { 553 list($lines,$pkey,$idxs) = $this->_genFields($flds); 554 // genfields can return FALSE at times 555 if ($lines == null) $lines = array(); 556 $first = current($lines); 557 list(,$column_def) = preg_split("/[\t ]+/",$first,2); 558 } 559 return array(sprintf($this->renameColumn,$tabname,$this->nameQuote($oldcolumn),$this->nameQuote($newcolumn),$column_def)); 560 } 561 562 /** 563 * Drop one column 564 * 565 * Some DBM's can't do that on their own, you need to supply the complete definition of the new table, 566 * to allow, recreating the table and copying the content over to the new table 567 * @param string $tabname table-name 568 * @param string $flds column-name and type for the changed column 569 * @param string $tableflds='' complete definition of the new table, eg. for postgres, default '' 570 * @param array|string $tableoptions='' options for the new table see createTableSQL, default '' 571 * @return array with SQL strings 572 */ 573 function dropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') 574 { 575 $tabname = $this->tableName($tabname); 576 if (!is_array($flds)) $flds = explode(',',$flds); 577 $sql = array(); 578 $alter = 'ALTER TABLE ' . $tabname . $this->dropCol . ' '; 579 foreach($flds as $v) { 580 $sql[] = $alter . $this->nameQuote($v); 581 } 582 return $sql; 583 } 584 585 function dropTableSQL($tabname) 586 { 587 return array (sprintf($this->dropTable, $this->tableName($tabname))); 588 } 589 590 function renameTableSQL($tabname,$newname) 591 { 592 return array (sprintf($this->renameTable, $this->tableName($tabname),$this->tableName($newname))); 593 } 594 595 /** 596 Generate the SQL to create table. Returns an array of sql strings. 597 */ 598 function createTableSQL($tabname, $flds, $tableoptions=array()) 599 { 600 list($lines,$pkey,$idxs) = $this->_genFields($flds, true); 601 // genfields can return FALSE at times 602 if ($lines == null) $lines = array(); 603 604 $taboptions = $this->_options($tableoptions); 605 $tabname = $this->tableName($tabname); 606 $sql = $this->_tableSQL($tabname,$lines,$pkey,$taboptions); 607 608 // ggiunta - 2006/10/12 - KLUDGE: 609 // if we are on autoincrement, and table options includes REPLACE, the 610 // autoincrement sequence has already been dropped on table creation sql, so 611 // we avoid passing REPLACE to trigger creation code. This prevents 612 // creating sql that double-drops the sequence 613 if ($this->autoIncrement && isset($taboptions['REPLACE'])) 614 unset($taboptions['REPLACE']); 615 $tsql = $this->_triggers($tabname,$taboptions); 616 foreach($tsql as $s) $sql[] = $s; 617 618 if (is_array($idxs)) { 619 foreach($idxs as $idx => $idxdef) { 620 $sql_idxs = $this->createIndexSql($idx, $tabname, $idxdef['cols'], $idxdef['opts']); 621 $sql = array_merge($sql, $sql_idxs); 622 } 623 } 624 625 return $sql; 626 } 627 628 629 630 function _genFields($flds,$widespacing=false) 631 { 632 if (is_string($flds)) { 633 $padding = ' '; 634 $txt = $flds.$padding; 635 $flds = array(); 636 $flds0 = lens_ParseArgs($txt,','); 637 $hasparam = false; 638 foreach($flds0 as $f0) { 639 $f1 = array(); 640 foreach($f0 as $token) { 641 switch (strtoupper($token)) { 642 case 'INDEX': 643 $f1['INDEX'] = ''; 644 // fall through intentionally 645 case 'CONSTRAINT': 646 case 'DEFAULT': 647 $hasparam = $token; 648 break; 649 default: 650 if ($hasparam) $f1[$hasparam] = $token; 651 else $f1[] = $token; 652 $hasparam = false; 653 break; 654 } 655 } 656 // 'index' token without a name means single column index: name it after column 657 if (array_key_exists('INDEX', $f1) && $f1['INDEX'] == '') { 658 $f1['INDEX'] = isset($f0['NAME']) ? $f0['NAME'] : $f0[0]; 659 // check if column name used to create an index name was quoted 660 if (($f1['INDEX'][0] == '"' || $f1['INDEX'][0] == "'" || $f1['INDEX'][0] == "`") && 661 ($f1['INDEX'][0] == substr($f1['INDEX'], -1))) { 662 $f1['INDEX'] = $f1['INDEX'][0].'idx_'.substr($f1['INDEX'], 1, -1).$f1['INDEX'][0]; 663 } 664 else 665 $f1['INDEX'] = 'idx_'.$f1['INDEX']; 666 } 667 // reset it, so we don't get next field 1st token as INDEX... 668 $hasparam = false; 669 670 $flds[] = $f1; 671 672 } 673 } 674 $this->autoIncrement = false; 675 $lines = array(); 676 $pkey = array(); 677 $idxs = array(); 678 foreach($flds as $fld) { 679 if (is_array($fld)) 680 $fld = array_change_key_case($fld,CASE_UPPER); 681 $fname = false; 682 $fdefault = false; 683 $fautoinc = false; 684 $ftype = false; 685 $fsize = false; 686 $fprec = false; 687 $fprimary = false; 688 $fnoquote = false; 689 $fdefts = false; 690 $fdefdate = false; 691 $fconstraint = false; 692 $fnotnull = false; 693 $funsigned = false; 694 $findex = ''; 695 $funiqueindex = false; 696 $fOptions = array(); 697 698 //----------------- 699 // Parse attributes 700 foreach($fld as $attr => $v) { 701 if ($attr == 2 && is_numeric($v)) 702 $attr = 'SIZE'; 703 elseif ($attr == 2 && strtoupper($ftype) == 'ENUM') 704 $attr = 'ENUM'; 705 else if (is_numeric($attr) && $attr > 1 && !is_numeric($v)) 706 $attr = strtoupper($v); 707 708 switch($attr) { 709 case '0': 710 case 'NAME': $fname = $v; break; 711 case '1': 712 case 'TYPE': 713 714 $ty = $v; 715 $ftype = $this->actualType(strtoupper($v)); 716 break; 717 718 case 'SIZE': 719 $dotat = strpos($v,'.'); 720 if ($dotat === false) 721 $dotat = strpos($v,','); 722 if ($dotat === false) 723 $fsize = $v; 724 else { 725 726 $fsize = substr($v,0,$dotat); 727 $fprec = substr($v,$dotat+1); 728 729 } 730 break; 731 case 'UNSIGNED': $funsigned = true; break; 732 case 'AUTOINCREMENT': 733 case 'AUTO': $fautoinc = true; $fnotnull = true; break; 734 case 'KEY': 735 // a primary key col can be non unique in itself (if key spans many cols...) 736 case 'PRIMARY': $fprimary = $v; $fnotnull = true; /*$funiqueindex = true;*/ break; 737 case 'DEF': 738 case 'DEFAULT': $fdefault = $v; break; 739 case 'NOTNULL': $fnotnull = $v; break; 740 case 'NOQUOTE': $fnoquote = $v; break; 741 case 'DEFDATE': $fdefdate = $v; break; 742 case 'DEFTIMESTAMP': $fdefts = $v; break; 743 case 'CONSTRAINT': $fconstraint = $v; break; 744 // let INDEX keyword create a 'very standard' index on column 745 case 'INDEX': $findex = $v; break; 746 case 'UNIQUE': $funiqueindex = true; break; 747 case 'ENUM': 748 $fOptions['ENUM'] = $v; break; 749 } //switch 750 } // foreach $fld 751 752 //-------------------- 753 // VALIDATE FIELD INFO 754 if (!strlen($fname)) { 755 if ($this->debug) ADOConnection::outp("Undefined NAME"); 756 return false; 757 } 758 759 $fid = strtoupper(preg_replace('/^`(.+)`$/', '$1', $fname)); 760 $fname = $this->nameQuote($fname); 761 762 if (!strlen($ftype)) { 763 if ($this->debug) ADOConnection::outp("Undefined TYPE for field '$fname'"); 764 return false; 765 } else { 766 $ftype = strtoupper($ftype); 767 } 768 769 $ftype = $this->_getSize($ftype, $ty, $fsize, $fprec, $fOptions); 770 771 if (($ty == 'X' || $ty == 'X2' || $ty == 'XL' || $ty == 'B') && !$this->blobAllowsNotNull) 772 /* 773 * some blob types do not accept nulls, so we override the 774 * previously defined value 775 */ 776 $fnotnull = false; 777 778 if ($fprimary) 779 $pkey[] = $fname; 780 781 if (($ty == 'X' || $ty == 'X2' || $ty == 'XL' || $ty == 'B') && !$this->blobAllowsDefaultValue) 782 /* 783 * some databases do not allow blobs to have defaults, so we 784 * override the previously defined value 785 */ 786 $fdefault = false; 787 788 // build list of indexes 789 if ($findex != '') { 790 if (array_key_exists($findex, $idxs)) { 791 $idxs[$findex]['cols'][] = ($fname); 792 if (in_array('UNIQUE', $idxs[$findex]['opts']) != $funiqueindex) { 793 if ($this->debug) ADOConnection::outp("Index $findex defined once UNIQUE and once not"); 794 } 795 if ($funiqueindex && !in_array('UNIQUE', $idxs[$findex]['opts'])) 796 $idxs[$findex]['opts'][] = 'UNIQUE'; 797 } 798 else 799 { 800 $idxs[$findex] = array(); 801 $idxs[$findex]['cols'] = array($fname); 802 if ($funiqueindex) 803 $idxs[$findex]['opts'] = array('UNIQUE'); 804 else 805 $idxs[$findex]['opts'] = array(); 806 } 807 } 808 809 //-------------------- 810 // CONSTRUCT FIELD SQL 811 if ($fdefts) { 812 if (substr($this->connection->databaseType,0,5) == 'mysql') { 813 $ftype = 'TIMESTAMP'; 814 } else { 815 $fdefault = $this->connection->sysTimeStamp; 816 } 817 } else if ($fdefdate) { 818 if (substr($this->connection->databaseType,0,5) == 'mysql') { 819 $ftype = 'TIMESTAMP'; 820 } else { 821 $fdefault = $this->connection->sysDate; 822 } 823 } else if ($fdefault !== false && !$fnoquote) { 824 if ($ty == 'C' or $ty == 'X' or 825 ( substr($fdefault,0,1) != "'" && !is_numeric($fdefault))) { 826 827 if (($ty == 'D' || $ty == 'T') && strtolower($fdefault) != 'null') { 828 // convert default date into database-aware code 829 if ($ty == 'T') 830 { 831 $fdefault = $this->connection->dbTimeStamp($fdefault); 832 } 833 else 834 { 835 $fdefault = $this->connection->dbDate($fdefault); 836 } 837 } 838 else 839 if (strlen($fdefault) != 1 && substr($fdefault,0,1) == ' ' && substr($fdefault,strlen($fdefault)-1) == ' ') 840 $fdefault = trim($fdefault); 841 else if (strtolower($fdefault) != 'null') 842 $fdefault = $this->connection->qstr($fdefault); 843 } 844 } 845 $suffix = $this->_createSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned); 846 847 // add index creation 848 if ($widespacing) $fname = str_pad($fname,24); 849 850 // check for field names appearing twice 851 if (array_key_exists($fid, $lines)) { 852 ADOConnection::outp("Field '$fname' defined twice"); 853 } 854 855 $lines[$fid] = $fname.' '.$ftype.$suffix; 856 857 if ($fautoinc) $this->autoIncrement = true; 858 } // foreach $flds 859 860 return array($lines,$pkey,$idxs); 861 } 862 863 /** 864 GENERATE THE SIZE PART OF THE DATATYPE 865 $ftype is the actual type 866 $ty is the type defined originally in the DDL 867 */ 868 function _getSize($ftype, $ty, $fsize, $fprec, $options=false) 869 { 870 if (strlen($fsize) && $ty != 'X' && $ty != 'B' && strpos($ftype,'(') === false) { 871 $ftype .= "(".$fsize; 872 if (strlen($fprec)) $ftype .= ",".$fprec; 873 $ftype .= ')'; 874 } 875 876 /* 877 * Handle additional options 878 */ 879 if (is_array($options)) 880 { 881 foreach($options as $type=>$value) 882 { 883 switch ($type) 884 { 885 case 'ENUM': 886 $ftype .= '(' . $value . ')'; 887 break; 888 889 default: 890 } 891 } 892 } 893 894 return $ftype; 895 } 896 897 898 // return string must begin with space 899 function _createSuffix($fname,&$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) 900 { 901 $suffix = ''; 902 if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; 903 if ($fnotnull) $suffix .= ' NOT NULL'; 904 if ($fconstraint) $suffix .= ' '.$fconstraint; 905 return $suffix; 906 } 907 908 function _indexSQL($idxname, $tabname, $flds, $idxoptions) 909 { 910 $sql = array(); 911 912 if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { 913 $sql[] = sprintf ($this->dropIndex, $idxname); 914 if ( isset($idxoptions['DROP']) ) 915 return $sql; 916 } 917 918 if ( empty ($flds) ) { 919 return $sql; 920 } 921 922 $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : ''; 923 924 $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' '; 925 926 if ( isset($idxoptions[$this->upperName]) ) 927 $s .= $idxoptions[$this->upperName]; 928 929 if ( is_array($flds) ) 930 $flds = implode(', ',$flds); 931 $s .= '(' . $flds . ')'; 932 $sql[] = $s; 933 934 return $sql; 935 } 936 937 function _dropAutoIncrement($tabname) 938 { 939 return false; 940 } 941 942 function _tableSQL($tabname,$lines,$pkey,$tableoptions) 943 { 944 $sql = array(); 945 946 if (isset($tableoptions['REPLACE']) || isset ($tableoptions['DROP'])) { 947 $sql[] = sprintf($this->dropTable,$tabname); 948 if ($this->autoIncrement) { 949 $sInc = $this->_dropAutoIncrement($tabname); 950 if ($sInc) $sql[] = $sInc; 951 } 952 if ( isset ($tableoptions['DROP']) ) { 953 return $sql; 954 } 955 } 956 957 $s = "CREATE TABLE $tabname (\n"; 958 $s .= implode(",\n", $lines); 959 if (sizeof($pkey)>0) { 960 $s .= ",\n PRIMARY KEY ("; 961 $s .= implode(", ",$pkey).")"; 962 } 963 if (isset($tableoptions['CONSTRAINTS'])) 964 $s .= "\n".$tableoptions['CONSTRAINTS']; 965 966 if (isset($tableoptions[$this->upperName.'_CONSTRAINTS'])) 967 $s .= "\n".$tableoptions[$this->upperName.'_CONSTRAINTS']; 968 969 $s .= "\n)"; 970 if (isset($tableoptions[$this->upperName])) $s .= $tableoptions[$this->upperName]; 971 $sql[] = $s; 972 973 return $sql; 974 } 975 976 /** 977 GENERATE TRIGGERS IF NEEDED 978 used when table has auto-incrementing field that is emulated using triggers 979 */ 980 function _triggers($tabname,$taboptions) 981 { 982 return array(); 983 } 984 985 /** 986 Sanitize options, so that array elements with no keys are promoted to keys 987 */ 988 function _options($opts) 989 { 990 if (!is_array($opts)) return array(); 991 $newopts = array(); 992 foreach($opts as $k => $v) { 993 if (is_numeric($k)) $newopts[strtoupper($v)] = $v; 994 else $newopts[strtoupper($k)] = $v; 995 } 996 return $newopts; 997 } 998 999 1000 function _getSizePrec($size) 1001 { 1002 $fsize = false; 1003 $fprec = false; 1004 $dotat = strpos($size,'.'); 1005 if ($dotat === false) $dotat = strpos($size,','); 1006 if ($dotat === false) $fsize = $size; 1007 else { 1008 $fsize = substr($size,0,$dotat); 1009 $fprec = substr($size,$dotat+1); 1010 } 1011 return array($fsize, $fprec); 1012 } 1013 1014 /** 1015 * This function changes/adds new fields to your table. 1016 * 1017 * You don't have to know if the col is new or not. It will check on its own. 1018 * 1019 * @param string $tablename 1020 * @param string $flds 1021 * @param string[] $tableoptions 1022 * @param bool $dropOldFlds 1023 * 1024 * @return string[] Array of SQL Commands 1025 */ 1026 function changeTableSQL($tablename, $flds, $tableoptions = false, $dropOldFlds=false) 1027 { 1028 global $ADODB_FETCH_MODE; 1029 1030 $save = $ADODB_FETCH_MODE; 1031 $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; 1032 if ($this->connection->fetchMode !== false) $savem = $this->connection->setFetchMode(false); 1033 1034 // check table exists 1035 $save_handler = $this->connection->raiseErrorFn; 1036 $this->connection->raiseErrorFn = ''; 1037 $cols = $this->metaColumns($tablename); 1038 $this->connection->raiseErrorFn = $save_handler; 1039 1040 if (isset($savem)) $this->connection->setFetchMode($savem); 1041 $ADODB_FETCH_MODE = $save; 1042 1043 if ( empty($cols)) { 1044 return $this->createTableSQL($tablename, $flds, $tableoptions); 1045 } 1046 1047 if (is_array($flds)) { 1048 // Cycle through the update fields, comparing 1049 // existing fields to fields to update. 1050 // if the Metatype and size is exactly the 1051 // same, ignore - by Mark Newham 1052 $holdflds = array(); 1053 foreach($flds as $k=>$v) { 1054 if ( isset($cols[$k]) && is_object($cols[$k]) ) { 1055 // If already not allowing nulls, then don't change 1056 $obj = $cols[$k]; 1057 if (isset($obj->not_null) && $obj->not_null) 1058 $v = str_replace('NOT NULL','',$v); 1059 if (isset($obj->auto_increment) && $obj->auto_increment && empty($v['AUTOINCREMENT'])) 1060 $v = str_replace('AUTOINCREMENT','',$v); 1061 1062 $c = $cols[$k]; 1063 $ml = $c->max_length; 1064 $mt = $this->metaType($c->type,$ml); 1065 1066 if (isset($c->scale)) $sc = $c->scale; 1067 else $sc = 99; // always force change if scale not known. 1068 1069 if ($sc == -1) $sc = false; 1070 list($fsize, $fprec) = $this->_getSizePrec($v['SIZE']); 1071 1072 if ($ml == -1) $ml = ''; 1073 if ($mt == 'X') $ml = $v['SIZE']; 1074 if (($mt != $v['TYPE']) || ($ml != $fsize || $sc != $fprec) || (isset($v['AUTOINCREMENT']) && $v['AUTOINCREMENT'] != $obj->auto_increment)) { 1075 $holdflds[$k] = $v; 1076 } 1077 } else { 1078 $holdflds[$k] = $v; 1079 } 1080 } 1081 $flds = $holdflds; 1082 } 1083 1084 $sql = $this->alterColumnSql($tablename, $flds); 1085 1086 if ($dropOldFlds) { 1087 foreach ($cols as $id => $v) { 1088 if (!isset($lines[$id])) { 1089 $sql[] = $this->dropColumnSQL($tablename, $flds); 1090 } 1091 } 1092 } 1093 return $sql; 1094 } 1095 } // class
title
Description
Body
title
Description
Body
title
Description
Body
title
Body