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