WIKINDX PHP Cross Reference Collaborative Wikis

Source: /core/sql/adodb-xmlschema.inc.php - 2194 lines - 56944 bytes - Summary - Text - Print

Description: xmlschema is a class that allows the user to quickly and easily build a database on any ADOdb-supported platform using a simple XML schema. Last Editor: $Author: sirfragalot $

   1  <?php
   2  // Copyright (c) 2004 ars Cognita Inc., all rights reserved

   3  /* ******************************************************************************

   4      Released under both BSD license and Lesser GPL library license. 

   5       Whenever there is any discrepancy between the two licenses, 

   6       the BSD license will take precedence. 

   7  *******************************************************************************/
   8  /**

   9   * xmlschema is a class that allows the user to quickly and easily

  10   * build a database on any ADOdb-supported platform using a simple

  11   * XML schema.

  12   *

  13   * Last Editor: $Author: sirfragalot $

  14   * @author Richard Tango-Lowy & Dan Cech

  15   * @version $Revision: 1.1 $

  16   *

  17   * @package axmls

  18   * @tutorial getting_started.pkg

  19   */
  20  
  21  /**

  22  * Debug on or off

  23  */
  24  if( !defined( 'XMLS_DEBUG' ) ) {
  25      define( 'XMLS_DEBUG', FALSE );
  26  }
  27  
  28  /**

  29  * Default prefix key

  30  */
  31  if( !defined( 'XMLS_PREFIX' ) ) {
  32      define( 'XMLS_PREFIX', '%%P' );
  33  }
  34  
  35  /**

  36  * Maximum length allowed for object prefix

  37  */
  38  if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
  39      define( 'XMLS_PREFIX_MAXLEN', 10 );
  40  }
  41  
  42  /**

  43  * Execute SQL inline as it is generated

  44  */
  45  if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
  46      define( 'XMLS_EXECUTE_INLINE', FALSE );
  47  }
  48  
  49  /**

  50  * Continue SQL Execution if an error occurs?

  51  */
  52  if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
  53      define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
  54  }
  55  
  56  /**

  57  * Current Schema Version

  58  */
  59  if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
  60      define( 'XMLS_SCHEMA_VERSION', '0.2' );
  61  }
  62  
  63  /**

  64  * Default Schema Version.  Used for Schemas without an explicit version set.

  65  */
  66  if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
  67      define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
  68  }
  69  
  70  /**

  71  * Default Schema Version.  Used for Schemas without an explicit version set.

  72  */
  73  if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
  74      define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
  75  }
  76  
  77  /**

  78  * Include the main ADODB library

  79  */
  80  if( !defined( '_ADODB_LAYER' ) ) {
  81      require ( 'adodb.inc.php' );
  82      require ( 'adodb-datadict.inc.php' );
  83  }
  84  
  85  /**

  86  * Abstract DB Object. This class provides basic methods for database objects, such

  87  * as tables and indexes.

  88  *

  89  * @package axmls

  90  * @access private

  91  */
  92  class dbObject {
  93      
  94      /**

  95      * var object Parent

  96      */
  97      var $parent;
  98      
  99      /**

 100      * var string current element

 101      */
 102      var $currentElement;
 103      
 104      /**

 105      * NOP

 106      */
 107  	function dbObject( &$parent, $attributes = NULL ) {
 108          $this->parent = $parent;
 109      }
 110      
 111      /**

 112      * XML Callback to process start elements

 113      *

 114      * @access private

 115      */
 116  	function _tag_open( &$parser, $tag, $attributes ) {
 117          
 118      }
 119      
 120      /**

 121      * XML Callback to process CDATA elements

 122      *

 123      * @access private

 124      */
 125  	function _tag_cdata( &$parser, $cdata ) {
 126          
 127      }
 128      
 129      /**

 130      * XML Callback to process end elements

 131      *

 132      * @access private

 133      */
 134  	function _tag_close( &$parser, $tag ) {
 135          
 136      }
 137      
 138  	function create() {
 139          return array();
 140      }
 141      
 142      /**

 143      * Destroys the object

 144      */
 145  	function destroy() {
 146          unset( $this );
 147      }
 148      
 149      /**

 150      * Checks whether the specified RDBMS is supported by the current

 151      * database object or its ranking ancestor.

 152      *

 153      * @param string $platform RDBMS platform name (from ADODB platform list).

 154      * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.

 155      */
 156  	function supportedPlatform( $platform = NULL ) {
 157          return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
 158      }
 159      
 160      /**

 161      * Returns the prefix set by the ranking ancestor of the database object.

 162      *

 163      * @param string $name Prefix string.

 164      * @return string Prefix.

 165      */
 166  	function prefix( $name = '' ) {
 167          return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
 168      }
 169      
 170      /**

 171      * Extracts a field ID from the specified field.

 172      *

 173      * @param string $field Field.

 174      * @return string Field ID.

 175      */
 176  	function FieldID( $field ) {
 177          return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
 178      }
 179  }
 180  
 181  /**

 182  * Creates a table object in ADOdb's datadict format

 183  *

 184  * This class stores information about a database table. As charactaristics

 185  * of the table are loaded from the external source, methods and properties

 186  * of this class are used to build up the table description in ADOdb's

 187  * datadict format.

 188  *

 189  * @package axmls

 190  * @access private

 191  */
 192  class dbTable extends dbObject {
 193      
 194      /**

 195      * @var string Table name

 196      */
 197      var $name;
 198      
 199      /**

 200      * @var array Field specifier: Meta-information about each field

 201      */
 202      var $fields = array();
 203      
 204      /**

 205      * @var array List of table indexes.

 206      */
 207      var $indexes = array();
 208      
 209      /**

 210      * @var array Table options: Table-level options

 211      */
 212      var $opts = array();
 213      
 214      /**

 215      * @var string Field index: Keeps track of which field is currently being processed

 216      */
 217      var $current_field;
 218      
 219      /**

 220      * @var boolean Mark table for destruction

 221      * @access private

 222      */
 223      var $drop_table;
 224      
 225      /**

 226      * @var boolean Mark field for destruction (not yet implemented)

 227      * @access private

 228      */
 229      var $drop_field = array();
 230      
 231      /**

 232      * Iniitializes a new table object.

 233      *

 234      * @param string $prefix DB Object prefix

 235      * @param array $attributes Array of table attributes.

 236      */
 237  	function dbTable( &$parent, $attributes = NULL ) {
 238          $this->parent = $parent;
 239          $this->name = $this->prefix($attributes['NAME']);
 240      }
 241      
 242      /**

 243      * XML Callback to process start elements. Elements currently 

 244      * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT. 

 245      *

 246      * @access private

 247      */
 248  	function _tag_open( &$parser, $tag, $attributes ) {
 249          $this->currentElement = strtoupper( $tag );
 250          
 251          switch( $this->currentElement ) {
 252              case 'INDEX':
 253                  if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
 254                      xml_set_object( $parser, $this->addIndex( $attributes ) );
 255                  }
 256                  break;
 257              case 'DATA':
 258                  if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
 259                      xml_set_object( $parser, $this->addData( $attributes ) );
 260                  }
 261                  break;
 262              case 'DROP':
 263                  $this->drop();
 264                  break;
 265              case 'FIELD':
 266                  // Add a field

 267                  $fieldName = $attributes['NAME'];
 268                  $fieldType = $attributes['TYPE'];
 269                  $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
 270                  $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
 271                  
 272                  $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
 273                  break;
 274              case 'KEY':
 275              case 'NOTNULL':
 276              case 'AUTOINCREMENT':
 277                  // Add a field option

 278                  $this->addFieldOpt( $this->current_field, $this->currentElement );
 279                  break;
 280              case 'DEFAULT':
 281                  // Add a field option to the table object

 282                  
 283                  // Work around ADOdb datadict issue that misinterprets empty strings.

 284                  if( $attributes['VALUE'] == '' ) {
 285                      $attributes['VALUE'] = " '' ";
 286                  }
 287                  
 288                  $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
 289                  break;
 290              case 'DEFDATE':
 291              case 'DEFTIMESTAMP':
 292                  // Add a field option to the table object

 293                  $this->addFieldOpt( $this->current_field, $this->currentElement );
 294                  break;
 295              default:
 296                  // print_r( array( $tag, $attributes ) );

 297          }
 298      }
 299      
 300      /**

 301      * XML Callback to process CDATA elements

 302      *

 303      * @access private

 304      */
 305  	function _tag_cdata( &$parser, $cdata ) {
 306          switch( $this->currentElement ) {
 307              // Table constraint

 308              case 'CONSTRAINT':
 309                  if( isset( $this->current_field ) ) {
 310                      $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
 311                  } else {
 312                      $this->addTableOpt( $cdata );
 313                  }
 314                  break;
 315              // Table option

 316              case 'OPT':
 317                  $this->addTableOpt( $cdata );
 318                  break;
 319              default:
 320                  
 321          }
 322      }
 323      
 324      /**

 325      * XML Callback to process end elements

 326      *

 327      * @access private

 328      */
 329  	function _tag_close( &$parser, $tag ) {
 330          $this->currentElement = '';
 331          
 332          switch( strtoupper( $tag ) ) {
 333              case 'TABLE':
 334                  $this->parent->addSQL( $this->create( $this->parent ) );
 335                  xml_set_object( $parser, $this->parent );
 336                  $this->destroy();
 337                  break;
 338              case 'FIELD':
 339                  unset($this->current_field);
 340                  break;
 341  
 342          }
 343      }
 344      
 345      /**

 346      * Adds an index to a table object

 347      *

 348      * @param array $attributes Index attributes

 349      * @return object dbIndex object

 350      */
 351      function &addIndex( $attributes ) {
 352          $name = strtoupper( $attributes['NAME'] );
 353          $this->indexes[$name] = new dbIndex( $this, $attributes );
 354          return $this->indexes[$name];
 355      }
 356      
 357      /**

 358      * Adds data to a table object

 359      *

 360      * @param array $attributes Data attributes

 361      * @return object dbData object

 362      */
 363      function &addData( $attributes ) {
 364          if( !isset( $this->data ) ) {
 365              $this->data = new dbData( $this, $attributes );
 366          }
 367          return $this->data;
 368      }
 369      
 370      /**

 371      * Adds a field to a table object

 372      *

 373      * $name is the name of the table to which the field should be added. 

 374      * $type is an ADODB datadict field type. The following field types

 375      * are supported as of ADODB 3.40:

 376      *     - C:  varchar

 377      *    - X:  CLOB (character large object) or largest varchar size

 378      *       if CLOB is not supported

 379      *    - C2: Multibyte varchar

 380      *    - X2: Multibyte CLOB

 381      *    - B:  BLOB (binary large object)

 382      *    - D:  Date (some databases do not support this, and we return a datetime type)

 383      *    - T:  Datetime or Timestamp

 384      *    - L:  Integer field suitable for storing booleans (0 or 1)

 385      *    - I:  Integer (mapped to I4)

 386      *    - I1: 1-byte integer

 387      *    - I2: 2-byte integer

 388      *    - I4: 4-byte integer

 389      *    - I8: 8-byte integer

 390      *    - F:  Floating point number

 391      *    - N:  Numeric or decimal number

 392      *

 393      * @param string $name Name of the table to which the field will be added.

 394      * @param string $type    ADODB datadict field type.

 395      * @param string $size    Field size

 396      * @param array $opts    Field options array

 397      * @return array Field specifier array

 398      */
 399  	function addField( $name, $type, $size = NULL, $opts = NULL ) {
 400          $field_id = $this->FieldID( $name );
 401          
 402          // Set the field index so we know where we are

 403          $this->current_field = $field_id;
 404          
 405          // Set the field name (required)

 406          $this->fields[$field_id]['NAME'] = $name;
 407          
 408          // Set the field type (required)

 409          $this->fields[$field_id]['TYPE'] = $type;
 410          
 411          // Set the field size (optional)

 412          if( isset( $size ) ) {
 413              $this->fields[$field_id]['SIZE'] = $size;
 414          }
 415          
 416          // Set the field options

 417          if( isset( $opts ) ) {
 418              $this->fields[$field_id]['OPTS'][] = $opts;
 419          }
 420      }
 421      
 422      /**

 423      * Adds a field option to the current field specifier

 424      *

 425      * This method adds a field option allowed by the ADOdb datadict 

 426      * and appends it to the given field.

 427      *

 428      * @param string $field    Field name

 429      * @param string $opt ADOdb field option

 430      * @param mixed $value Field option value

 431      * @return array Field specifier array

 432      */
 433  	function addFieldOpt( $field, $opt, $value = NULL ) {
 434          if( !isset( $value ) ) {
 435              $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
 436          // Add the option and value

 437          } else {
 438              $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
 439          }
 440      }
 441      
 442      /**

 443      * Adds an option to the table

 444      *

 445      * This method takes a comma-separated list of table-level options

 446      * and appends them to the table object.

 447      *

 448      * @param string $opt Table option

 449      * @return array Options

 450      */
 451  	function addTableOpt( $opt ) {
 452          $this->opts[] = $opt;
 453          
 454          return $this->opts;
 455      }
 456      
 457      /**

 458      * Generates the SQL that will create the table in the database

 459      *

 460      * @param object $xmls adoSchema object

 461      * @return array Array containing table creation SQL

 462      */
 463  	function create( &$xmls ) {
 464          $sql = array();
 465          
 466          // drop any existing indexes

 467          if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
 468              foreach( $legacy_indexes as $index => $index_details ) {
 469                  $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
 470              }
 471          }
 472          
 473          // remove fields to be dropped from table object

 474          foreach( $this->drop_field as $field ) {
 475              unset( $this->fields[$field] );
 476          }
 477          
 478          // if table exists

 479          if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
 480              // drop table

 481              if( $this->drop_table ) {
 482                  $sql[] = $xmls->dict->DropTableSQL( $this->name );
 483                  
 484                  return $sql;
 485              }
 486              
 487              // drop any existing fields not in schema

 488              foreach( $legacy_fields as $field_id => $field ) {
 489                  if( !isset( $this->fields[$field_id] ) ) {
 490                      $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
 491                  }
 492              }
 493          // if table doesn't exist

 494          } else {
 495              if( $this->drop_table ) {
 496                  return $sql;
 497              }
 498              
 499              $legacy_fields = array();
 500          }
 501          
 502          // Loop through the field specifier array, building the associative array for the field options

 503          $fldarray = array();
 504          
 505          foreach( $this->fields as $field_id => $finfo ) {
 506              // Set an empty size if it isn't supplied

 507              if( !isset( $finfo['SIZE'] ) ) {
 508                  $finfo['SIZE'] = '';
 509              }
 510              
 511              // Initialize the field array with the type and size

 512              $fldarray[$field_id] = array(
 513                  'NAME' => $finfo['NAME'],
 514                  'TYPE' => $finfo['TYPE'],
 515                  'SIZE' => $finfo['SIZE']
 516              );
 517              
 518              // Loop through the options array and add the field options. 

 519              if( isset( $finfo['OPTS'] ) ) {
 520                  foreach( $finfo['OPTS'] as $opt ) {
 521                      // Option has an argument.

 522                      if( is_array( $opt ) ) {
 523                          $key = key( $opt );
 524                          $value = $opt[key( $opt )];
 525                          @$fldarray[$field_id][$key] .= $value;
 526                      // Option doesn't have arguments

 527                      } else {
 528                          $fldarray[$field_id][$opt] = $opt;
 529                      }
 530                  }
 531              }
 532          }
 533          
 534          if( empty( $legacy_fields ) ) {
 535              // Create the new table

 536              $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
 537              logMsg( end( $sql ), 'Generated CreateTableSQL' );
 538          } else {
 539              // Upgrade an existing table

 540              logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
 541              switch( $xmls->upgrade ) {
 542                  // Use ChangeTableSQL

 543                  case 'ALTER':
 544                      logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
 545                      $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
 546                      break;
 547                  case 'REPLACE':
 548                      logMsg( 'Doing upgrade REPLACE (testing)' );
 549                      $sql[] = $xmls->dict->DropTableSQL( $this->name );
 550                      $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
 551                      break;
 552                  // ignore table

 553                  default:
 554                      return array();
 555              }
 556          }
 557          
 558          foreach( $this->indexes as $index ) {
 559              $sql[] = $index->create( $xmls );
 560          }
 561          
 562          if( isset( $this->data ) ) {
 563              $sql[] = $this->data->create( $xmls );
 564          }
 565          
 566          return $sql;
 567      }
 568      
 569      /**

 570      * Marks a field or table for destruction

 571      */
 572  	function drop() {
 573          if( isset( $this->current_field ) ) {
 574              // Drop the current field

 575              logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
 576              // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );

 577              $this->drop_field[$this->current_field] = $this->current_field;
 578          } else {
 579              // Drop the current table

 580              logMsg( "Dropping table '{$this->name}'" );
 581              // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );

 582              $this->drop_table = TRUE;
 583          }
 584      }
 585  }
 586  
 587  /**

 588  * Creates an index object in ADOdb's datadict format

 589  *

 590  * This class stores information about a database index. As charactaristics

 591  * of the index are loaded from the external source, methods and properties

 592  * of this class are used to build up the index description in ADOdb's

 593  * datadict format.

 594  *

 595  * @package axmls

 596  * @access private

 597  */
 598  class dbIndex extends dbObject {
 599      
 600      /**

 601      * @var string    Index name

 602      */
 603      var $name;
 604      
 605      /**

 606      * @var array    Index options: Index-level options

 607      */
 608      var $opts = array();
 609      
 610      /**

 611      * @var array    Indexed fields: Table columns included in this index

 612      */
 613      var $columns = array();
 614      
 615      /**

 616      * @var boolean Mark index for destruction

 617      * @access private

 618      */
 619      var $drop = FALSE;
 620      
 621      /**

 622      * Initializes the new dbIndex object.

 623      *

 624      * @param object $parent Parent object

 625      * @param array $attributes Attributes

 626      *

 627      * @internal

 628      */
 629  	function dbIndex( &$parent, $attributes = NULL ) {
 630          $this->parent = $parent;
 631          
 632          $this->name = $this->prefix ($attributes['NAME']);
 633      }
 634      
 635      /**

 636      * XML Callback to process start elements

 637      *

 638      * Processes XML opening tags. 

 639      * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 

 640      *

 641      * @access private

 642      */
 643  	function _tag_open( &$parser, $tag, $attributes ) {
 644          $this->currentElement = strtoupper( $tag );
 645          
 646          switch( $this->currentElement ) {
 647              case 'DROP':
 648                  $this->drop();
 649                  break;
 650              case 'CLUSTERED':
 651              case 'BITMAP':
 652              case 'UNIQUE':
 653              case 'FULLTEXT':
 654              case 'HASH':
 655                  // Add index Option

 656                  $this->addIndexOpt( $this->currentElement );
 657                  break;
 658              default:
 659                  // print_r( array( $tag, $attributes ) );

 660          }
 661      }
 662      
 663      /**

 664      * XML Callback to process CDATA elements

 665      *

 666      * Processes XML cdata.

 667      *

 668      * @access private

 669      */
 670  	function _tag_cdata( &$parser, $cdata ) {
 671          switch( $this->currentElement ) {
 672              // Index field name

 673              case 'COL':
 674                  $this->addField( $cdata );
 675                  break;
 676              default:
 677                  
 678          }
 679      }
 680      
 681      /**

 682      * XML Callback to process end elements

 683      *

 684      * @access private

 685      */
 686  	function _tag_close( &$parser, $tag ) {
 687          $this->currentElement = '';
 688          
 689          switch( strtoupper( $tag ) ) {
 690              case 'INDEX':
 691                  xml_set_object( $parser, $this->parent );
 692                  break;
 693          }
 694      }
 695      
 696      /**

 697      * Adds a field to the index

 698      *

 699      * @param string $name Field name

 700      * @return string Field list

 701      */
 702  	function addField( $name ) {
 703          $this->columns[$this->FieldID( $name )] = $name;
 704          
 705          // Return the field list

 706          return $this->columns;
 707      }
 708      
 709      /**

 710      * Adds options to the index

 711      *

 712      * @param string $opt Comma-separated list of index options.

 713      * @return string Option list

 714      */
 715  	function addIndexOpt( $opt ) {
 716          $this->opts[] = $opt;
 717          
 718          // Return the options list

 719          return $this->opts;
 720      }
 721      
 722      /**

 723      * Generates the SQL that will create the index in the database

 724      *

 725      * @param object $xmls adoSchema object

 726      * @return array Array containing index creation SQL

 727      */
 728  	function create( &$xmls ) {
 729          if( $this->drop ) {
 730              return NULL;
 731          }
 732          
 733          // eliminate any columns that aren't in the table

 734          foreach( $this->columns as $id => $col ) {
 735              if( !isset( $this->parent->fields[$id] ) ) {
 736                  unset( $this->columns[$id] );
 737              }
 738          }
 739          
 740          return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
 741      }
 742      
 743      /**

 744      * Marks an index for destruction

 745      */
 746  	function drop() {
 747          $this->drop = TRUE;
 748      }
 749  }
 750  
 751  /**

 752  * Creates a data object in ADOdb's datadict format

 753  *

 754  * This class stores information about table data.

 755  *

 756  * @package axmls

 757  * @access private

 758  */
 759  class dbData extends dbObject {
 760      
 761      var $data = array();
 762      
 763      var $row;
 764      
 765      /**

 766      * Initializes the new dbIndex object.

 767      *

 768      * @param object $parent Parent object

 769      * @param array $attributes Attributes

 770      *

 771      * @internal

 772      */
 773  	function dbData( &$parent, $attributes = NULL ) {
 774          $this->parent = $parent;
 775      }
 776      
 777      /**

 778      * XML Callback to process start elements

 779      *

 780      * Processes XML opening tags. 

 781      * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 

 782      *

 783      * @access private

 784      */
 785  	function _tag_open( &$parser, $tag, $attributes ) {
 786          $this->currentElement = strtoupper( $tag );
 787          
 788          switch( $this->currentElement ) {
 789              case 'ROW':
 790                  $this->row = count( $this->data );
 791                  $this->data[$this->row] = array();
 792                  break;
 793              case 'F':
 794                  $this->addField($attributes);
 795              default:
 796                  // print_r( array( $tag, $attributes ) );

 797          }
 798      }
 799      
 800      /**

 801      * XML Callback to process CDATA elements

 802      *

 803      * Processes XML cdata.

 804      *

 805      * @access private

 806      */
 807  	function _tag_cdata( &$parser, $cdata ) {
 808          switch( $this->currentElement ) {
 809              // Index field name

 810              case 'F':
 811                  $this->addData( $cdata );
 812                  break;
 813              default:
 814                  
 815          }
 816      }
 817      
 818      /**

 819      * XML Callback to process end elements

 820      *

 821      * @access private

 822      */
 823  	function _tag_close( &$parser, $tag ) {
 824          $this->currentElement = '';
 825          
 826          switch( strtoupper( $tag ) ) {
 827              case 'DATA':
 828                  xml_set_object( $parser, $this->parent );
 829                  break;
 830          }
 831      }
 832      
 833      /**

 834      * Adds a field to the index

 835      *

 836      * @param string $name Field name

 837      * @return string Field list

 838      */
 839  	function addField( $attributes ) {
 840          if( isset( $attributes['NAME'] ) ) {
 841              $name = $attributes['NAME'];
 842          } else {
 843              $name = count($this->data[$this->row]);
 844          }
 845          
 846          // Set the field index so we know where we are

 847          $this->current_field = $this->FieldID( $name );
 848      }
 849      
 850      /**

 851      * Adds options to the index

 852      *

 853      * @param string $opt Comma-separated list of index options.

 854      * @return string Option list

 855      */
 856  	function addData( $cdata ) {
 857          if( !isset( $this->data[$this->row] ) ) {
 858              $this->data[$this->row] = array();
 859          }
 860          
 861          if( !isset( $this->data[$this->row][$this->current_field] ) ) {
 862              $this->data[$this->row][$this->current_field] = '';
 863          }
 864          
 865          $this->data[$this->row][$this->current_field] .= $cdata;
 866      }
 867      
 868      /**

 869      * Generates the SQL that will create the index in the database

 870      *

 871      * @param object $xmls adoSchema object

 872      * @return array Array containing index creation SQL

 873      */
 874  	function create( &$xmls ) {
 875          $table = $xmls->dict->TableName($this->parent->name);
 876          $table_field_count = count($this->parent->fields);
 877          $sql = array();
 878          
 879          // eliminate any columns that aren't in the table

 880          foreach( $this->data as $row ) {
 881              $table_fields = $this->parent->fields;
 882              $fields = array();
 883              
 884              foreach( $row as $field_id => $field_data ) {
 885                  if( !array_key_exists( $field_id, $table_fields ) ) {
 886                      if( is_numeric( $field_id ) ) {
 887                          $field_id = reset( array_keys( $table_fields ) );
 888                      } else {
 889                          continue;
 890                      }
 891                  }
 892                  
 893                  $name = $table_fields[$field_id]['NAME'];
 894                  
 895                  switch( $table_fields[$field_id]['TYPE'] ) {
 896                      case 'C':
 897                      case 'C2':
 898                      case 'X':
 899                      case 'X2':
 900                          $fields[$name] = $xmls->db->qstr( $field_data );
 901                          break;
 902                      case 'I':
 903                      case 'I1':
 904                      case 'I2':
 905                      case 'I4':
 906                      case 'I8':
 907                          $fields[$name] = intval($field_data);
 908                          break;
 909                      default:
 910                          $fields[$name] = $field_data;
 911                  }
 912                  
 913                  unset($table_fields[$field_id]);
 914              }
 915              
 916              // check that at least 1 column is specified

 917              if( empty( $fields ) ) {
 918                  continue;
 919              }
 920              
 921              // check that no required columns are missing

 922              if( count( $fields ) < $table_field_count ) {
 923                  foreach( $table_fields as $field ) {
 924                      if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
 925                          continue(2);
 926                      }
 927                  }
 928              }
 929              
 930              $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
 931          }
 932          
 933          return $sql;
 934      }
 935  }
 936  
 937  /**

 938  * Creates the SQL to execute a list of provided SQL queries

 939  *

 940  * @package axmls

 941  * @access private

 942  */
 943  class dbQuerySet extends dbObject {
 944      
 945      /**

 946      * @var array    List of SQL queries

 947      */
 948      var $queries = array();
 949      
 950      /**

 951      * @var string    String used to build of a query line by line

 952      */
 953      var $query;
 954      
 955      /**

 956      * @var string    Query prefix key

 957      */
 958      var $prefixKey = '';
 959      
 960      /**

 961      * @var boolean    Auto prefix enable (TRUE)

 962      */
 963      var $prefixMethod = 'AUTO';
 964      
 965      /**

 966      * Initializes the query set.

 967      *

 968      * @param object $parent Parent object

 969      * @param array $attributes Attributes

 970      */
 971  	function dbQuerySet( &$parent, $attributes = NULL ) {
 972          $this->parent = $parent;
 973              
 974          // Overrides the manual prefix key

 975          if( isset( $attributes['KEY'] ) ) {
 976              $this->prefixKey = $attributes['KEY'];
 977          }
 978          
 979          $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
 980          
 981          // Enables or disables automatic prefix prepending

 982          switch( $prefixMethod ) {
 983              case 'AUTO':
 984                  $this->prefixMethod = 'AUTO';
 985                  break;
 986              case 'MANUAL':
 987                  $this->prefixMethod = 'MANUAL';
 988                  break;
 989              case 'NONE':
 990                  $this->prefixMethod = 'NONE';
 991                  break;
 992          }
 993      }
 994      
 995      /**

 996      * XML Callback to process start elements. Elements currently 

 997      * processed are: QUERY. 

 998      *

 999      * @access private

1000      */
1001  	function _tag_open( &$parser, $tag, $attributes ) {
1002          $this->currentElement = strtoupper( $tag );
1003          
1004          switch( $this->currentElement ) {
1005              case 'QUERY':
1006                  // Create a new query in a SQL queryset.

1007                  // Ignore this query set if a platform is specified and it's different than the 

1008                  // current connection platform.

1009                  if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1010                      $this->newQuery();
1011                  } else {
1012                      $this->discardQuery();
1013                  }
1014                  break;
1015              default:
1016                  // print_r( array( $tag, $attributes ) );

1017          }
1018      }
1019      
1020      /**

1021      * XML Callback to process CDATA elements

1022      */
1023  	function _tag_cdata( &$parser, $cdata ) {
1024          switch( $this->currentElement ) {
1025              // Line of queryset SQL data

1026              case 'QUERY':
1027                  $this->buildQuery( $cdata );
1028                  break;
1029              default:
1030                  
1031          }
1032      }
1033      
1034      /**

1035      * XML Callback to process end elements

1036      *

1037      * @access private

1038      */
1039  	function _tag_close( &$parser, $tag ) {
1040          $this->currentElement = '';
1041          
1042          switch( strtoupper( $tag ) ) {
1043              case 'QUERY':
1044                  // Add the finished query to the open query set.

1045                  $this->addQuery();
1046                  break;
1047              case 'SQL':
1048                  $this->parent->addSQL( $this->create( $this->parent ) );
1049                  xml_set_object( $parser, $this->parent );
1050                  $this->destroy();
1051                  break;
1052              default:
1053                  
1054          }
1055      }
1056      
1057      /**

1058      * Re-initializes the query.

1059      *

1060      * @return boolean TRUE

1061      */
1062  	function newQuery() {
1063          $this->query = '';
1064          
1065          return TRUE;
1066      }
1067      
1068      /**

1069      * Discards the existing query.

1070      *

1071      * @return boolean TRUE

1072      */
1073  	function discardQuery() {
1074          unset( $this->query );
1075          
1076          return TRUE;
1077      }
1078      
1079      /** 

1080      * Appends a line to a query that is being built line by line

1081      *

1082      * @param string $data Line of SQL data or NULL to initialize a new query

1083      * @return string SQL query string.

1084      */
1085  	function buildQuery( $sql = NULL ) {
1086          if( !isset( $this->query ) OR empty( $sql ) ) {
1087              return FALSE;
1088          }
1089          
1090          $this->query .= $sql;
1091          
1092          return $this->query;
1093      }
1094      
1095      /**

1096      * Adds a completed query to the query list

1097      *

1098      * @return string    SQL of added query

1099      */
1100  	function addQuery() {
1101          if( !isset( $this->query ) ) {
1102              return FALSE;
1103          }
1104          
1105          $this->queries[] = $return = trim($this->query);
1106          
1107          unset( $this->query );
1108          
1109          return $return;
1110      }
1111      
1112      /**

1113      * Creates and returns the current query set

1114      *

1115      * @param object $xmls adoSchema object

1116      * @return array Query set

1117      */
1118  	function create( &$xmls ) {
1119          foreach( $this->queries as $id => $query ) {
1120              switch( $this->prefixMethod ) {
1121                  case 'AUTO':
1122                      // Enable auto prefix replacement

1123                      
1124                      // Process object prefix.

1125                      // Evaluate SQL statements to prepend prefix to objects

1126                      $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1127                      $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1128                      $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1129                      
1130                      // SELECT statements aren't working yet

1131                      #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );

1132                      
1133                  case 'MANUAL':
1134                      // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.

1135                      // If prefixKey is not set, we use the default constant XMLS_PREFIX

1136                      if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
1137                          // Enable prefix override

1138                          $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
1139                      } else {
1140                          // Use default replacement

1141                          $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
1142                      }
1143              }
1144              
1145              $this->queries[$id] = trim( $query );
1146          }
1147          
1148          // Return the query set array

1149          return $this->queries;
1150      }
1151      
1152      /**

1153      * Rebuilds the query with the prefix attached to any objects

1154      *

1155      * @param string $regex Regex used to add prefix

1156      * @param string $query SQL query string

1157      * @param string $prefix Prefix to be appended to tables, indices, etc.

1158      * @return string Prefixed SQL query string.

1159      */
1160  	function prefixQuery( $regex, $query, $prefix = NULL ) {
1161          if( !isset( $prefix ) ) {
1162              return $query;
1163          }
1164          
1165          if( preg_match( $regex, $query, $match ) ) {
1166              $preamble = $match[1];
1167              $postamble = $match[5];
1168              $objectList = explode( ',', $match[3] );
1169              // $prefix = $prefix . '_';

1170              
1171              $prefixedList = '';
1172              
1173              foreach( $objectList as $object ) {
1174                  if( $prefixedList !== '' ) {
1175                      $prefixedList .= ', ';
1176                  }
1177                  
1178                  $prefixedList .= $prefix . trim( $object );
1179              }
1180              
1181              $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
1182          }
1183          
1184          return $query;
1185      }
1186  }
1187  
1188  /**

1189  * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements

1190  * 

1191  * This class is used to load and parse the XML file, to create an array of SQL statements

1192  * that can be used to build a database, and to build the database using the SQL array.

1193  *

1194  * @tutorial getting_started.pkg

1195  *

1196  * @author Richard Tango-Lowy & Dan Cech

1197  * @version $Revision: 1.1 $

1198  *

1199  * @package axmls

1200  */
1201  class adoSchema {
1202      
1203      /**

1204      * @var array    Array containing SQL queries to generate all objects

1205      * @access private

1206      */
1207      var $sqlArray;
1208      
1209      /**

1210      * @var object    ADOdb connection object

1211      * @access private

1212      */
1213      var $db;
1214      
1215      /**

1216      * @var object    ADOdb Data Dictionary

1217      * @access private

1218      */
1219      var $dict;
1220      
1221      /**

1222      * @var string Current XML element

1223      * @access private

1224      */
1225      var $currentElement = '';
1226      
1227      /**

1228      * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database

1229      * @access private

1230      */
1231      var $upgrade = '';
1232      
1233      /**

1234      * @var string Optional object prefix

1235      * @access private

1236      */
1237      var $objectPrefix = '';
1238      
1239      /**

1240      * @var long    Original Magic Quotes Runtime value

1241      * @access private

1242      */
1243      var $mgq;
1244      
1245      /**

1246      * @var long    System debug

1247      * @access private

1248      */
1249      var $debug;
1250      
1251      /**

1252      * @var string Regular expression to find schema version

1253      * @access private

1254      */
1255      var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
1256      
1257      /**

1258      * @var string Current schema version

1259      * @access private

1260      */
1261      var $schemaVersion;
1262      
1263      /**

1264      * @var int    Success of last Schema execution

1265      */
1266      var $success;
1267      
1268      /**

1269      * @var bool    Execute SQL inline as it is generated

1270      */
1271      var $executeInline;
1272      
1273      /**

1274      * @var bool    Continue SQL execution if errors occur

1275      */
1276      var $continueOnError;
1277      
1278      /**

1279      * Creates an adoSchema object

1280      *

1281      * Creating an adoSchema object is the first step in processing an XML schema.

1282      * The only parameter is an ADOdb database connection object, which must already

1283      * have been created.

1284      *

1285      * @param object $db ADOdb database connection object.

1286      */
1287  	function adoSchema( &$db ) {
1288          // Initialize the environment

1289          $this->mgq = get_magic_quotes_runtime();
1290          @set_magic_quotes_runtime(0);
1291          
1292          $this->db = $db;
1293          $this->debug = $this->db->debug;
1294          $this->dict = NewDataDictionary( $this->db );
1295          $this->sqlArray = array();
1296          $this->schemaVersion = XMLS_SCHEMA_VERSION;
1297          $this->executeInline( XMLS_EXECUTE_INLINE );
1298          $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
1299          $this->setUpgradeMethod();
1300      }
1301      
1302      /**

1303      * Sets the method to be used for upgrading an existing database

1304      *

1305      * Use this method to specify how existing database objects should be upgraded.

1306      * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to

1307      * alter each database object directly, REPLACE attempts to rebuild each object

1308      * from scratch, BEST attempts to determine the best upgrade method for each

1309      * object, and NONE disables upgrading.

1310      *

1311      * This method is not yet used by AXMLS, but exists for backward compatibility.

1312      * The ALTER method is automatically assumed when the adoSchema object is

1313      * instantiated; other upgrade methods are not currently supported.

1314      *

1315      * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)

1316      * @returns string Upgrade method used

1317      */
1318  	function SetUpgradeMethod( $method = '' ) {
1319          if( !is_string( $method ) ) {
1320              return FALSE;
1321          }
1322          
1323          $method = strtoupper( $method );
1324          
1325          // Handle the upgrade methods

1326          switch( $method ) {
1327              case 'ALTER':
1328                  $this->upgrade = $method;
1329                  break;
1330              case 'REPLACE':
1331                  $this->upgrade = $method;
1332                  break;
1333              case 'BEST':
1334                  $this->upgrade = 'ALTER';
1335                  break;
1336              case 'NONE':
1337                  $this->upgrade = 'NONE';
1338                  break;
1339              default:
1340                  // Use default if no legitimate method is passed.

1341                  $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
1342          }
1343          
1344          return $this->upgrade;
1345      }
1346      
1347      /**

1348      * Enables/disables inline SQL execution.

1349      *

1350      * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),

1351      * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode

1352      * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()

1353      * to apply the schema to the database.

1354      *

1355      * @param bool $mode execute

1356      * @return bool current execution mode

1357      *

1358      * @see ParseSchema(), ExecuteSchema()

1359      */
1360  	function ExecuteInline( $mode = NULL ) {
1361          if( is_bool( $mode ) ) {
1362              $this->executeInline = $mode;
1363          }
1364          
1365          return $this->executeInline;
1366      }
1367      
1368      /**

1369      * Enables/disables SQL continue on error.

1370      *

1371      * Call this method to enable or disable continuation of SQL execution if an error occurs.

1372      * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.

1373      * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing

1374      * of the schema will continue.

1375      *

1376      * @param bool $mode execute

1377      * @return bool current continueOnError mode

1378      *

1379      * @see addSQL(), ExecuteSchema()

1380      */
1381  	function ContinueOnError( $mode = NULL ) {
1382          if( is_bool( $mode ) ) {
1383              $this->continueOnError = $mode;
1384          }
1385          
1386          return $this->continueOnError;
1387      }
1388      
1389      /**

1390      * Loads an XML schema from a file and converts it to SQL.

1391      *

1392      * Call this method to load the specified schema (see the DTD for the proper format) from

1393      * the filesystem and generate the SQL necessary to create the database described. 

1394      * @see ParseSchemaString()

1395      *

1396      * @param string $file Name of XML schema file.

1397      * @param bool $returnSchema Return schema rather than parsing.

1398      * @return array Array of SQL queries, ready to execute

1399      */
1400  	function ParseSchema( $filename, $returnSchema = FALSE ) {
1401          return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
1402      }
1403      
1404      /**

1405      * Loads an XML schema from a file and converts it to SQL.

1406      *

1407      * Call this method to load the specified schema from a file (see the DTD for the proper format) 

1408      * and generate the SQL necessary to create the database described by the schema.

1409      *

1410      * @param string $file Name of XML schema file.

1411      * @param bool $returnSchema Return schema rather than parsing.

1412      * @return array Array of SQL queries, ready to execute.

1413      *

1414      * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()

1415      * @see ParseSchema(), ParseSchemaString()

1416      */
1417  	function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
1418          // Open the file

1419          if( !($fp = fopen( $filename, 'r' )) ) {
1420              // die( 'Unable to open file' );

1421              return FALSE;
1422          }
1423          
1424          // do version detection here

1425          if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
1426              return FALSE;
1427          }
1428          
1429          if ( $returnSchema )
1430          {
1431              return $xmlstring;
1432          }
1433          
1434          $this->success = 2;
1435          
1436          $xmlParser = $this->create_parser();
1437          
1438          // Process the file

1439          while( $data = fread( $fp, 4096 ) ) {
1440              if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
1441                  die( sprintf(
1442                      "XML error: %s at line %d",
1443                      xml_error_string( xml_get_error_code( $xmlParser) ),
1444                      xml_get_current_line_number( $xmlParser)
1445                  ) );
1446              }
1447          }
1448          
1449          xml_parser_free( $xmlParser );
1450          
1451          return $this->sqlArray;
1452      }
1453      
1454      /**

1455      * Converts an XML schema string to SQL.

1456      *

1457      * Call this method to parse a string containing an XML schema (see the DTD for the proper format)

1458      * and generate the SQL necessary to create the database described by the schema. 

1459      * @see ParseSchema()

1460      *

1461      * @param string $xmlstring XML schema string.

1462      * @param bool $returnSchema Return schema rather than parsing.

1463      * @return array Array of SQL queries, ready to execute.

1464      */
1465  	function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
1466          if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
1467              return FALSE;
1468          }
1469          
1470          // do version detection here

1471          if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
1472              return FALSE;
1473          }
1474          
1475          if ( $returnSchema )
1476          {
1477              return $xmlstring;
1478          }
1479          
1480          $this->success = 2;
1481          
1482          $xmlParser = $this->create_parser();
1483          
1484          if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
1485              die( sprintf(
1486                  "XML error: %s at line %d",
1487                  xml_error_string( xml_get_error_code( $xmlParser) ),
1488                  xml_get_current_line_number( $xmlParser)
1489              ) );
1490          }
1491          
1492          xml_parser_free( $xmlParser );
1493          
1494          return $this->sqlArray;
1495      }
1496      
1497      /**

1498      * Loads an XML schema from a file and converts it to uninstallation SQL.

1499      *

1500      * Call this method to load the specified schema (see the DTD for the proper format) from

1501      * the filesystem and generate the SQL necessary to remove the database described.

1502      * @see RemoveSchemaString()

1503      *

1504      * @param string $file Name of XML schema file.

1505      * @param bool $returnSchema Return schema rather than parsing.

1506      * @return array Array of SQL queries, ready to execute

1507      */
1508  	function RemoveSchema( $filename, $returnSchema = FALSE ) {
1509          return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
1510      }
1511      
1512      /**

1513      * Converts an XML schema string to uninstallation SQL.

1514      *

1515      * Call this method to parse a string containing an XML schema (see the DTD for the proper format)

1516      * and generate the SQL necessary to uninstall the database described by the schema. 

1517      * @see RemoveSchema()

1518      *

1519      * @param string $schema XML schema string.

1520      * @param bool $returnSchema Return schema rather than parsing.

1521      * @return array Array of SQL queries, ready to execute.

1522      */
1523  	function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
1524          
1525          // grab current version

1526          if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
1527              return FALSE;
1528          }
1529          
1530          return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
1531      }
1532      
1533      /**

1534      * Applies the current XML schema to the database (post execution).

1535      *

1536      * Call this method to apply the current schema (generally created by calling 

1537      * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, 

1538      * and executing other SQL specified in the schema) after parsing.

1539      * @see ParseSchema(), ParseSchemaString(), ExecuteInline()

1540      *

1541      * @param array $sqlArray Array of SQL statements that will be applied rather than

1542      *        the current schema.

1543      * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.

1544      * @returns integer 0 if failure, 1 if errors, 2 if successful.

1545      */
1546  	function ExecuteSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
1547          if( !is_bool( $continueOnErr ) ) {
1548              $continueOnErr = $this->ContinueOnError();
1549          }
1550          
1551          if( !isset( $sqlArray ) ) {
1552              $sqlArray = $this->sqlArray;
1553          }
1554          
1555          if( !is_array( $sqlArray ) ) {
1556              $this->success = 0;
1557          } else {
1558              $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
1559          }
1560          
1561          return $this->success;
1562      }
1563      
1564      /**

1565      * Returns the current SQL array. 

1566      *

1567      * Call this method to fetch the array of SQL queries resulting from 

1568      * ParseSchema() or ParseSchemaString(). 

1569      *

1570      * @param string $format Format: HTML, TEXT, or NONE (PHP array)

1571      * @return array Array of SQL statements or FALSE if an error occurs

1572      */
1573  	function PrintSQL( $format = 'NONE' ) {
1574          return $this->getSQL( $format, $sqlArray );
1575      }
1576      
1577      /**

1578      * Saves the current SQL array to the local filesystem as a list of SQL queries.

1579      *

1580      * Call this method to save the array of SQL queries (generally resulting from a

1581      * parsed XML schema) to the filesystem.

1582      *

1583      * @param string $filename Path and name where the file should be saved.

1584      * @return boolean TRUE if save is successful, else FALSE. 

1585      */
1586  	function SaveSQL( $filename = './schema.sql' ) {
1587          
1588          if( !isset( $sqlArray ) ) {
1589              $sqlArray = $this->sqlArray;
1590          }
1591          if( !isset( $sqlArray ) ) {
1592              return FALSE;
1593          }
1594          
1595          $fp = fopen( $filename, "w" );
1596          
1597          foreach( $sqlArray as $key => $query ) {
1598              fwrite( $fp, $query . ";\n" );
1599          }
1600          fclose( $fp );
1601      }
1602      
1603      /**

1604      * Create an xml parser

1605      *

1606      * @return object PHP XML parser object

1607      *

1608      * @access private

1609      */
1610      function &create_parser() {
1611          // Create the parser

1612          $xmlParser = xml_parser_create();
1613          xml_set_object( $xmlParser, $this );
1614          
1615          // Initialize the XML callback functions

1616          xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
1617          xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
1618          
1619          return $xmlParser;
1620      }
1621      
1622      /**

1623      * XML Callback to process start elements

1624      *

1625      * @access private

1626      */
1627  	function _tag_open( &$parser, $tag, $attributes ) {
1628          switch( strtoupper( $tag ) ) {
1629              case 'TABLE':
1630                  $this->obj = new dbTable( $this, $attributes );
1631                  xml_set_object( $parser, $this->obj );
1632                  break;
1633              case 'SQL':
1634                  if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1635                      $this->obj = new dbQuerySet( $this, $attributes );
1636                      xml_set_object( $parser, $this->obj );
1637                  }
1638                  break;
1639              default:
1640                  // print_r( array( $tag, $attributes ) );

1641          }
1642          
1643      }
1644      
1645      /**

1646      * XML Callback to process CDATA elements

1647      *

1648      * @access private

1649      */
1650  	function _tag_cdata( &$parser, $cdata ) {
1651      }
1652      
1653      /**

1654      * XML Callback to process end elements

1655      *

1656      * @access private

1657      * @internal

1658      */
1659  	function _tag_close( &$parser, $tag ) {
1660          
1661      }
1662      
1663      /**

1664      * Converts an XML schema string to the specified DTD version.

1665      *

1666      * Call this method to convert a string containing an XML schema to a different AXMLS

1667      * DTD version. For instance, to convert a schema created for an pre-1.0 version for 

1668      * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 

1669      * parameter is specified, the schema will be converted to the current DTD version. 

1670      * If the newFile parameter is provided, the converted schema will be written to the specified

1671      * file.

1672      * @see ConvertSchemaFile()

1673      *

1674      * @param string $schema String containing XML schema that will be converted.

1675      * @param string $newVersion DTD version to convert to.

1676      * @param string $newFile File name of (converted) output file.

1677      * @return string Converted XML schema or FALSE if an error occurs.

1678      */
1679  	function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
1680          
1681          // grab current version

1682          if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
1683              return FALSE;
1684          }
1685          
1686          if( !isset ($newVersion) ) {
1687              $newVersion = $this->schemaVersion;
1688          }
1689          
1690          if( $version == $newVersion ) {
1691              $result = $schema;
1692          } else {
1693              $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
1694          }
1695          
1696          if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
1697              fwrite( $fp, $result );
1698              fclose( $fp );
1699          }
1700          
1701          return $result;
1702      }
1703      
1704      /**

1705      * Converts an XML schema file to the specified DTD version.

1706      *

1707      * Call this method to convert the specified XML schema file to a different AXMLS

1708      * DTD version. For instance, to convert a schema created for an pre-1.0 version for 

1709      * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 

1710      * parameter is specified, the schema will be converted to the current DTD version. 

1711      * If the newFile parameter is provided, the converted schema will be written to the specified

1712      * file.

1713      * @see ConvertSchemaString()

1714      *

1715      * @param string $filename Name of XML schema file that will be converted.

1716      * @param string $newVersion DTD version to convert to.

1717      * @param string $newFile File name of (converted) output file.

1718      * @return string Converted XML schema or FALSE if an error occurs.

1719      */
1720  	function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
1721          
1722          // grab current version

1723          if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
1724              return FALSE;
1725          }
1726          
1727          if( !isset ($newVersion) ) {
1728              $newVersion = $this->schemaVersion;
1729          }
1730          
1731          if( $version == $newVersion ) {
1732              $result = file_get_contents( $filename );
1733              
1734              // remove unicode BOM if present

1735              if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
1736                  $result = substr( $result, 3 );
1737              }
1738          } else {
1739              $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
1740          }
1741          
1742          if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
1743              fwrite( $fp, $result );
1744              fclose( $fp );
1745          }
1746          
1747          return $result;
1748      }
1749      
1750  	function TransformSchema( $schema, $xsl, $schematype='string' )
1751      {
1752          // Fail if XSLT extension is not available

1753          if( ! function_exists( 'xslt_create' ) ) {
1754              return FALSE;
1755          }
1756          
1757          $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
1758          
1759          // look for xsl

1760          if( !is_readable( $xsl_file ) ) {
1761              return FALSE;
1762          }
1763          
1764          switch( $schematype )
1765          {
1766              case 'file':
1767                  if( !is_readable( $schema ) ) {
1768                      return FALSE;
1769                  }
1770                  
1771                  $schema = file_get_contents( $schema );
1772                  break;
1773              case 'string':
1774              default:
1775                  if( !is_string( $schema ) ) {
1776                      return FALSE;
1777                  }
1778          }
1779          
1780          $arguments = array (
1781              '/_xml' => $schema,
1782              '/_xsl' => file_get_contents( $xsl_file )
1783          );
1784          
1785          // create an XSLT processor

1786          $xh = xslt_create ();
1787          
1788          // set error handler

1789          xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
1790          
1791          // process the schema

1792          $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 
1793          
1794          xslt_free ($xh);
1795          
1796          return $result;
1797      }
1798      
1799      /**

1800      * Processes XSLT transformation errors

1801      *

1802      * @param object $parser XML parser object

1803      * @param integer $errno Error number

1804      * @param integer $level Error level

1805      * @param array $fields Error information fields

1806      *

1807      * @access private

1808      */
1809  	function xslt_error_handler( $parser, $errno, $level, $fields ) {
1810          if( is_array( $fields ) ) {
1811              $msg = array(
1812                  'Message Type' => ucfirst( $fields['msgtype'] ),
1813                  'Message Code' => $fields['code'],
1814                  'Message' => $fields['msg'],
1815                  'Error Number' => $errno,
1816                  'Level' => $level
1817              );
1818              
1819              switch( $fields['URI'] ) {
1820                  case 'arg:/_xml':
1821                      $msg['Input'] = 'XML';
1822                      break;
1823                  case 'arg:/_xsl':
1824                      $msg['Input'] = 'XSL';
1825                      break;
1826                  default:
1827                      $msg['Input'] = $fields['URI'];
1828              }
1829              
1830              $msg['Line'] = $fields['line'];
1831          } else {
1832              $msg = array(
1833                  'Message Type' => 'Error',
1834                  'Error Number' => $errno,
1835                  'Level' => $level,
1836                  'Fields' => var_export( $fields, TRUE )
1837              );
1838          }
1839          
1840          $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
1841                         . '<table>' . "\n";
1842          
1843          foreach( $msg as $label => $details ) {
1844              $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
1845          }
1846          
1847          $error_details .= '</table>';
1848          
1849          trigger_error( $error_details, E_USER_ERROR );
1850      }
1851      
1852      /**

1853      * Returns the AXMLS Schema Version of the requested XML schema file.

1854      *

1855      * Call this method to obtain the AXMLS DTD version of the requested XML schema file.

1856      * @see SchemaStringVersion()

1857      *

1858      * @param string $filename AXMLS schema file

1859      * @return string Schema version number or FALSE on error

1860      */
1861  	function SchemaFileVersion( $filename ) {
1862          // Open the file

1863          if( !($fp = fopen( $filename, 'r' )) ) {
1864              // die( 'Unable to open file' );

1865              return FALSE;
1866          }
1867          
1868          // Process the file

1869          while( $data = fread( $fp, 4096 ) ) {
1870              if( preg_match( $this->versionRegex, $data, $matches ) ) {
1871                  return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
1872              }
1873          }
1874          
1875          return FALSE;
1876      }
1877      
1878      /**

1879      * Returns the AXMLS Schema Version of the provided XML schema string.

1880      *

1881      * Call this method to obtain the AXMLS DTD version of the provided XML schema string.

1882      * @see SchemaFileVersion()

1883      *

1884      * @param string $xmlstring XML schema string

1885      * @return string Schema version number or FALSE on error

1886      */
1887  	function SchemaStringVersion( $xmlstring ) {
1888          if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
1889              return FALSE;
1890          }
1891          
1892          if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
1893              return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
1894          }
1895          
1896          return FALSE;
1897      }
1898      
1899      /**

1900      * Extracts an XML schema from an existing database.

1901      *

1902      * Call this method to create an XML schema string from an existing database.

1903      * If the data parameter is set to TRUE, AXMLS will include the data from the database

1904      * in the schema. 

1905      *

1906      * @param boolean $data Include data in schema dump

1907      * @return string Generated XML schema

1908      */
1909  	function ExtractSchema( $data = FALSE ) {
1910          $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
1911          
1912          $schema = '<?xml version="1.0"?>' . "\n"
1913                  . '<schema version="' . $this->schemaVersion . '">' . "\n";
1914          
1915          if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) {
1916              foreach( $tables as $table ) {
1917                  $schema .= '    <table name="' . $table . '">' . "\n";
1918                  
1919                  // grab details from database

1920                  $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' );
1921                  $fields = $this->db->MetaColumns( $table );
1922                  $indexes = $this->db->MetaIndexes( $table );
1923                  
1924                  if( is_array( $fields ) ) {
1925                      foreach( $fields as $details ) {
1926                          $extra = '';
1927                          $content = array();
1928                          
1929                          if( $details->max_length > 0 ) {
1930                              $extra .= ' size="' . $details->max_length . '"';
1931                          }
1932                          
1933                          if( $details->primary_key ) {
1934                              $content[] = '<KEY/>';
1935                          } elseif( $details->not_null ) {
1936                              $content[] = '<NOTNULL/>';
1937                          }
1938                          
1939                          if( $details->has_default ) {
1940                              $content[] = '<DEFAULT value="' . $details->default_value . '"/>';
1941                          }
1942                          
1943                          if( $details->auto_increment ) {
1944                              $content[] = '<AUTOINCREMENT/>';
1945                          }
1946                          
1947                          // this stops the creation of 'R' columns,

1948                          // AUTOINCREMENT is used to create auto columns

1949                          $details->primary_key = 0;
1950                          $type = $rs->MetaType( $details );
1951                          
1952                          $schema .= '        <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>';
1953                          
1954                          if( !empty( $content ) ) {
1955                              $schema .= "\n            " . implode( "\n            ", $content ) . "\n        ";
1956                          }
1957                          
1958                          $schema .= '</field>' . "\n";
1959                      }
1960                  }
1961                  
1962                  if( is_array( $indexes ) ) {
1963                      foreach( $indexes as $index => $details ) {
1964                          $schema .= '        <index name="' . $index . '">' . "\n";
1965                          
1966                          if( $details['unique'] ) {
1967                              $schema .= '            <UNIQUE/>' . "\n";
1968                          }
1969                          
1970                          foreach( $details['columns'] as $column ) {
1971                              $schema .= '            <col>' . $column . '</col>' . "\n";
1972                          }
1973                          
1974                          $schema .= '        </index>' . "\n";
1975                      }
1976                  }
1977                  
1978                  if( $data ) {
1979                      $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
1980                      
1981                      if( is_object( $rs ) ) {
1982                          $schema .= '        <data>' . "\n";
1983                          
1984                          while( $row = $rs->FetchRow() ) {
1985                              foreach( $row as $key => $val ) {
1986                                  $row[$key] = htmlentities($val);
1987                              }
1988                              
1989                              $schema .= '            <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n";
1990                          }
1991                          
1992                          $schema .= '        </data>' . "\n";
1993                      }
1994                  }
1995                  
1996                  $schema .= '    </table>' . "\n";
1997              }
1998          }
1999          
2000          $this->db->SetFetchMode( $old_mode );
2001          
2002          $schema .= '</schema>';
2003          return $schema;
2004      }
2005      
2006      /**

2007      * Sets a prefix for database objects

2008      *

2009      * Call this method to set a standard prefix that will be prepended to all database tables 

2010      * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.

2011      *

2012      * @param string $prefix Prefix that will be prepended.

2013      * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.

2014      * @return boolean TRUE if successful, else FALSE

2015      */
2016  	function SetPrefix( $prefix = '', $underscore = TRUE ) {
2017          switch( TRUE ) {
2018              // clear prefix

2019              case empty( $prefix ):
2020                  logMsg( 'Cleared prefix' );
2021                  $this->objectPrefix = '';
2022                  return TRUE;
2023              // prefix too long

2024              case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
2025              // prefix contains invalid characters

2026              case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ):
2027                  logMsg( 'Invalid prefix: ' . $prefix );
2028                  return FALSE;
2029          }
2030          
2031          if( $underscore AND substr( $prefix, -1 ) != '_' ) {
2032              $prefix .= '_';
2033          }
2034          
2035          // prefix valid

2036          logMsg( 'Set prefix: ' . $prefix );
2037          $this->objectPrefix = $prefix;
2038          return TRUE;
2039      }
2040      
2041      /**

2042      * Returns an object name with the current prefix prepended.

2043      *

2044      * @param string    $name Name

2045      * @return string    Prefixed name

2046      *

2047      * @access private

2048      */
2049  	function prefix( $name = '' ) {
2050          // if prefix is set

2051          if( !empty( $this->objectPrefix ) ) {
2052              // Prepend the object prefix to the table name

2053              // prepend after quote if used

2054              return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
2055          }
2056          
2057          // No prefix set. Use name provided.

2058          return $name;
2059      }
2060      
2061      /**

2062      * Checks if element references a specific platform

2063      *

2064      * @param string $platform Requested platform

2065      * @returns boolean TRUE if platform check succeeds

2066      *

2067      * @access private

2068      */
2069  	function supportedPlatform( $platform = NULL ) {
2070          $regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/';
2071          
2072          if( !isset( $platform ) OR preg_match( $regex, $platform ) ) {
2073              logMsg( "Platform $platform is supported" );
2074              return TRUE;
2075          } else {
2076              logMsg( "Platform $platform is NOT supported" );
2077              return FALSE;
2078          }
2079      }
2080      
2081      /**

2082      * Clears the array of generated SQL.

2083      *

2084      * @access private

2085      */
2086  	function clearSQL() {
2087          $this->sqlArray = array();
2088      }
2089      
2090      /**

2091      * Adds SQL into the SQL array.

2092      *

2093      * @param mixed $sql SQL to Add

2094      * @return boolean TRUE if successful, else FALSE.

2095      *

2096      * @access private

2097      */    
2098  	function addSQL( $sql = NULL ) {
2099          if( is_array( $sql ) ) {
2100              foreach( $sql as $line ) {
2101                  $this->addSQL( $line );
2102              }
2103              
2104              return TRUE;
2105          }
2106          
2107          if( is_string( $sql ) ) {
2108              $this->sqlArray[] = $sql;
2109              
2110              // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.

2111              if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
2112                  $saved = $this->db->debug;
2113                  $this->db->debug = $this->debug;
2114                  $ok = $this->db->Execute( $sql );
2115                  $this->db->debug = $saved;
2116                  
2117                  if( !$ok ) {
2118                      if( $this->debug ) {
2119                          ADOConnection::outp( $this->db->ErrorMsg() );
2120                      }
2121                      
2122                      $this->success = 1;
2123                  }
2124              }
2125              
2126              return TRUE;
2127          }
2128          
2129          return FALSE;
2130      }
2131      
2132      /**

2133      * Gets the SQL array in the specified format.

2134      *

2135      * @param string $format Format

2136      * @return mixed SQL

2137      *    

2138      * @access private

2139      */
2140  	function getSQL( $format = NULL, $sqlArray = NULL ) {
2141          if( !is_array( $sqlArray ) ) {
2142              $sqlArray = $this->sqlArray;
2143          }
2144          
2145          if( !is_array( $sqlArray ) ) {
2146              return FALSE;
2147          }
2148          
2149          switch( strtolower( $format ) ) {
2150              case 'string':
2151              case 'text':
2152                  return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
2153              case'html':
2154                  return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
2155          }
2156          
2157          return $this->sqlArray;
2158      }
2159      
2160      /**

2161      * Destroys an adoSchema object.

2162      *

2163      * Call this method to clean up after an adoSchema object that is no longer in use.

2164      * @deprecated adoSchema now cleans up automatically.

2165      */
2166  	function Destroy() {
2167          @set_magic_quotes_runtime( $this->mgq );
2168          unset( $this );
2169      }
2170  }
2171  
2172  /**

2173  * Message logging function

2174  *

2175  * @access private

2176  */
2177  function logMsg( $msg, $title = NULL, $force = FALSE ) {
2178      if( XMLS_DEBUG or $force ) {
2179          echo '<pre>';
2180          
2181          if( isset( $title ) ) {
2182              echo '<h3>' . htmlentities( $title ) . '</h3>';
2183          }
2184          
2185          if( is_object( $this ) ) {
2186              echo '[' . get_class( $this ) . '] ';
2187          }
2188          
2189          print_r( $msg );
2190          
2191          echo '</pre>';
2192      }
2193  }
2194  ?>

title

Description

title

Description

title

Description

title

title

Body