]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/adodb-xmlschema03.inc.php
Upgrade adodb
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / adodb-xmlschema03.inc.php
1 <?php
2 // Copyright (c) 2004-2005 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: jlim $
14  * @author Richard Tango-Lowy & Dan Cech
15  * @version $Revision: 1.62 $
16  *
17  * @package axmls
18  * @tutorial getting_started.pkg
19  */
20  
21 function _file_get_contents($file) 
22 {
23         if (function_exists('file_get_contents')) return file_get_contents($file);
24         
25         $f = fopen($file,'r');
26         if (!$f) return '';
27         $t = '';
28         
29         while ($s = fread($f,100000)) $t .= $s;
30         fclose($f);
31         return $t;
32 }
33
34
35 /**
36 * Debug on or off
37 */
38 if( !defined( 'XMLS_DEBUG' ) ) {
39         define( 'XMLS_DEBUG', FALSE );
40 }
41
42 /**
43 * Default prefix key
44 */
45 if( !defined( 'XMLS_PREFIX' ) ) {
46         define( 'XMLS_PREFIX', '%%P' );
47 }
48
49 /**
50 * Maximum length allowed for object prefix
51 */
52 if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
53         define( 'XMLS_PREFIX_MAXLEN', 10 );
54 }
55
56 /**
57 * Execute SQL inline as it is generated
58 */
59 if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
60         define( 'XMLS_EXECUTE_INLINE', FALSE );
61 }
62
63 /**
64 * Continue SQL Execution if an error occurs?
65 */
66 if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
67         define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
68 }
69
70 /**
71 * Current Schema Version
72 */
73 if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
74         define( 'XMLS_SCHEMA_VERSION', '0.3' );
75 }
76
77 /**
78 * Default Schema Version.  Used for Schemas without an explicit version set.
79 */
80 if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
81         define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
82 }
83
84 /**
85 * How to handle data rows that already exist in a database during and upgrade.
86 * Options are INSERT (attempts to insert duplicate rows), UPDATE (updates existing
87 * rows) and IGNORE (ignores existing rows).
88 */
89 if( !defined( 'XMLS_MODE_INSERT' ) ) {
90         define( 'XMLS_MODE_INSERT', 0 );
91 }
92 if( !defined( 'XMLS_MODE_UPDATE' ) ) {
93         define( 'XMLS_MODE_UPDATE', 1 );
94 }
95 if( !defined( 'XMLS_MODE_IGNORE' ) ) {
96         define( 'XMLS_MODE_IGNORE', 2 );
97 }
98 if( !defined( 'XMLS_EXISTING_DATA' ) ) {
99         define( 'XMLS_EXISTING_DATA', XMLS_MODE_INSERT );
100 }
101
102 /**
103 * Default Schema Version.  Used for Schemas without an explicit version set.
104 */
105 if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
106         define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
107 }
108
109 /**
110 * Include the main ADODB library
111 */
112 if( !defined( '_ADODB_LAYER' ) ) {
113         require( 'adodb.inc.php' );
114         require( 'adodb-datadict.inc.php' );
115 }
116
117 /**
118 * Abstract DB Object. This class provides basic methods for database objects, such
119 * as tables and indexes.
120 *
121 * @package axmls
122 * @access private
123 */
124 class dbObject {
125         
126         /**
127         * var object Parent
128         */
129         var $parent;
130         
131         /**
132         * var string current element
133         */
134         var $currentElement;
135         
136         /**
137         * NOP
138         */
139         function dbObject( &$parent, $attributes = NULL ) {
140                 $this->parent = $parent;
141         }
142         
143         /**
144         * XML Callback to process start elements
145         *
146         * @access private
147         */
148         function _tag_open( &$parser, $tag, $attributes ) {
149                 
150         }
151         
152         /**
153         * XML Callback to process CDATA elements
154         *
155         * @access private
156         */
157         function _tag_cdata( &$parser, $cdata ) {
158                 
159         }
160         
161         /**
162         * XML Callback to process end elements
163         *
164         * @access private
165         */
166         function _tag_close( &$parser, $tag ) {
167                 
168         }
169         
170         function create(&$xmls) {
171                 return array();
172         }
173         
174         /**
175         * Destroys the object
176         */
177         function destroy() {
178                 unset( $this );
179         }
180         
181         /**
182         * Checks whether the specified RDBMS is supported by the current
183         * database object or its ranking ancestor.
184         *
185         * @param string $platform RDBMS platform name (from ADODB platform list).
186         * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
187         */
188         function supportedPlatform( $platform = NULL ) {
189                 return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
190         }
191         
192         /**
193         * Returns the prefix set by the ranking ancestor of the database object.
194         *
195         * @param string $name Prefix string.
196         * @return string Prefix.
197         */
198         function prefix( $name = '' ) {
199                 return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
200         }
201         
202         /**
203         * Extracts a field ID from the specified field.
204         *
205         * @param string $field Field.
206         * @return string Field ID.
207         */
208         function FieldID( $field ) {
209                 return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
210         }
211 }
212
213 /**
214 * Creates a table object in ADOdb's datadict format
215 *
216 * This class stores information about a database table. As charactaristics
217 * of the table are loaded from the external source, methods and properties
218 * of this class are used to build up the table description in ADOdb's
219 * datadict format.
220 *
221 * @package axmls
222 * @access private
223 */
224 class dbTable extends dbObject {
225         
226         /**
227         * @var string Table name
228         */
229         var $name;
230         
231         /**
232         * @var array Field specifier: Meta-information about each field
233         */
234         var $fields = array();
235         
236         /**
237         * @var array List of table indexes.
238         */
239         var $indexes = array();
240         
241         /**
242         * @var array Table options: Table-level options
243         */
244         var $opts = array();
245         
246         /**
247         * @var string Field index: Keeps track of which field is currently being processed
248         */
249         var $current_field;
250         
251         /**
252         * @var boolean Mark table for destruction
253         * @access private
254         */
255         var $drop_table;
256         
257         /**
258         * @var boolean Mark field for destruction (not yet implemented)
259         * @access private
260         */
261         var $drop_field = array();
262         
263         /**
264         * @var array Platform-specific options
265         * @access private
266         */
267         var $currentPlatform = true;
268         
269         
270         /**
271         * Iniitializes a new table object.
272         *
273         * @param string $prefix DB Object prefix
274         * @param array $attributes Array of table attributes.
275         */
276         function dbTable( &$parent, $attributes = NULL ) {
277                 $this->parent = $parent;
278                 $this->name = $this->prefix($attributes['NAME']);
279         }
280         
281         /**
282         * XML Callback to process start elements. Elements currently 
283         * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT. 
284         *
285         * @access private
286         */
287         function _tag_open( &$parser, $tag, $attributes ) {
288                 $this->currentElement = strtoupper( $tag );
289                 
290                 switch( $this->currentElement ) {
291                         case 'INDEX':
292                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
293                                         xml_set_object( $parser, $this->addIndex( $attributes ) );
294                                 }
295                                 break;
296                         case 'DATA':
297                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
298                                         xml_set_object( $parser, $this->addData( $attributes ) );
299                                 }
300                                 break;
301                         case 'DROP':
302                                 $this->drop();
303                                 break;
304                         case 'FIELD':
305                                 // Add a field
306                                 $fieldName = $attributes['NAME'];
307                                 $fieldType = $attributes['TYPE'];
308                                 $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
309                                 $fieldOpts = !empty( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
310                                 
311                                 $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
312                                 break;
313                         case 'KEY':
314                         case 'NOTNULL':
315                         case 'AUTOINCREMENT':
316                         case 'DEFDATE':
317                         case 'DEFTIMESTAMP':
318                         case 'UNSIGNED':
319                                 // Add a field option
320                                 $this->addFieldOpt( $this->current_field, $this->currentElement );
321                                 break;
322                         case 'DEFAULT':
323                                 // Add a field option to the table object
324                                 
325                                 // Work around ADOdb datadict issue that misinterprets empty strings.
326                                 if( $attributes['VALUE'] == '' ) {
327                                         $attributes['VALUE'] = " '' ";
328                                 }
329                                 
330                                 $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
331                                 break;
332                         case 'OPT':
333                         case 'CONSTRAINT':
334                                 // Accept platform-specific options
335                                 $this->currentPlatform = ( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) );
336                                 break;
337                         default:
338                                 // print_r( array( $tag, $attributes ) );
339                 }
340         }
341         
342         /**
343         * XML Callback to process CDATA elements
344         *
345         * @access private
346         */
347         function _tag_cdata( &$parser, $cdata ) {
348                 switch( $this->currentElement ) {
349                         // Table/field constraint
350                         case 'CONSTRAINT':
351                                 if( isset( $this->current_field ) ) {
352                                         $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
353                                 } else {
354                                         $this->addTableOpt( $cdata );
355                                 }
356                                 break;
357                         // Table/field option
358                         case 'OPT':
359                                 if( isset( $this->current_field ) ) {
360                                         $this->addFieldOpt( $this->current_field, $cdata );
361                                 } else {
362                                 $this->addTableOpt( $cdata );
363                                 }
364                                 break;
365                         default:
366                                 
367                 }
368         }
369         
370         /**
371         * XML Callback to process end elements
372         *
373         * @access private
374         */
375         function _tag_close( &$parser, $tag ) {
376                 $this->currentElement = '';
377                 
378                 switch( strtoupper( $tag ) ) {
379                         case 'TABLE':
380                                 $this->parent->addSQL( $this->create( $this->parent ) );
381                                 xml_set_object( $parser, $this->parent );
382                                 $this->destroy();
383                                 break;
384                         case 'FIELD':
385                                 unset($this->current_field);
386                                 break;
387                         case 'OPT':
388                         case 'CONSTRAINT':
389                                 $this->currentPlatform = true;
390                                 break;
391                         default:
392
393                 }
394         }
395         
396         /**
397         * Adds an index to a table object
398         *
399         * @param array $attributes Index attributes
400         * @return object dbIndex object
401         */
402         function addIndex( $attributes ) {
403                 $name = strtoupper( $attributes['NAME'] );
404                 $this->indexes[$name] = new dbIndex( $this, $attributes );
405                 return $this->indexes[$name];
406         }
407         
408         /**
409         * Adds data to a table object
410         *
411         * @param array $attributes Data attributes
412         * @return object dbData object
413         */
414         function addData( $attributes ) {
415                 if( !isset( $this->data ) ) {
416                         $this->data = new dbData( $this, $attributes );
417                 }
418                 return $this->data;
419         }
420         
421         /**
422         * Adds a field to a table object
423         *
424         * $name is the name of the table to which the field should be added. 
425         * $type is an ADODB datadict field type. The following field types
426         * are supported as of ADODB 3.40:
427         *       - C:  varchar
428         *       - X:  CLOB (character large object) or largest varchar size
429         *          if CLOB is not supported
430         *       - C2: Multibyte varchar
431         *       - X2: Multibyte CLOB
432         *       - B:  BLOB (binary large object)
433         *       - D:  Date (some databases do not support this, and we return a datetime type)
434         *       - T:  Datetime or Timestamp
435         *       - L:  Integer field suitable for storing booleans (0 or 1)
436         *       - I:  Integer (mapped to I4)
437         *       - I1: 1-byte integer
438         *       - I2: 2-byte integer
439         *       - I4: 4-byte integer
440         *       - I8: 8-byte integer
441         *       - F:  Floating point number
442         *       - N:  Numeric or decimal number
443         *
444         * @param string $name Name of the table to which the field will be added.
445         * @param string $type   ADODB datadict field type.
446         * @param string $size   Field size
447         * @param array $opts    Field options array
448         * @return array Field specifier array
449         */
450         function addField( $name, $type, $size = NULL, $opts = NULL ) {
451                 $field_id = $this->FieldID( $name );
452                 
453                 // Set the field index so we know where we are
454                 $this->current_field = $field_id;
455                 
456                 // Set the field name (required)
457                 $this->fields[$field_id]['NAME'] = $name;
458                 
459                 // Set the field type (required)
460                 $this->fields[$field_id]['TYPE'] = $type;
461                 
462                 // Set the field size (optional)
463                 if( isset( $size ) ) {
464                         $this->fields[$field_id]['SIZE'] = $size;
465                 }
466                 
467                 // Set the field options
468                 if( isset( $opts ) ) {
469                         $this->fields[$field_id]['OPTS'] = array($opts);
470                 } else {
471                         $this->fields[$field_id]['OPTS'] = array();
472                 }
473         }
474         
475         /**
476         * Adds a field option to the current field specifier
477         *
478         * This method adds a field option allowed by the ADOdb datadict 
479         * and appends it to the given field.
480         *
481         * @param string $field  Field name
482         * @param string $opt ADOdb field option
483         * @param mixed $value Field option value
484         * @return array Field specifier array
485         */
486         function addFieldOpt( $field, $opt, $value = NULL ) {
487                 if( $this->currentPlatform ) {
488                 if( !isset( $value ) ) {
489                         $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
490                 // Add the option and value
491                 } else {
492                         $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
493                 }
494         }
495         }
496         
497         /**
498         * Adds an option to the table
499         *
500         * This method takes a comma-separated list of table-level options
501         * and appends them to the table object.
502         *
503         * @param string $opt Table option
504         * @return array Options
505         */
506         function addTableOpt( $opt ) {
507                 if(isset($this->currentPlatform)) {
508                         $this->opts[$this->parent->db->databaseType] = $opt;
509                 }
510                 return $this->opts;
511         }
512
513         
514         /**
515         * Generates the SQL that will create the table in the database
516         *
517         * @param object $xmls adoSchema object
518         * @return array Array containing table creation SQL
519         */
520         function create( &$xmls ) {
521                 $sql = array();
522                 
523                 // drop any existing indexes
524                 if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
525                         foreach( $legacy_indexes as $index => $index_details ) {
526                                 $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
527                         }
528                 }
529                 
530                 // remove fields to be dropped from table object
531                 foreach( $this->drop_field as $field ) {
532                         unset( $this->fields[$field] );
533                 }
534                 
535                 // if table exists
536                 if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
537                         // drop table
538                         if( $this->drop_table ) {
539                                 $sql[] = $xmls->dict->DropTableSQL( $this->name );
540                                 
541                                 return $sql;
542                         }
543                         
544                         // drop any existing fields not in schema
545                         foreach( $legacy_fields as $field_id => $field ) {
546                                 if( !isset( $this->fields[$field_id] ) ) {
547                                         $sql[] = $xmls->dict->DropColumnSQL( $this->name, $field->name );
548                                 }
549                         }
550                 // if table doesn't exist
551                 } else {
552                         if( $this->drop_table ) {
553                                 return $sql;
554                         }
555                         
556                         $legacy_fields = array();
557                 }
558                 
559                 // Loop through the field specifier array, building the associative array for the field options
560                 $fldarray = array();
561                 
562                 foreach( $this->fields as $field_id => $finfo ) {
563                         // Set an empty size if it isn't supplied
564                         if( !isset( $finfo['SIZE'] ) ) {
565                                 $finfo['SIZE'] = '';
566                         }
567                         
568                         // Initialize the field array with the type and size
569                         $fldarray[$field_id] = array(
570                                 'NAME' => $finfo['NAME'],
571                                 'TYPE' => $finfo['TYPE'],
572                                 'SIZE' => $finfo['SIZE']
573                         );
574                         
575                         // Loop through the options array and add the field options. 
576                         if( isset( $finfo['OPTS'] ) ) {
577                                 foreach( $finfo['OPTS'] as $opt ) {
578                                         // Option has an argument.
579                                         if( is_array( $opt ) ) {
580                                                 $key = key( $opt );
581                                                 $value = $opt[key( $opt )];
582                                                 @$fldarray[$field_id][$key] .= $value;
583                                         // Option doesn't have arguments
584                                         } else {
585                                                 $fldarray[$field_id][$opt] = $opt;
586                                         }
587                                 }
588                         }
589                 }
590                 
591                 if( empty( $legacy_fields ) ) {
592                         // Create the new table
593                         $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
594                         logMsg( end( $sql ), 'Generated CreateTableSQL' );
595                 } else {
596                         // Upgrade an existing table
597                         logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
598                         switch( $xmls->upgrade ) {
599                                 // Use ChangeTableSQL
600                                 case 'ALTER':
601                                         logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
602                                         $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
603                                         break;
604                                 case 'REPLACE':
605                                         logMsg( 'Doing upgrade REPLACE (testing)' );
606                                         $sql[] = $xmls->dict->DropTableSQL( $this->name );
607                                         $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
608                                         break;
609                                 // ignore table
610                                 default:
611                                         return array();
612                         }
613                 }
614                 
615                 foreach( $this->indexes as $index ) {
616                         $sql[] = $index->create( $xmls );
617                 }
618                 
619                 if( isset( $this->data ) ) {
620                         $sql[] = $this->data->create( $xmls );
621                 }
622                 
623                 return $sql;
624         }
625         
626         /**
627         * Marks a field or table for destruction
628         */
629         function drop() {
630                 if( isset( $this->current_field ) ) {
631                         // Drop the current field
632                         logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
633                         // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
634                         $this->drop_field[$this->current_field] = $this->current_field;
635                 } else {
636                         // Drop the current table
637                         logMsg( "Dropping table '{$this->name}'" );
638                         // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
639                         $this->drop_table = TRUE;
640                 }
641         }
642 }
643
644 /**
645 * Creates an index object in ADOdb's datadict format
646 *
647 * This class stores information about a database index. As charactaristics
648 * of the index are loaded from the external source, methods and properties
649 * of this class are used to build up the index description in ADOdb's
650 * datadict format.
651 *
652 * @package axmls
653 * @access private
654 */
655 class dbIndex extends dbObject {
656         
657         /**
658         * @var string   Index name
659         */
660         var $name;
661         
662         /**
663         * @var array    Index options: Index-level options
664         */
665         var $opts = array();
666         
667         /**
668         * @var array    Indexed fields: Table columns included in this index
669         */
670         var $columns = array();
671         
672         /**
673         * @var boolean Mark index for destruction
674         * @access private
675         */
676         var $drop = FALSE;
677         
678         /**
679         * Initializes the new dbIndex object.
680         *
681         * @param object $parent Parent object
682         * @param array $attributes Attributes
683         *
684         * @internal
685         */
686         function dbIndex( &$parent, $attributes = NULL ) {
687                 $this->parent = $parent;
688                 
689                 $this->name = $this->prefix ($attributes['NAME']);
690         }
691         
692         /**
693         * XML Callback to process start elements
694         *
695         * Processes XML opening tags. 
696         * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. 
697         *
698         * @access private
699         */
700         function _tag_open( &$parser, $tag, $attributes ) {
701                 $this->currentElement = strtoupper( $tag );
702                 
703                 switch( $this->currentElement ) {
704                         case 'DROP':
705                                 $this->drop();
706                                 break;
707                         case 'CLUSTERED':
708                         case 'BITMAP':
709                         case 'UNIQUE':
710                         case 'FULLTEXT':
711                         case 'HASH':
712                                 // Add index Option
713                                 $this->addIndexOpt( $this->currentElement );
714                                 break;
715                         default:
716                                 // print_r( array( $tag, $attributes ) );
717                 }
718         }
719         
720         /**
721         * XML Callback to process CDATA elements
722         *
723         * Processes XML cdata.
724         *
725         * @access private
726         */
727         function _tag_cdata( &$parser, $cdata ) {
728                 switch( $this->currentElement ) {
729                         // Index field name
730                         case 'COL':
731                                 $this->addField( $cdata );
732                                 break;
733                         default:
734                                 
735                 }
736         }
737         
738         /**
739         * XML Callback to process end elements
740         *
741         * @access private
742         */
743         function _tag_close( &$parser, $tag ) {
744                 $this->currentElement = '';
745                 
746                 switch( strtoupper( $tag ) ) {
747                         case 'INDEX':
748                                 xml_set_object( $parser, $this->parent );
749                                 break;
750                 }
751         }
752         
753         /**
754         * Adds a field to the index
755         *
756         * @param string $name Field name
757         * @return string Field list
758         */
759         function addField( $name ) {
760                 $this->columns[$this->FieldID( $name )] = $name;
761                 
762                 // Return the field list
763                 return $this->columns;
764         }
765         
766         /**
767         * Adds options to the index
768         *
769         * @param string $opt Comma-separated list of index options.
770         * @return string Option list
771         */
772         function addIndexOpt( $opt ) {
773                 $this->opts[] = $opt;
774                 
775                 // Return the options list
776                 return $this->opts;
777         }
778         
779         /**
780         * Generates the SQL that will create the index in the database
781         *
782         * @param object $xmls adoSchema object
783         * @return array Array containing index creation SQL
784         */
785         function create( &$xmls ) {
786                 if( $this->drop ) {
787                         return NULL;
788                 }
789                 
790                 // eliminate any columns that aren't in the table
791                 foreach( $this->columns as $id => $col ) {
792                         if( !isset( $this->parent->fields[$id] ) ) {
793                                 unset( $this->columns[$id] );
794                         }
795                 }
796                 
797                 return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts );
798         }
799         
800         /**
801         * Marks an index for destruction
802         */
803         function drop() {
804                 $this->drop = TRUE;
805         }
806 }
807
808 /**
809 * Creates a data object in ADOdb's datadict format
810 *
811 * This class stores information about table data, and is called
812 * when we need to load field data into a table.
813 *
814 * @package axmls
815 * @access private
816 */
817 class dbData extends dbObject {
818         
819         var $data = array();
820         
821         var $row;
822         
823         /**
824         * Initializes the new dbData object.
825         *
826         * @param object $parent Parent object
827         * @param array $attributes Attributes
828         *
829         * @internal
830         */
831         function dbData( &$parent, $attributes = NULL ) {
832                 $this->parent = $parent;
833         }
834         
835         /**
836         * XML Callback to process start elements
837         *
838         * Processes XML opening tags. 
839         * Elements currently processed are: ROW and F (field). 
840         *
841         * @access private
842         */
843         function _tag_open( &$parser, $tag, $attributes ) {
844                 $this->currentElement = strtoupper( $tag );
845                 
846                 switch( $this->currentElement ) {
847                         case 'ROW':
848                                 $this->row = count( $this->data );
849                                 $this->data[$this->row] = array();
850                                 break;
851                         case 'F':
852                                 $this->addField($attributes);
853                         default:
854                                 // print_r( array( $tag, $attributes ) );
855                 }
856         }
857         
858         /**
859         * XML Callback to process CDATA elements
860         *
861         * Processes XML cdata.
862         *
863         * @access private
864         */
865         function _tag_cdata( &$parser, $cdata ) {
866                 switch( $this->currentElement ) {
867                         // Index field name
868                         case 'F':
869                                 $this->addData( $cdata );
870                                 break;
871                         default:
872                                 
873                 }
874         }
875         
876         /**
877         * XML Callback to process end elements
878         *
879         * @access private
880         */
881         function _tag_close( &$parser, $tag ) {
882                 $this->currentElement = '';
883                 
884                 switch( strtoupper( $tag ) ) {
885                         case 'DATA':
886                                 xml_set_object( $parser, $this->parent );
887                                 break;
888                 }
889         }
890         
891         /**
892         * Adds a field to the insert
893         *
894         * @param string $name Field name
895         * @return string Field list
896         */
897         function addField( $attributes ) {
898                 // check we're in a valid row
899                 if( !isset( $this->row ) || !isset( $this->data[$this->row] ) ) {
900                         return;
901                 }
902                 
903                 // Set the field index so we know where we are
904                 if( isset( $attributes['NAME'] ) ) {
905                         $this->current_field = $this->FieldID( $attributes['NAME'] );
906                 } else {
907                         $this->current_field = count( $this->data[$this->row] );
908                 }
909                 
910                 // initialise data
911                 if( !isset( $this->data[$this->row][$this->current_field] ) ) {
912                         $this->data[$this->row][$this->current_field] = '';
913                 }
914         }
915         
916         /**
917         * Adds options to the index
918         *
919         * @param string $opt Comma-separated list of index options.
920         * @return string Option list
921         */
922         function addData( $cdata ) {
923                 // check we're in a valid field
924                 if ( isset( $this->data[$this->row][$this->current_field] ) ) {
925                         // add data to field
926                         $this->data[$this->row][$this->current_field] .= $cdata;
927                 }
928         }
929         
930         /**
931         * Generates the SQL that will add/update the data in the database
932         *
933         * @param object $xmls adoSchema object
934         * @return array Array containing index creation SQL
935         */
936         function create( &$xmls ) {
937                 $table = $xmls->dict->TableName($this->parent->name);
938                 $table_field_count = count($this->parent->fields);
939                 $tables = $xmls->db->MetaTables(); 
940                 $sql = array();
941                 
942                 $ukeys = $xmls->db->MetaPrimaryKeys( $table );
943                 if( !empty( $this->parent->indexes ) and !empty( $ukeys ) ) {
944                         foreach( $this->parent->indexes as $indexObj ) {
945                                 if( !in_array( $indexObj->name, $ukeys ) ) $ukeys[] = $indexObj->name;
946                         }
947                 }
948                 
949                 // eliminate any columns that aren't in the table
950                 foreach( $this->data as $row ) {
951                         $table_fields = $this->parent->fields;
952                         $fields = array();
953                         $rawfields = array(); // Need to keep some of the unprocessed data on hand.
954                         
955                         foreach( $row as $field_id => $field_data ) {
956                                 if( !array_key_exists( $field_id, $table_fields ) ) {
957                                         if( is_numeric( $field_id ) ) {
958                                                 $field_id = reset( array_keys( $table_fields ) );
959                                         } else {
960                                                 continue;
961                                         }
962                                 }
963                                 
964                                 $name = $table_fields[$field_id]['NAME'];
965                                 
966                                 switch( $table_fields[$field_id]['TYPE'] ) {
967                                         case 'I':
968                                         case 'I1':
969                                         case 'I2':
970                                         case 'I4':
971                                         case 'I8':
972                                                 $fields[$name] = intval($field_data);
973                                                 break;
974                                         case 'C':
975                                         case 'C2':
976                                         case 'X':
977                                         case 'X2':
978                                         default:
979                                                 $fields[$name] = $xmls->db->qstr( $field_data );
980                                                 $rawfields[$name] = $field_data;
981                                 }
982                                 
983                                 unset($table_fields[$field_id]);
984                                 
985                         }
986                         
987                         // check that at least 1 column is specified
988                         if( empty( $fields ) ) {
989                                 continue;
990                         }
991                         
992                         // check that no required columns are missing
993                         if( count( $fields ) < $table_field_count ) {
994                                 foreach( $table_fields as $field ) {
995                                         if( isset( $field['OPTS'] ) and ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) {
996                                                         continue(2);
997                                                 }
998                                 }
999                         }
1000                         
1001                         // The rest of this method deals with updating existing data records.
1002                         
1003                         if( !in_array( $table, $tables ) or ( $mode = $xmls->existingData() ) == XMLS_MODE_INSERT ) {
1004                                 // Table doesn't yet exist, so it's safe to insert.
1005                                 logMsg( "$table doesn't exist, inserting or mode is INSERT" );
1006                         $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
1007                                 continue;
1008                 }
1009                 
1010                         // Prepare to test for potential violations. Get primary keys and unique indexes
1011                         $mfields = array_merge( $fields, $rawfields );
1012                         $keyFields = array_intersect( $ukeys, array_keys( $mfields ) );
1013                         
1014                         if( empty( $ukeys ) or count( $keyFields ) == 0 ) {
1015                                 // No unique keys in schema, so safe to insert
1016                                 logMsg( "Either schema or data has no unique keys, so safe to insert" );
1017                                 $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')';
1018                                 continue;
1019                         }
1020                         
1021                         // Select record containing matching unique keys.
1022                         $where = '';
1023                         foreach( $ukeys as $key ) {
1024                                 if( isset( $mfields[$key] ) and $mfields[$key] ) {
1025                                         if( $where ) $where .= ' AND ';
1026                                         $where .= $key . ' = ' . $xmls->db->qstr( $mfields[$key] );
1027                                 }
1028                         }
1029                         $records = $xmls->db->Execute( 'SELECT * FROM ' . $table . ' WHERE ' . $where );
1030                         switch( $records->RecordCount() ) {
1031                                 case 0:
1032                                         // No matching record, so safe to insert.
1033                                         logMsg( "No matching records. Inserting new row with unique data" );
1034                                         $sql[] = $xmls->db->GetInsertSQL( $records, $mfields );
1035                                         break;
1036                                 case 1:
1037                                         // Exactly one matching record, so we can update if the mode permits.
1038                                         logMsg( "One matching record..." );
1039                                         if( $mode == XMLS_MODE_UPDATE ) {
1040                                                 logMsg( "...Updating existing row from unique data" );
1041                                                 $sql[] = $xmls->db->GetUpdateSQL( $records, $mfields );
1042                                         }
1043                                         break;
1044                                 default:
1045                                         // More than one matching record; the result is ambiguous, so we must ignore the row.
1046                                         logMsg( "More than one matching record. Ignoring row." );
1047                         }
1048                 }
1049                 return $sql;
1050         }
1051 }
1052
1053 /**
1054 * Creates the SQL to execute a list of provided SQL queries
1055 *
1056 * @package axmls
1057 * @access private
1058 */
1059 class dbQuerySet extends dbObject {
1060         
1061         /**
1062         * @var array    List of SQL queries
1063         */
1064         var $queries = array();
1065         
1066         /**
1067         * @var string   String used to build of a query line by line
1068         */
1069         var $query;
1070         
1071         /**
1072         * @var string   Query prefix key
1073         */
1074         var $prefixKey = '';
1075         
1076         /**
1077         * @var boolean  Auto prefix enable (TRUE)
1078         */
1079         var $prefixMethod = 'AUTO';
1080         
1081         /**
1082         * Initializes the query set.
1083         *
1084         * @param object $parent Parent object
1085         * @param array $attributes Attributes
1086         */
1087         function dbQuerySet( &$parent, $attributes = NULL ) {
1088                 $this->parent = $parent;
1089                         
1090                 // Overrides the manual prefix key
1091                 if( isset( $attributes['KEY'] ) ) {
1092                         $this->prefixKey = $attributes['KEY'];
1093                 }
1094                 
1095                 $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
1096                 
1097                 // Enables or disables automatic prefix prepending
1098                 switch( $prefixMethod ) {
1099                         case 'AUTO':
1100                                 $this->prefixMethod = 'AUTO';
1101                                 break;
1102                         case 'MANUAL':
1103                                 $this->prefixMethod = 'MANUAL';
1104                                 break;
1105                         case 'NONE':
1106                                 $this->prefixMethod = 'NONE';
1107                                 break;
1108                 }
1109         }
1110         
1111         /**
1112         * XML Callback to process start elements. Elements currently 
1113         * processed are: QUERY. 
1114         *
1115         * @access private
1116         */
1117         function _tag_open( &$parser, $tag, $attributes ) {
1118                 $this->currentElement = strtoupper( $tag );
1119                 
1120                 switch( $this->currentElement ) {
1121                         case 'QUERY':
1122                                 // Create a new query in a SQL queryset.
1123                                 // Ignore this query set if a platform is specified and it's different than the 
1124                                 // current connection platform.
1125                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1126                                         $this->newQuery();
1127                                 } else {
1128                                         $this->discardQuery();
1129                                 }
1130                                 break;
1131                         default:
1132                                 // print_r( array( $tag, $attributes ) );
1133                 }
1134         }
1135         
1136         /**
1137         * XML Callback to process CDATA elements
1138         */
1139         function _tag_cdata( &$parser, $cdata ) {
1140                 switch( $this->currentElement ) {
1141                         // Line of queryset SQL data
1142                         case 'QUERY':
1143                                 $this->buildQuery( $cdata );
1144                                 break;
1145                         default:
1146                                 
1147                 }
1148         }
1149         
1150         /**
1151         * XML Callback to process end elements
1152         *
1153         * @access private
1154         */
1155         function _tag_close( &$parser, $tag ) {
1156                 $this->currentElement = '';
1157                 
1158                 switch( strtoupper( $tag ) ) {
1159                         case 'QUERY':
1160                                 // Add the finished query to the open query set.
1161                                 $this->addQuery();
1162                                 break;
1163                         case 'SQL':
1164                                 $this->parent->addSQL( $this->create( $this->parent ) );
1165                                 xml_set_object( $parser, $this->parent );
1166                                 $this->destroy();
1167                                 break;
1168                         default:
1169                                 
1170                 }
1171         }
1172         
1173         /**
1174         * Re-initializes the query.
1175         *
1176         * @return boolean TRUE
1177         */
1178         function newQuery() {
1179                 $this->query = '';
1180                 
1181                 return TRUE;
1182         }
1183         
1184         /**
1185         * Discards the existing query.
1186         *
1187         * @return boolean TRUE
1188         */
1189         function discardQuery() {
1190                 unset( $this->query );
1191                 
1192                 return TRUE;
1193         }
1194         
1195         /** 
1196         * Appends a line to a query that is being built line by line
1197         *
1198         * @param string $data Line of SQL data or NULL to initialize a new query
1199         * @return string SQL query string.
1200         */
1201         function buildQuery( $sql = NULL ) {
1202                 if( !isset( $this->query ) OR empty( $sql ) ) {
1203                         return FALSE;
1204                 }
1205                 
1206                 $this->query .= $sql;
1207                 
1208                 return $this->query;
1209         }
1210         
1211         /**
1212         * Adds a completed query to the query list
1213         *
1214         * @return string        SQL of added query
1215         */
1216         function addQuery() {
1217                 if( !isset( $this->query ) ) {
1218                         return FALSE;
1219                 }
1220                 
1221                 $this->queries[] = $return = trim($this->query);
1222                 
1223                 unset( $this->query );
1224                 
1225                 return $return;
1226         }
1227         
1228         /**
1229         * Creates and returns the current query set
1230         *
1231         * @param object $xmls adoSchema object
1232         * @return array Query set
1233         */
1234         function create( &$xmls ) {
1235                 foreach( $this->queries as $id => $query ) {
1236                         switch( $this->prefixMethod ) {
1237                                 case 'AUTO':
1238                                         // Enable auto prefix replacement
1239                                         
1240                                         // Process object prefix.
1241                                         // Evaluate SQL statements to prepend prefix to objects
1242                                         $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1243                                         $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1244                                         $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix );
1245                                         
1246                                         // SELECT statements aren't working yet
1247                                         #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
1248                                         
1249                                 case 'MANUAL':
1250                                         // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
1251                                         // If prefixKey is not set, we use the default constant XMLS_PREFIX
1252                                         if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) {
1253                                                 // Enable prefix override
1254                                                 $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query );
1255                                         } else {
1256                                                 // Use default replacement
1257                                                 $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query );
1258                                         }
1259                         }
1260                         
1261                         $this->queries[$id] = trim( $query );
1262                 }
1263                 
1264                 // Return the query set array
1265                 return $this->queries;
1266         }
1267         
1268         /**
1269         * Rebuilds the query with the prefix attached to any objects
1270         *
1271         * @param string $regex Regex used to add prefix
1272         * @param string $query SQL query string
1273         * @param string $prefix Prefix to be appended to tables, indices, etc.
1274         * @return string Prefixed SQL query string.
1275         */
1276         function prefixQuery( $regex, $query, $prefix = NULL ) {
1277                 if( !isset( $prefix ) ) {
1278                         return $query;
1279                 }
1280                 
1281                 if( preg_match( $regex, $query, $match ) ) {
1282                         $preamble = $match[1];
1283                         $postamble = $match[5];
1284                         $objectList = explode( ',', $match[3] );
1285                         // $prefix = $prefix . '_';
1286                         
1287                         $prefixedList = '';
1288                         
1289                         foreach( $objectList as $object ) {
1290                                 if( $prefixedList !== '' ) {
1291                                         $prefixedList .= ', ';
1292                                 }
1293                                 
1294                                 $prefixedList .= $prefix . trim( $object );
1295                         }
1296                         
1297                         $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
1298                 }
1299                 
1300                 return $query;
1301         }
1302 }
1303
1304 /**
1305 * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
1306
1307 * This class is used to load and parse the XML file, to create an array of SQL statements
1308 * that can be used to build a database, and to build the database using the SQL array.
1309 *
1310 * @tutorial getting_started.pkg
1311 *
1312 * @author Richard Tango-Lowy & Dan Cech
1313 * @version $Revision: 1.62 $
1314 *
1315 * @package axmls
1316 */
1317 class adoSchema {
1318         
1319         /**
1320         * @var array    Array containing SQL queries to generate all objects
1321         * @access private
1322         */
1323         var $sqlArray;
1324         
1325         /**
1326         * @var object   ADOdb connection object
1327         * @access private
1328         */
1329         var $db;
1330         
1331         /**
1332         * @var object   ADOdb Data Dictionary
1333         * @access private
1334         */
1335         var $dict;
1336         
1337         /**
1338         * @var string Current XML element
1339         * @access private
1340         */
1341         var $currentElement = '';
1342         
1343         /**
1344         * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
1345         * @access private
1346         */
1347         var $upgrade = '';
1348         
1349         /**
1350         * @var string Optional object prefix
1351         * @access private
1352         */
1353         var $objectPrefix = '';
1354         
1355         /**
1356         * @var long     Original Magic Quotes Runtime value
1357         * @access private
1358         */
1359         var $mgq;
1360         
1361         /**
1362         * @var long     System debug
1363         * @access private
1364         */
1365         var $debug;
1366         
1367         /**
1368         * @var string Regular expression to find schema version
1369         * @access private
1370         */
1371         var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
1372         
1373         /**
1374         * @var string Current schema version
1375         * @access private
1376         */
1377         var $schemaVersion;
1378         
1379         /**
1380         * @var int      Success of last Schema execution
1381         */
1382         var $success;
1383         
1384         /**
1385         * @var bool     Execute SQL inline as it is generated
1386         */
1387         var $executeInline;
1388         
1389         /**
1390         * @var bool     Continue SQL execution if errors occur
1391         */
1392         var $continueOnError;
1393         
1394         /**
1395         * @var int      How to handle existing data rows (insert, update, or ignore)
1396         */
1397         var $existingData;
1398         
1399         /**
1400         * Creates an adoSchema object
1401         *
1402         * Creating an adoSchema object is the first step in processing an XML schema.
1403         * The only parameter is an ADOdb database connection object, which must already
1404         * have been created.
1405         *
1406         * @param object $db ADOdb database connection object.
1407         */
1408         function adoSchema( $db ) {
1409                 // Initialize the environment
1410                 $this->mgq = get_magic_quotes_runtime();
1411                 #set_magic_quotes_runtime(0);
1412                 ini_set("magic_quotes_runtime", 0);
1413                 
1414                 $this->db = $db;
1415                 $this->debug = $this->db->debug;
1416                 $this->dict = NewDataDictionary( $this->db );
1417                 $this->sqlArray = array();
1418                 $this->schemaVersion = XMLS_SCHEMA_VERSION;
1419                 $this->executeInline( XMLS_EXECUTE_INLINE );
1420                 $this->continueOnError( XMLS_CONTINUE_ON_ERROR );
1421                 $this->existingData( XMLS_EXISTING_DATA );
1422                 $this->setUpgradeMethod();
1423         }
1424         
1425         /**
1426         * Sets the method to be used for upgrading an existing database
1427         *
1428         * Use this method to specify how existing database objects should be upgraded.
1429         * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
1430         * alter each database object directly, REPLACE attempts to rebuild each object
1431         * from scratch, BEST attempts to determine the best upgrade method for each
1432         * object, and NONE disables upgrading.
1433         *
1434         * This method is not yet used by AXMLS, but exists for backward compatibility.
1435         * The ALTER method is automatically assumed when the adoSchema object is
1436         * instantiated; other upgrade methods are not currently supported.
1437         *
1438         * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
1439         * @returns string Upgrade method used
1440         */
1441         function SetUpgradeMethod( $method = '' ) {
1442                 if( !is_string( $method ) ) {
1443                         return FALSE;
1444                 }
1445                 
1446                 $method = strtoupper( $method );
1447                 
1448                 // Handle the upgrade methods
1449                 switch( $method ) {
1450                         case 'ALTER':
1451                                 $this->upgrade = $method;
1452                                 break;
1453                         case 'REPLACE':
1454                                 $this->upgrade = $method;
1455                                 break;
1456                         case 'BEST':
1457                                 $this->upgrade = 'ALTER';
1458                                 break;
1459                         case 'NONE':
1460                                 $this->upgrade = 'NONE';
1461                                 break;
1462                         default:
1463                                 // Use default if no legitimate method is passed.
1464                                 $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD;
1465                 }
1466                 
1467                 return $this->upgrade;
1468         }
1469         
1470         /**
1471         * Specifies how to handle existing data row when there is a unique key conflict.
1472         *
1473         * The existingData setting specifies how the parser should handle existing rows
1474         * when a unique key violation occurs during the insert. This can happen when inserting
1475         * data into an existing table with one or more primary keys or unique indexes.
1476         * The existingData method takes one of three options: XMLS_MODE_INSERT attempts
1477         * to always insert the data as a new row. In the event of a unique key violation,
1478         * the database will generate an error.  XMLS_MODE_UPDATE attempts to update the 
1479         * any existing rows with the new data based upon primary or unique key fields in
1480         * the schema. If the data row in the schema specifies no unique fields, the row
1481         * data will be inserted as a new row. XMLS_MODE_IGNORE specifies that any data rows
1482         * that would result in a unique key violation be ignored; no inserts or updates will
1483         * take place. For backward compatibility, the default setting is XMLS_MODE_INSERT,
1484         * but XMLS_MODE_UPDATE will generally be the most appropriate setting.
1485         *
1486         * @param int $mode XMLS_MODE_INSERT, XMLS_MODE_UPDATE, or XMLS_MODE_IGNORE
1487         * @return int current mode
1488         */
1489         function ExistingData( $mode = NULL ) {
1490                 if( is_int( $mode ) ) {
1491                         switch( $mode ) {
1492                                 case XMLS_MODE_UPDATE:
1493                                         $mode = XMLS_MODE_UPDATE;
1494                                         break;
1495                                 case XMLS_MODE_IGNORE:
1496                                         $mode = XMLS_MODE_IGNORE;
1497                                         break;
1498                                 case XMLS_MODE_INSERT:
1499                                         $mode = XMLS_MODE_INSERT;
1500                                         break;
1501                                 default:
1502                                         $mode = XMLS_EXISTING_DATA;
1503                                         break;
1504                         }
1505                         $this->existingData = $mode;
1506                 }
1507                 
1508                 return $this->existingData;
1509         }
1510         
1511         /**
1512         * Enables/disables inline SQL execution.
1513         *
1514         * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
1515         * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
1516         * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
1517         * to apply the schema to the database.
1518         *
1519         * @param bool $mode execute
1520         * @return bool current execution mode
1521         *
1522         * @see ParseSchema(), ExecuteSchema()
1523         */
1524         function ExecuteInline( $mode = NULL ) {
1525                 if( is_bool( $mode ) ) {
1526                         $this->executeInline = $mode;
1527                 }
1528                 
1529                 return $this->executeInline;
1530         }
1531         
1532         /**
1533         * Enables/disables SQL continue on error.
1534         *
1535         * Call this method to enable or disable continuation of SQL execution if an error occurs.
1536         * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
1537         * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
1538         * of the schema will continue.
1539         *
1540         * @param bool $mode execute
1541         * @return bool current continueOnError mode
1542         *
1543         * @see addSQL(), ExecuteSchema()
1544         */
1545         function ContinueOnError( $mode = NULL ) {
1546                 if( is_bool( $mode ) ) {
1547                         $this->continueOnError = $mode;
1548                 }
1549                 
1550                 return $this->continueOnError;
1551         }
1552         
1553         /**
1554         * Loads an XML schema from a file and converts it to SQL.
1555         *
1556         * Call this method to load the specified schema (see the DTD for the proper format) from
1557         * the filesystem and generate the SQL necessary to create the database
1558         * described. This method automatically converts the schema to the latest
1559         * axmls schema version.
1560         * @see ParseSchemaString()
1561         *
1562         * @param string $file Name of XML schema file.
1563         * @param bool $returnSchema Return schema rather than parsing.
1564         * @return array Array of SQL queries, ready to execute
1565         */
1566         function ParseSchema( $filename, $returnSchema = FALSE ) {
1567                 return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
1568         }
1569         
1570         /**
1571         * Loads an XML schema from a file and converts it to SQL.
1572         *
1573         * Call this method to load the specified schema directly from a file (see
1574         * the DTD for the proper format) and generate the SQL necessary to create
1575         * the database described by the schema. Use this method when you are dealing
1576         * with large schema files. Otherwise, ParseSchema() is faster.
1577         * This method does not automatically convert the schema to the latest axmls
1578         * schema version. You must convert the schema manually using either the
1579         * ConvertSchemaFile() or ConvertSchemaString() method.
1580         * @see ParseSchema()
1581         * @see ConvertSchemaFile()
1582         * @see ConvertSchemaString()
1583         *
1584         * @param string $file Name of XML schema file.
1585         * @param bool $returnSchema Return schema rather than parsing.
1586         * @return array Array of SQL queries, ready to execute.
1587         *
1588         * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
1589         * @see ParseSchema(), ParseSchemaString()
1590         */
1591         function ParseSchemaFile( $filename, $returnSchema = FALSE ) {
1592                 // Open the file
1593                 if( !($fp = fopen( $filename, 'r' )) ) {
1594                         logMsg( 'Unable to open file' );
1595                         return FALSE;
1596                 }
1597                 
1598                 // do version detection here
1599                 if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) {
1600                         logMsg( 'Invalid Schema Version' );
1601                         return FALSE;
1602                 }
1603                 
1604                 if( $returnSchema ) {
1605                         $xmlstring = '';
1606                         while( $data = fread( $fp, 4096 ) ) {
1607                                 $xmlstring .= $data . "\n";
1608                         }
1609                         return $xmlstring;
1610                 }
1611                 
1612                 $this->success = 2;
1613                 
1614                 $xmlParser = $this->create_parser();
1615                 
1616                 // Process the file
1617                 while( $data = fread( $fp, 4096 ) ) {
1618                         if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
1619                                 die( sprintf(
1620                                         "XML error: %s at line %d",
1621                                         xml_error_string( xml_get_error_code( $xmlParser) ),
1622                                         xml_get_current_line_number( $xmlParser)
1623                                 ) );
1624                         }
1625                 }
1626                 
1627                 xml_parser_free( $xmlParser );
1628                 
1629                 return $this->sqlArray;
1630         }
1631         
1632         /**
1633         * Converts an XML schema string to SQL.
1634         *
1635         * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
1636         * and generate the SQL necessary to create the database described by the schema. 
1637         * @see ParseSchema()
1638         *
1639         * @param string $xmlstring XML schema string.
1640         * @param bool $returnSchema Return schema rather than parsing.
1641         * @return array Array of SQL queries, ready to execute.
1642         */
1643         function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) {
1644                 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
1645                         logMsg( 'Empty or Invalid Schema' );
1646                         return FALSE;
1647                 }
1648                 
1649                 // do version detection here
1650                 if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) {
1651                         logMsg( 'Invalid Schema Version' );
1652                         return FALSE;
1653                 }
1654                 
1655                 if( $returnSchema ) {
1656                         return $xmlstring;
1657                 }
1658                 
1659                 $this->success = 2;
1660                 
1661                 $xmlParser = $this->create_parser();
1662                 
1663                 if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
1664                         die( sprintf(
1665                                 "XML error: %s at line %d",
1666                                 xml_error_string( xml_get_error_code( $xmlParser) ),
1667                                 xml_get_current_line_number( $xmlParser)
1668                         ) );
1669                 }
1670                 
1671                 xml_parser_free( $xmlParser );
1672                 
1673                 return $this->sqlArray;
1674         }
1675         
1676         /**
1677         * Loads an XML schema from a file and converts it to uninstallation SQL.
1678         *
1679         * Call this method to load the specified schema (see the DTD for the proper format) from
1680         * the filesystem and generate the SQL necessary to remove the database described.
1681         * @see RemoveSchemaString()
1682         *
1683         * @param string $file Name of XML schema file.
1684         * @param bool $returnSchema Return schema rather than parsing.
1685         * @return array Array of SQL queries, ready to execute
1686         */
1687         function RemoveSchema( $filename, $returnSchema = FALSE ) {
1688                 return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema );
1689         }
1690         
1691         /**
1692         * Converts an XML schema string to uninstallation SQL.
1693         *
1694         * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
1695         * and generate the SQL necessary to uninstall the database described by the schema. 
1696         * @see RemoveSchema()
1697         *
1698         * @param string $schema XML schema string.
1699         * @param bool $returnSchema Return schema rather than parsing.
1700         * @return array Array of SQL queries, ready to execute.
1701         */
1702         function RemoveSchemaString( $schema, $returnSchema = FALSE ) {
1703                 
1704                 // grab current version
1705                 if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
1706                         return FALSE;
1707                 }
1708                 
1709                 return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema );
1710         }
1711         
1712         /**
1713         * Applies the current XML schema to the database (post execution).
1714         *
1715         * Call this method to apply the current schema (generally created by calling 
1716         * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, 
1717         * and executing other SQL specified in the schema) after parsing.
1718         * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
1719         *
1720         * @param array $sqlArray Array of SQL statements that will be applied rather than
1721         *               the current schema.
1722         * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
1723         * @returns integer 0 if failure, 1 if errors, 2 if successful.
1724         */
1725         function ExecuteSchema( $sqlArray = NULL, $continueOnErr =  NULL ) {
1726                 if( !is_bool( $continueOnErr ) ) {
1727                         $continueOnErr = $this->ContinueOnError();
1728                 }
1729                 
1730                 if( !isset( $sqlArray ) ) {
1731                         $sqlArray = $this->sqlArray;
1732                 }
1733                 
1734                 if( !is_array( $sqlArray ) ) {
1735                         $this->success = 0;
1736                 } else {
1737                         $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr );
1738                 }
1739                 
1740                 return $this->success;
1741         }
1742         
1743         /**
1744         * Returns the current SQL array. 
1745         *
1746         * Call this method to fetch the array of SQL queries resulting from 
1747         * ParseSchema() or ParseSchemaString(). 
1748         *
1749         * @param string $format Format: HTML, TEXT, or NONE (PHP array)
1750         * @return array Array of SQL statements or FALSE if an error occurs
1751         */
1752         function PrintSQL( $format = 'NONE' ) {
1753                 $sqlArray = null;
1754                 return $this->getSQL( $format, $sqlArray );
1755         }
1756         
1757         /**
1758         * Saves the current SQL array to the local filesystem as a list of SQL queries.
1759         *
1760         * Call this method to save the array of SQL queries (generally resulting from a
1761         * parsed XML schema) to the filesystem.
1762         *
1763         * @param string $filename Path and name where the file should be saved.
1764         * @return boolean TRUE if save is successful, else FALSE. 
1765         */
1766         function SaveSQL( $filename = './schema.sql' ) {
1767                 
1768                 if( !isset( $sqlArray ) ) {
1769                         $sqlArray = $this->sqlArray;
1770                 }
1771                 if( !isset( $sqlArray ) ) {
1772                         return FALSE;
1773                 }
1774                 
1775                 $fp = fopen( $filename, "w" );
1776                 
1777                 foreach( $sqlArray as $key => $query ) {
1778                         fwrite( $fp, $query . ";\n" );
1779                 }
1780                 fclose( $fp );
1781         }
1782         
1783         /**
1784         * Create an xml parser
1785         *
1786         * @return object PHP XML parser object
1787         *
1788         * @access private
1789         */
1790         function create_parser() {
1791                 // Create the parser
1792                 $xmlParser = xml_parser_create();
1793                 xml_set_object( $xmlParser, $this );
1794                 
1795                 // Initialize the XML callback functions
1796                 xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
1797                 xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
1798                 
1799                 return $xmlParser;
1800         }
1801         
1802         /**
1803         * XML Callback to process start elements
1804         *
1805         * @access private
1806         */
1807         function _tag_open( &$parser, $tag, $attributes ) {
1808                 switch( strtoupper( $tag ) ) {
1809                         case 'TABLE':
1810                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1811                                 $this->obj = new dbTable( $this, $attributes );
1812                                 xml_set_object( $parser, $this->obj );
1813                                 }
1814                                 break;
1815                         case 'SQL':
1816                                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1817                                         $this->obj = new dbQuerySet( $this, $attributes );
1818                                         xml_set_object( $parser, $this->obj );
1819                                 }
1820                                 break;
1821                         default:
1822                                 // print_r( array( $tag, $attributes ) );
1823                 }
1824                 
1825         }
1826         
1827         /**
1828         * XML Callback to process CDATA elements
1829         *
1830         * @access private
1831         */
1832         function _tag_cdata( &$parser, $cdata ) {
1833         }
1834         
1835         /**
1836         * XML Callback to process end elements
1837         *
1838         * @access private
1839         * @internal
1840         */
1841         function _tag_close( &$parser, $tag ) {
1842                 
1843         }
1844         
1845         /**
1846         * Converts an XML schema string to the specified DTD version.
1847         *
1848         * Call this method to convert a string containing an XML schema to a different AXMLS
1849         * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
1850         * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
1851         * parameter is specified, the schema will be converted to the current DTD version. 
1852         * If the newFile parameter is provided, the converted schema will be written to the specified
1853         * file.
1854         * @see ConvertSchemaFile()
1855         *
1856         * @param string $schema String containing XML schema that will be converted.
1857         * @param string $newVersion DTD version to convert to.
1858         * @param string $newFile File name of (converted) output file.
1859         * @return string Converted XML schema or FALSE if an error occurs.
1860         */
1861         function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
1862                 
1863                 // grab current version
1864                 if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
1865                         return FALSE;
1866                 }
1867                 
1868                 if( !isset ($newVersion) ) {
1869                         $newVersion = $this->schemaVersion;
1870                 }
1871                 
1872                 if( $version == $newVersion ) {
1873                         $result = $schema;
1874                 } else {
1875                         $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion);
1876                 }
1877                 
1878                 if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
1879                         fwrite( $fp, $result );
1880                         fclose( $fp );
1881                 }
1882                 
1883                 return $result;
1884         }
1885
1886         /*
1887         // compat for pre-4.3 - jlim
1888         function _file_get_contents($path)
1889         {
1890                 if (function_exists('file_get_contents')) return file_get_contents($path);
1891                 return join('',file($path));
1892         }*/
1893         
1894         /**
1895         * Converts an XML schema file to the specified DTD version.
1896         *
1897         * Call this method to convert the specified XML schema file to a different AXMLS
1898         * DTD version. For instance, to convert a schema created for an pre-1.0 version for 
1899         * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version 
1900         * parameter is specified, the schema will be converted to the current DTD version. 
1901         * If the newFile parameter is provided, the converted schema will be written to the specified
1902         * file.
1903         * @see ConvertSchemaString()
1904         *
1905         * @param string $filename Name of XML schema file that will be converted.
1906         * @param string $newVersion DTD version to convert to.
1907         * @param string $newFile File name of (converted) output file.
1908         * @return string Converted XML schema or FALSE if an error occurs.
1909         */
1910         function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
1911                 
1912                 // grab current version
1913                 if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
1914                         return FALSE;
1915                 }
1916                 
1917                 if( !isset ($newVersion) ) {
1918                         $newVersion = $this->schemaVersion;
1919                 }
1920                 
1921                 if( $version == $newVersion ) {
1922                         $result = _file_get_contents( $filename );
1923                         
1924                         // remove unicode BOM if present
1925                         if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
1926                                 $result = substr( $result, 3 );
1927                         }
1928                 } else {
1929                         $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' );
1930                 }
1931                 
1932                 if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) {
1933                         fwrite( $fp, $result );
1934                         fclose( $fp );
1935                 }
1936                 
1937                 return $result;
1938         }
1939         
1940         function TransformSchema( $schema, $xsl, $schematype='string' )
1941         {
1942                 // Fail if XSLT extension is not available
1943                 if( ! function_exists( 'xslt_create' ) ) {
1944                         return FALSE;
1945                 }
1946                 
1947                 $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl';
1948                 
1949                 // look for xsl
1950                 if( !is_readable( $xsl_file ) ) {
1951                         return FALSE;
1952                 }
1953                 
1954                 switch( $schematype )
1955                 {
1956                         case 'file':
1957                                 if( !is_readable( $schema ) ) {
1958                                         return FALSE;
1959                                 }
1960                                 
1961                                 $schema = _file_get_contents( $schema );
1962                                 break;
1963                         case 'string':
1964                         default:
1965                                 if( !is_string( $schema ) ) {
1966                                         return FALSE;
1967                                 }
1968                 }
1969                 
1970                 $arguments = array (
1971                         '/_xml' => $schema,
1972                         '/_xsl' => _file_get_contents( $xsl_file )
1973                 );
1974                 
1975                 // create an XSLT processor
1976                 $xh = xslt_create ();
1977                 
1978                 // set error handler
1979                 xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
1980                 
1981                 // process the schema
1982                 $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); 
1983                 
1984                 xslt_free ($xh);
1985                 
1986                 return $result;
1987         }
1988         
1989         /**
1990         * Processes XSLT transformation errors
1991         *
1992         * @param object $parser XML parser object
1993         * @param integer $errno Error number
1994         * @param integer $level Error level
1995         * @param array $fields Error information fields
1996         *
1997         * @access private
1998         */
1999         function xslt_error_handler( $parser, $errno, $level, $fields ) {
2000                 if( is_array( $fields ) ) {
2001                         $msg = array(
2002                                 'Message Type' => ucfirst( $fields['msgtype'] ),
2003                                 'Message Code' => $fields['code'],
2004                                 'Message' => $fields['msg'],
2005                                 'Error Number' => $errno,
2006                                 'Level' => $level
2007                         );
2008                         
2009                         switch( $fields['URI'] ) {
2010                                 case 'arg:/_xml':
2011                                         $msg['Input'] = 'XML';
2012                                         break;
2013                                 case 'arg:/_xsl':
2014                                         $msg['Input'] = 'XSL';
2015                                         break;
2016                                 default:
2017                                         $msg['Input'] = $fields['URI'];
2018                         }
2019                         
2020                         $msg['Line'] = $fields['line'];
2021                 } else {
2022                         $msg = array(
2023                                 'Message Type' => 'Error',
2024                                 'Error Number' => $errno,
2025                                 'Level' => $level,
2026                                 'Fields' => var_export( $fields, TRUE )
2027                         );
2028                 }
2029                 
2030                 $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
2031                                            . '<table>' . "\n";
2032                 
2033                 foreach( $msg as $label => $details ) {
2034                         $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
2035                 }
2036                 
2037                 $error_details .= '</table>';
2038                 
2039                 trigger_error( $error_details, E_USER_ERROR );
2040         }
2041         
2042         /**
2043         * Returns the AXMLS Schema Version of the requested XML schema file.
2044         *
2045         * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
2046         * @see SchemaStringVersion()
2047         *
2048         * @param string $filename AXMLS schema file
2049         * @return string Schema version number or FALSE on error
2050         */
2051         function SchemaFileVersion( $filename ) {
2052                 // Open the file
2053                 if( !($fp = fopen( $filename, 'r' )) ) {
2054                         // die( 'Unable to open file' );
2055                         return FALSE;
2056                 }
2057                 
2058                 // Process the file
2059                 while( $data = fread( $fp, 4096 ) ) {
2060                         if( preg_match( $this->versionRegex, $data, $matches ) ) {
2061                                 return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
2062                         }
2063                 }
2064                 
2065                 return FALSE;
2066         }
2067         
2068         /**
2069         * Returns the AXMLS Schema Version of the provided XML schema string.
2070         *
2071         * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
2072         * @see SchemaFileVersion()
2073         *
2074         * @param string $xmlstring XML schema string
2075         * @return string Schema version number or FALSE on error
2076         */
2077         function SchemaStringVersion( $xmlstring ) {
2078                 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
2079                         return FALSE;
2080                 }
2081                 
2082                 if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) {
2083                         return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION;
2084                 }
2085                 
2086                 return FALSE;
2087         }
2088         
2089         /**
2090         * Extracts an XML schema from an existing database.
2091         *
2092         * Call this method to create an XML schema string from an existing database.
2093         * If the data parameter is set to TRUE, AXMLS will include the data from the database
2094         * in the schema. 
2095         *
2096         * @param boolean $data Include data in schema dump
2097         * @indent string indentation to use
2098         * @prefix string extract only tables with given prefix
2099         * @stripprefix strip prefix string when storing in XML schema
2100         * @return string Generated XML schema
2101         */
2102         function ExtractSchema( $data = FALSE, $indent = '  ', $prefix = '' , $stripprefix=false) {
2103                 $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM );
2104                 
2105                 $schema = '<?xml version="1.0"?>' . "\n"
2106                                 . '<schema version="' . $this->schemaVersion . '">' . "\n";
2107                 
2108                 if( is_array( $tables = $this->db->MetaTables( 'TABLES' , ($prefix) ? $prefix.'%' : '') ) ) {
2109                         foreach( $tables as $table ) {
2110                                 if ($stripprefix) $table = str_replace(str_replace('\\_', '_', $pfx ), '', $table);
2111                                 $schema .= $indent . '<table name="' . htmlentities( $table ) . '">' . "\n";
2112                                 
2113                                 // grab details from database
2114                                 $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE -1' );
2115                                 $fields = $this->db->MetaColumns( $table );
2116                                 $indexes = $this->db->MetaIndexes( $table );
2117                                 
2118                                 if( is_array( $fields ) ) {
2119                                         foreach( $fields as $details ) {
2120                                                 $extra = '';
2121                                                 $content = array();
2122                                                 
2123                                                 if( isset($details->max_length) && $details->max_length > 0 ) {
2124                                                         $extra .= ' size="' . $details->max_length . '"';
2125                                                 }
2126                                                 
2127                                                 if( isset($details->primary_key) && $details->primary_key ) {
2128                                                         $content[] = '<KEY/>';
2129                                                 } elseif( isset($details->not_null) && $details->not_null ) {
2130                                                         $content[] = '<NOTNULL/>';
2131                                                 }
2132                                                 
2133                                                 if( isset($details->has_default) && $details->has_default ) {
2134                                                         $content[] = '<DEFAULT value="' . htmlentities( $details->default_value ) . '"/>';
2135                                                 }
2136                                                 
2137                                                 if( isset($details->auto_increment) && $details->auto_increment ) {
2138                                                         $content[] = '<AUTOINCREMENT/>';
2139                                                 }
2140                                                 
2141                                                 if( isset($details->unsigned) && $details->unsigned ) {
2142                                                         $content[] = '<UNSIGNED/>';
2143                                                 }
2144                                                 
2145                                                 // this stops the creation of 'R' columns,
2146                                                 // AUTOINCREMENT is used to create auto columns
2147                                                 $details->primary_key = 0;
2148                                                 $type = $rs->MetaType( $details );
2149                                                 
2150                                                 $schema .= str_repeat( $indent, 2 ) . '<field name="' . htmlentities( $details->name ) . '" type="' . $type . '"' . $extra;
2151                                                 
2152                                                 if( !empty( $content ) ) {
2153                                                         $schema .= ">\n" . str_repeat( $indent, 3 )
2154                                                                          . implode( "\n" . str_repeat( $indent, 3 ), $content ) . "\n"
2155                                                                          . str_repeat( $indent, 2 ) . '</field>' . "\n";
2156                                                 } else {
2157                                                         $schema .= "/>\n";
2158                                                 }
2159                                         }
2160                                 }
2161                                 
2162                                 if( is_array( $indexes ) ) {
2163                                         foreach( $indexes as $index => $details ) {
2164                                                 $schema .= str_repeat( $indent, 2 ) . '<index name="' . $index . '">' . "\n";
2165                                                 
2166                                                 if( $details['unique'] ) {
2167                                                         $schema .= str_repeat( $indent, 3 ) . '<UNIQUE/>' . "\n";
2168                                                 }
2169                                                 
2170                                                 foreach( $details['columns'] as $column ) {
2171                                                         $schema .= str_repeat( $indent, 3 ) . '<col>' . htmlentities( $column ) . '</col>' . "\n";
2172                                                 }
2173                                                 
2174                                                 $schema .= str_repeat( $indent, 2 ) . '</index>' . "\n";
2175                                         }
2176                                 }
2177                                 
2178                                 if( $data ) {
2179                                         $rs = $this->db->Execute( 'SELECT * FROM ' . $table );
2180                                         
2181                                         if( is_object( $rs ) && !$rs->EOF ) {
2182                                                 $schema .= str_repeat( $indent, 2 ) . "<data>\n";
2183                                                 
2184                                                 while( $row = $rs->FetchRow() ) {
2185                                                         foreach( $row as $key => $val ) {
2186                                                                 if ( $val != htmlentities( $val ) ) {
2187                                                                         $row[$key] = '<![CDATA[' . $val . ']]>';
2188                                                                 }
2189                                                         }
2190                                                         
2191                                                         $schema .= str_repeat( $indent, 3 ) . '<row><f>' . implode( '</f><f>', $row ) . "</f></row>\n";
2192                                                 }
2193                                                 
2194                                                 $schema .= str_repeat( $indent, 2 ) . "</data>\n";
2195                                         }
2196                                 }
2197                                 
2198                                 $schema .= $indent . "</table>\n";
2199                         }
2200                 }
2201                 
2202                 $this->db->SetFetchMode( $old_mode );
2203                 
2204                 $schema .= '</schema>';
2205                 return $schema;
2206         }
2207         
2208         /**
2209         * Sets a prefix for database objects
2210         *
2211         * Call this method to set a standard prefix that will be prepended to all database tables 
2212         * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
2213         *
2214         * @param string $prefix Prefix that will be prepended.
2215         * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
2216         * @return boolean TRUE if successful, else FALSE
2217         */
2218         function SetPrefix( $prefix = '', $underscore = TRUE ) {
2219                 switch( TRUE ) {
2220                         // clear prefix
2221                         case empty( $prefix ):
2222                                 logMsg( 'Cleared prefix' );
2223                                 $this->objectPrefix = '';
2224                                 return TRUE;
2225                         // prefix too long
2226                         case strlen( $prefix ) > XMLS_PREFIX_MAXLEN:
2227                         // prefix contains invalid characters
2228                         case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ):
2229                                 logMsg( 'Invalid prefix: ' . $prefix );
2230                                 return FALSE;
2231                 }
2232                 
2233                 if( $underscore AND substr( $prefix, -1 ) != '_' ) {
2234                         $prefix .= '_';
2235                 }
2236                 
2237                 // prefix valid
2238                 logMsg( 'Set prefix: ' . $prefix );
2239                 $this->objectPrefix = $prefix;
2240                 return TRUE;
2241         }
2242         
2243         /**
2244         * Returns an object name with the current prefix prepended.
2245         *
2246         * @param string $name Name
2247         * @return string        Prefixed name
2248         *
2249         * @access private
2250         */
2251         function prefix( $name = '' ) {
2252                 // if prefix is set
2253                 if( !empty( $this->objectPrefix ) ) {
2254                         // Prepend the object prefix to the table name
2255                         // prepend after quote if used
2256                         return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name );
2257                 }
2258                 
2259                 // No prefix set. Use name provided.
2260                 return $name;
2261         }
2262         
2263         /**
2264         * Checks if element references a specific platform
2265         *
2266         * @param string $platform Requested platform
2267         * @returns boolean TRUE if platform check succeeds
2268         *
2269         * @access private
2270         */
2271         function supportedPlatform( $platform = NULL ) {
2272                 if( !empty( $platform ) ) {
2273                         $regex = '/(^|\|)' . $this->db->databaseType . '(\||$)/i';
2274                 
2275                         if( preg_match( '/^- /', $platform ) ) {
2276                                 if (preg_match ( $regex, substr( $platform, 2 ) ) ) {
2277                                         logMsg( 'Platform ' . $platform . ' is NOT supported' );
2278                                         return FALSE;
2279                                 }
2280                 } else {
2281                                 if( !preg_match ( $regex, $platform ) ) {
2282                                         logMsg( 'Platform ' . $platform . ' is NOT supported' );
2283                         return FALSE;
2284                 }
2285         }
2286                 }
2287                 
2288                 logMsg( 'Platform ' . $platform . ' is supported' );
2289                 return TRUE;
2290         }
2291         
2292         /**
2293         * Clears the array of generated SQL.
2294         *
2295         * @access private
2296         */
2297         function clearSQL() {
2298                 $this->sqlArray = array();
2299         }
2300         
2301         /**
2302         * Adds SQL into the SQL array.
2303         *
2304         * @param mixed $sql SQL to Add
2305         * @return boolean TRUE if successful, else FALSE.
2306         *
2307         * @access private
2308         */      
2309         function addSQL( $sql = NULL ) {
2310                 if( is_array( $sql ) ) {
2311                         foreach( $sql as $line ) {
2312                                 $this->addSQL( $line );
2313                         }
2314                         
2315                         return TRUE;
2316                 }
2317                 
2318                 if( is_string( $sql ) ) {
2319                         $this->sqlArray[] = $sql;
2320                         
2321                         // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
2322                         if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) {
2323                                 $saved = $this->db->debug;
2324                                 $this->db->debug = $this->debug;
2325                                 $ok = $this->db->Execute( $sql );
2326                                 $this->db->debug = $saved;
2327                                 
2328                                 if( !$ok ) {
2329                                         if( $this->debug ) {
2330                                                 ADOConnection::outp( $this->db->ErrorMsg() );
2331                                         }
2332                                         
2333                                         $this->success = 1;
2334                                 }
2335                         }
2336                         
2337                         return TRUE;
2338                 }
2339                 
2340                 return FALSE;
2341         }
2342         
2343         /**
2344         * Gets the SQL array in the specified format.
2345         *
2346         * @param string $format Format
2347         * @return mixed SQL
2348         *       
2349         * @access private
2350         */
2351         function getSQL( $format = NULL, $sqlArray = NULL ) {
2352                 if( !is_array( $sqlArray ) ) {
2353                         $sqlArray = $this->sqlArray;
2354                 }
2355                 
2356                 if( !is_array( $sqlArray ) ) {
2357                         return FALSE;
2358                 }
2359                 
2360                 switch( strtolower( $format ) ) {
2361                         case 'string':
2362                         case 'text':
2363                                 return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : '';
2364                         case'html':
2365                                 return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
2366                 }
2367                 
2368                 return $this->sqlArray;
2369         }
2370         
2371         /**
2372         * Destroys an adoSchema object.
2373         *
2374         * Call this method to clean up after an adoSchema object that is no longer in use.
2375         * @deprecated adoSchema now cleans up automatically.
2376         */
2377         function Destroy() {
2378                 ini_set("magic_quotes_runtime", $this->mgq );
2379                 #set_magic_quotes_runtime( $this->mgq );
2380                 unset( $this );
2381         }
2382 }
2383
2384 /**
2385 * Message logging function
2386 *
2387 * @access private
2388 */
2389 function logMsg( $msg, $title = NULL, $force = FALSE ) {
2390         if( XMLS_DEBUG or $force ) {
2391                 echo '<pre>';
2392                 
2393                 if( isset( $title ) ) {
2394                         echo '<h3>' . htmlentities( $title ) . '</h3>';
2395                 }
2396                 
2397                 if( @is_object( $this ) ) {
2398                         echo '[' . get_class( $this ) . '] ';
2399                 }
2400                 
2401                 print_r( $msg );
2402                 
2403                 echo '</pre>';
2404         }
2405 }
2406 ?>