root/releases/elgg0.8rc2/lib/adodb/adodb-xmlschema.inc.php

Revision 725, 54.0 kB (checked in by misja, 2 years ago)

Updated ADODB library.

  • Property svn:eol-style set to native
Line 
1 <?php
2 // Copyright (c) 2004 ars Cognita Inc., all rights reserved
3 /* ******************************************************************************
4     Released under both BSD license and Lesser GPL library license.
5      Whenever there is any discrepancy between the two licenses,
6      the BSD license will take precedence.
7 *******************************************************************************/
8 /**
9  * xmlschema is a class that allows the user to quickly and easily
10  * build a database on any ADOdb-supported platform using a simple
11  * XML schema.
12  *
13  * Last Editor: $Author: jlim $
14  * @author Richard Tango-Lowy & Dan Cech
15  * @version $Revision: 1.12 $
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.2' );
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 * Default Schema Version.  Used for Schemas without an explicit version set.
86 */
87 if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
88     define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
89 }
90
91 /**
92 * Include the main ADODB library
93 */
94 if( !defined( '_ADODB_LAYER' ) ) {
95     require( 'adodb.inc.php' );
96     require( 'adodb-datadict.inc.php' );
97 }
98
99 /**
100 * Abstract DB Object. This class provides basic methods for database objects, such
101 * as tables and indexes.
102 *
103 * @package axmls
104 * @access private
105 */
106 class dbObject {
107     
108     /**
109     * var object Parent
110     */
111     var $parent;
112     
113     /**
114     * var string current element
115     */
116     var $currentElement;
117     
118     /**
119     * NOP
120     */
121     function dbObject( &$parent, $attributes = NULL ) {
122         $this->parent =& $parent;
123     }
124     
125     /**
126     * XML Callback to process start elements
127     *
128     * @access private
129     */
130     function _tag_open( &$parser, $tag, $attributes ) {
131         
132     }
133     
134     /**
135     * XML Callback to process CDATA elements
136     *
137     * @access private
138     */
139     function _tag_cdata( &$parser, $cdata ) {
140         
141     }
142     
143     /**
144     * XML Callback to process end elements
145     *
146     * @access private
147     */
148     function _tag_close( &$parser, $tag ) {
149         
150     }
151     
152     function create() {
153         return array();
154     }
155     
156     /**
157     * Destroys the object
158     */
159     function destroy() {
160         unset( $this );
161     }
162     
163     /**
164     * Checks whether the specified RDBMS is supported by the current
165     * database object or its ranking ancestor.
166     *
167     * @param string $platform RDBMS platform name (from ADODB platform list).
168     * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
169     */
170     function supportedPlatform( $platform = NULL ) {
171         return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE;
172     }
173     
174     /**
175     * Returns the prefix set by the ranking ancestor of the database object.
176     *
177     * @param string $name Prefix string.
178     * @return string Prefix.
179     */
180     function prefix( $name = '' ) {
181         return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name;
182     }
183     
184     /**
185     * Extracts a field ID from the specified field.
186     *
187     * @param string $field Field.
188     * @return string Field ID.
189     */
190     function FieldID( $field ) {
191         return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
192     }
193 }
194
195 /**
196 * Creates a table object in ADOdb's datadict format
197 *
198 * This class stores information about a database table. As charactaristics
199 * of the table are loaded from the external source, methods and properties
200 * of this class are used to build up the table description in ADOdb's
201 * datadict format.
202 *
203 * @package axmls
204 * @access private
205 */
206 class dbTable extends dbObject {
207     
208     /**
209     * @var string Table name
210     */
211     var $name;
212     
213     /**
214     * @var array Field specifier: Meta-information about each field
215     */
216     var $fields = array();
217     
218     /**
219     * @var array List of table indexes.
220     */
221     var $indexes = array();
222     
223     /**
224     * @var array Table options: Table-level options
225     */
226     var $opts = array();
227     
228     /**
229     * @var string Field index: Keeps track of which field is currently being processed
230     */
231     var $current_field;
232     
233     /**
234     * @var boolean Mark table for destruction
235     * @access private
236     */
237     var $drop_table;
238     
239     /**
240     * @var boolean Mark field for destruction (not yet implemented)
241     * @access private
242     */
243     var $drop_field = array();
244     
245     /**
246     * Iniitializes a new table object.
247     *
248     * @param string $prefix DB Object prefix
249     * @param array $attributes Array of table attributes.
250     */
251     function dbTable( &$parent, $attributes = NULL ) {
252         $this->parent =& $parent;
253         $this->name = $this->prefix($attributes['NAME']);
254     }
255     
256     /**
257     * XML Callback to process start elements. Elements currently
258     * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
259     *
260     * @access private
261     */
262     function _tag_open( &$parser, $tag, $attributes ) {
263         $this->currentElement = strtoupper( $tag );
264         
265         switch( $this->currentElement ) {
266             case 'INDEX':
267                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
268                     xml_set_object( $parser, $this->addIndex( $attributes ) );
269                 }
270                 break;
271             case 'DATA':
272                 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
273                     xml_set_object( $parser, $this->addData( $attributes ) );
274                 }
275                 break;
276             case 'DROP':
277                 $this->drop();
278                 break;
279             case 'FIELD':
280                 // Add a field
281                 $fieldName = $attributes['NAME'];
282                 $fieldType = $attributes['TYPE'];
283                 $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL;
284                 $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL;
285                 
286                 $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
287                 break;
288             case 'KEY':
289             case 'NOTNULL':
290             case 'AUTOINCREMENT':
291                 // Add a field option
292                 $this->addFieldOpt( $this->current_field, $this->currentElement );
293                 break;
294             case 'DEFAULT':
295                 // Add a field option to the table object
296                 
297                 // Work around ADOdb datadict issue that misinterprets empty strings.
298                 if( $attributes['VALUE'] == '' ) {
299                     $attributes['VALUE'] = " '' ";
300                 }
301                 
302                 $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] );
303                 break;
304             case 'DEFDATE':
305             case 'DEFTIMESTAMP':
306                 // Add a field option to the table object
307                 $this->addFieldOpt( $this->current_field, $this->currentElement );
308                 break;
309             default:
310                 // print_r( array( $tag, $attributes ) );
311         }
312     }
313     
314     /**
315     * XML Callback to process CDATA elements
316     *
317     * @access private
318     */
319     function _tag_cdata( &$parser, $cdata ) {
320         switch( $this->currentElement ) {
321             // Table constraint
322             case 'CONSTRAINT':
323                 if( isset( $this->current_field ) ) {
324                     $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata );
325                 } else {
326                     $this->addTableOpt( $cdata );
327                 }
328                 break;
329             // Table option
330             case 'OPT':
331                 $this->addTableOpt( $cdata );
332                 break;
333             default:
334                 
335         }
336     }
337     
338     /**
339     * XML Callback to process end elements
340     *
341     * @access private
342     */
343     function _tag_close( &$parser, $tag ) {
344         $this->currentElement = '';
345         
346         switch( strtoupper( $tag ) ) {
347             case 'TABLE':
348                 $this->parent->addSQL( $this->create( $this->parent ) );
349                 xml_set_object( $parser, $this->parent );
350                 $this->destroy();
351                 break;
352             case 'FIELD':
353                 unset($this->current_field);
354                 break;
355
356         }
357     }
358     
359     /**
360     * Adds an index to a table object
361     *
362     * @param array $attributes Index attributes
363     * @return object dbIndex object
364     */
365     function &addIndex( $attributes ) {
366         $name = strtoupper( $attributes['NAME'] );
367         $this->indexes[$name] =& new dbIndex( $this, $attributes );
368         return $this->indexes[$name];
369     }
370     
371     /**
372     * Adds data to a table object
373     *
374     * @param array $attributes Data attributes
375     * @return object dbData object
376     */
377     function &addData( $attributes ) {
378         if( !isset( $this->data ) ) {
379             $this->data =& new dbData( $this, $attributes );
380         }
381         return $this->data;
382     }
383     
384     /**
385     * Adds a field to a table object
386     *
387     * $name is the name of the table to which the field should be added.
388     * $type is an ADODB datadict field type. The following field types
389     * are supported as of ADODB 3.40:
390     *     - C:  varchar
391     *    - X:  CLOB (character large object) or largest varchar size
392     *       if CLOB is not supported
393     *    - C2: Multibyte varchar
394     *    - X2: Multibyte CLOB
395     *    - B:  BLOB (binary large object)
396     *    - D:  Date (some databases do not support this, and we return a datetime type)
397     *    - T:  Datetime or Timestamp
398     *    - L:  Integer field suitable for storing booleans (0 or 1)
399     *    - I:  Integer (mapped to I4)
400     *    - I1: 1-byte integer
401     *    - I2: 2-byte integer
402     *    - I4: 4-byte integer
403     *    - I8: 8-byte integer
404     *    - F:  Floating point number
405     *    - N:  Numeric or decimal number
406     *
407     * @param string $name Name of the table to which the field will be added.
408     * @param string $type    ADODB datadict field type.
409     * @param string $size    Field size
410     * @param array $opts    Field options array
411     * @return array Field specifier array
412     */
413     function addField( $name, $type, $size = NULL, $opts = NULL ) {
414         $field_id = $this->FieldID( $name );
415         
416         // Set the field index so we know where we are
417         $this->current_field = $field_id;
418         
419         // Set the field name (required)
420         $this->fields[$field_id]['NAME'] = $name;
421         
422         // Set the field type (required)
423         $this->fields[$field_id]['TYPE'] = $type;
424         
425         // Set the field size (optional)
426         if( isset( $size ) ) {
427             $this->fields[$field_id]['SIZE'] = $size;
428         }
429         
430         // Set the field options
431         if( isset( $opts ) ) {
432             $this->fields[$field_id]['OPTS'][] = $opts;
433         }
434     }
435     
436     /**
437     * Adds a field option to the current field specifier
438     *
439     * This method adds a field option allowed by the ADOdb datadict
440     * and appends it to the given field.
441     *
442     * @param string $field    Field name
443     * @param string $opt ADOdb field option
444     * @param mixed $value Field option value
445     * @return array Field specifier array
446     */
447     function addFieldOpt( $field, $opt, $value = NULL ) {
448         if( !isset( $value ) ) {
449             $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt;
450         // Add the option and value
451         } else {
452             $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
453         }
454     }
455     
456     /**
457     * Adds an option to the table
458     *
459     * This method takes a comma-separated list of table-level options
460     * and appends them to the table object.
461     *
462     * @param string $opt Table option
463     * @return array Options
464     */
465     function addTableOpt( $opt ) {
466         $this->opts[] = $opt;
467         
468         return $this->opts;
469     }
470     
471     /**
472     * Generates the SQL that will create the table in the database
473     *
474     * @param object $xmls adoSchema object
475     * @return array Array containing table creation SQL
476     */
477     function create( &$xmls ) {
478         $sql = array();
479         
480         // drop any existing indexes
481         if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) {
482             foreach( $legacy_indexes as $index => $index_details ) {
483                 $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name );
484             }
485         }
486         
487         // remove fields to be dropped from table object
488         foreach( $this->drop_field as $field ) {
489             unset( $this->fields[$field] );
490         }
491         
492         // if table exists
493         if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) {
494             // drop table
495             if( $this->drop_table ) {
496                 $sql[] = $xmls->dict->DropTableSQL( $this->name );
497                 
498                 return $sql;
499             }
500             
501             // drop any existing fields not in schema
502             foreach( $legacy_fields as $field_id => $field ) {
503                 if( !isset( $this->fields[$field_id] ) ) {
504                     $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' );
505                 }
506             }
507         // if table doesn't exist
508         } else {
509             if( $this->drop_table ) {
510                 return $sql;
511             }
512             
513             $legacy_fields = array();
514         }
515         
516         // Loop through the field specifier array, building the associative array for the field options
517         $fldarray = array();
518         
519         foreach( $this->fields as $field_id => $finfo ) {
520             // Set an empty size if it isn't supplied
521             if( !isset( $finfo['SIZE'] ) ) {
522                 $finfo['SIZE'] = '';
523             }
524             
525             // Initialize the field array with the type and size
526             $fldarray[$field_id] = array(
527                 'NAME' => $finfo['NAME'],
528                 'TYPE' => $finfo['TYPE'],
529                 'SIZE' => $finfo['SIZE']
530             );
531             
532             // Loop through the options array and add the field options.
533             if( isset( $finfo['OPTS'] ) ) {
534                 foreach( $finfo['OPTS'] as $opt ) {
535                     // Option has an argument.
536                     if( is_array( $opt ) ) {
537                         $key = key( $opt );
538                         $value = $opt[key( $opt )];
539                         @$fldarray[$field_id][$key] .= $value;
540                     // Option doesn't have arguments
541                     } else {
542                         $fldarray[$field_id][$opt] = $opt;
543                     }
544                 }
545             }
546         }
547         
548         if( empty( $legacy_fields ) ) {
549             // Create the new table
550             $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
551             logMsg( end( $sql ), 'Generated CreateTableSQL' );
552         } else {
553             // Upgrade an existing table
554             logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
555             switch( $xmls->upgrade ) {
556                 // Use ChangeTableSQL
557                 case 'ALTER':
558                     logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
559                     $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts );
560                     break;
561                 case 'REPLACE':
562                     logMsg( 'Doing upgrade REPLACE (testing)' );
563                     $sql[] = $xmls->dict->DropTableSQL( $this->name );
564                     $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts );
565                     break;
566                 // ignore table
567                 default:
568                     return array();
569             }
570         }
571         
572         foreach( $this->indexes as $index ) {
573             $sql[] = $index->create( $xmls );
574         }
575         
576         if( isset( $this->data ) ) {
577             $sql[] = $this->data->create( $xmls );
578         }
579         
580         return $sql;
581     }
582     
583     /**
584     * Marks a field or table for destruction
585     */
586     function drop() {
587         if( isset( $this->current_field ) ) {
588             // Drop the current field
589             logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
590             // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
591             $this->drop_field[$this->current_field] = $this->current_field;
592         } else {
593