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.
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
11 Redistributions of source code must retain the above copyright notice, this list
12 of conditions and the following disclaimer.
13 Redistributions in binary form must reproduce the above copyright notice, this l
14 ist of conditions and the following disclaimer in the documentation and/or other
15 materials provided with the distribution.
16 Neither the name of the ars Cognita, Inc., nor the names of its contributors may be used
17 to endorse or promote products derived from this software without specific prior
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WA
23 RRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIREC
25 T, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR P
27 ROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWI
29 SE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE P
30 OSSIBILITY OF SUCH DAMAGE.
32 *******************************************************************************/
34 * xmlschema is a class that allows the user to quickly and easily
35 * build a database on any ADOdb-supported platform using a simple
38 * Last Editor: $Author$
39 * @author Richard Tango-Lowy & Dan Cech
43 * @tutorial getting_started.pkg
49 if( !defined( 'XMLS_DEBUG' ) ) {
50 define( 'XMLS_DEBUG', FALSE );
56 if( !defined( 'XMLS_PREFIX' ) ) {
57 define( 'XMLS_PREFIX', '%%P' );
61 * Maximum length allowed for object prefix
63 if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) {
64 define( 'XMLS_PREFIX_MAXLEN', 10 );
68 * Execute SQL inline as it is generated
70 if( !defined( 'XMLS_EXECUTE_INLINE' ) ) {
71 define( 'XMLS_EXECUTE_INLINE', FALSE );
75 * Continue SQL Execution if an error occurs?
77 if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) {
78 define( 'XMLS_CONTINUE_ON_ERROR', FALSE );
82 * Current Schema Version
84 if( !defined( 'XMLS_SCHEMA_VERSION' ) ) {
85 define( 'XMLS_SCHEMA_VERSION', '0.2' );
89 * Default Schema Version. Used for Schemas without an explicit version set.
91 if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) {
92 define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' );
96 * Default Schema Version. Used for Schemas without an explicit version set.
98 if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) {
99 define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' );
103 * Include the main ADODB library
105 if( !defined( '_ADODB_LAYER' ) ) {
106 require( 'adodb.inc.php' );
110 * Abstract DB Object. This class provides basic methods for database objects, such
111 * as tables and indexes.
124 * var string current element
131 function dbObject( &$parent, $attributes = NULL ) {
132 $this->parent
=& $parent;
136 * XML Callback to process start elements
140 function _tag_open( &$parser, $tag, $attributes ) {
145 * XML Callback to process CDATA elements
149 function _tag_cdata( &$parser, $cdata ) {
154 * XML Callback to process end elements
158 function _tag_close( &$parser, $tag ) {
167 * Destroys the object
174 * Checks whether the specified RDBMS is supported by the current
175 * database object or its ranking ancestor.
177 * @param string $platform RDBMS platform name (from ADODB platform list).
178 * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE.
180 function supportedPlatform( $platform = NULL ) {
181 return is_object( $this->parent
) ?
$this->parent
->supportedPlatform( $platform ) : TRUE;
185 * Returns the prefix set by the ranking ancestor of the database object.
187 * @param string $name Prefix string.
188 * @return string Prefix.
190 function prefix( $name = '' ) {
191 return is_object( $this->parent
) ?
$this->parent
->prefix( $name ) : $name;
195 * Extracts a field ID from the specified field.
197 * @param string $field Field.
198 * @return string Field ID.
200 function FieldID( $field ) {
201 return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) );
206 * Creates a table object in ADOdb's datadict format
208 * This class stores information about a database table. As charactaristics
209 * of the table are loaded from the external source, methods and properties
210 * of this class are used to build up the table description in ADOdb's
216 class dbTable
extends dbObject
{
219 * @var string Table name
224 * @var array Field specifier: Meta-information about each field
226 var $fields = array();
229 * @var array List of table indexes.
231 var $indexes = array();
234 * @var array Table options: Table-level options
239 * @var string Field index: Keeps track of which field is currently being processed
244 * @var boolean Mark table for destruction
250 * @var boolean Mark field for destruction (not yet implemented)
253 var $drop_field = array();
256 * Iniitializes a new table object.
258 * @param string $prefix DB Object prefix
259 * @param array $attributes Array of table attributes.
261 function dbTable( &$parent, $attributes = NULL ) {
262 $this->parent
=& $parent;
263 $this->name
= $this->prefix($attributes['NAME']);
267 * XML Callback to process start elements. Elements currently
268 * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT.
272 function _tag_open( &$parser, $tag, $attributes ) {
273 $this->currentElement
= strtoupper( $tag );
275 switch( $this->currentElement
) {
277 xml_set_object( $parser, $this->addIndex( $attributes ) );
284 $fieldName = $attributes['NAME'];
285 $fieldType = $attributes['TYPE'];
286 $fieldSize = isset( $attributes['SIZE'] ) ?
$attributes['SIZE'] : NULL;
287 $fieldOpts = isset( $attributes['OPTS'] ) ?
$attributes['OPTS'] : NULL;
289 $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts );
293 case 'AUTOINCREMENT':
294 // Add a field option
295 $this->addFieldOpt( $this->current_field
, $this->currentElement
);
298 // Add a field option to the table object
300 // Work around ADOdb datadict issue that misinterprets empty strings.
301 if( $attributes['VALUE'] == '' ) {
302 $attributes['VALUE'] = " '' ";
305 $this->addFieldOpt( $this->current_field
, $this->currentElement
, $attributes['VALUE'] );
308 // print_r( array( $tag, $attributes ) );
313 * XML Callback to process CDATA elements
317 function _tag_cdata( &$parser, $cdata ) {
318 switch( $this->currentElement
) {
323 $this->addTableOpt( $cdata );
331 * XML Callback to process end elements
335 function _tag_close( &$parser, $tag ) {
336 $this->currentElement
= '';
338 switch( strtoupper( $tag ) ) {
340 $this->parent
->addSQL( $this->create( $this->parent
) );
341 xml_set_object( $parser, $this->parent
);
348 * Adds an index to a table object
350 * @param array $attributes Index attributes
351 * @return object dbIndex object
353 function &addIndex( $attributes ) {
354 $name = strtoupper( $attributes['NAME'] );
355 $this->indexes
[$name] =& new dbIndex( $this, $attributes );
356 return $this->indexes
[$name];
360 * Adds a field to a table object
362 * $name is the name of the table to which the field should be added.
363 * $type is an ADODB datadict field type. The following field types
364 * are supported as of ADODB 3.40:
366 * - X: CLOB (character large object) or largest varchar size
367 * if CLOB is not supported
368 * - C2: Multibyte varchar
369 * - X2: Multibyte CLOB
370 * - B: BLOB (binary large object)
371 * - D: Date (some databases do not support this, and we return a datetime type)
372 * - T: Datetime or Timestamp
373 * - L: Integer field suitable for storing booleans (0 or 1)
374 * - I: Integer (mapped to I4)
375 * - I1: 1-byte integer
376 * - I2: 2-byte integer
377 * - I4: 4-byte integer
378 * - I8: 8-byte integer
379 * - F: Floating point number
380 * - N: Numeric or decimal number
382 * @param string $name Name of the table to which the field will be added.
383 * @param string $type ADODB datadict field type.
384 * @param string $size Field size
385 * @param array $opts Field options array
386 * @return array Field specifier array
388 function addField( $name, $type, $size = NULL, $opts = NULL ) {
389 $field_id = $this->FieldID( $name );
391 // Set the field index so we know where we are
392 $this->current_field
= $field_id;
394 // Set the field name (required)
395 $this->fields
[$field_id]['NAME'] = $name;
397 // Set the field type (required)
398 $this->fields
[$field_id]['TYPE'] = $type;
400 // Set the field size (optional)
401 if( isset( $size ) ) {
402 $this->fields
[$field_id]['SIZE'] = $size;
405 // Set the field options
406 if( isset( $opts ) ) {
407 $this->fields
[$field_id]['OPTS'] = $opts;
412 * Adds a field option to the current field specifier
414 * This method adds a field option allowed by the ADOdb datadict
415 * and appends it to the given field.
417 * @param string $field Field name
418 * @param string $opt ADOdb field option
419 * @param mixed $value Field option value
420 * @return array Field specifier array
422 function addFieldOpt( $field, $opt, $value = NULL ) {
423 if( !isset( $value ) ) {
424 $this->fields
[$this->FieldID( $field )]['OPTS'][] = $opt;
425 // Add the option and value
427 $this->fields
[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value );
432 * Adds an option to the table
434 * This method takes a comma-separated list of table-level options
435 * and appends them to the table object.
437 * @param string $opt Table option
438 * @return array Options
440 function addTableOpt( $opt ) {
441 $this->opts
[] = $opt;
447 * Generates the SQL that will create the table in the database
449 * @param object $xmls adoSchema object
450 * @return array Array containing table creation SQL
452 function create( &$xmls ) {
455 // drop any existing indexes
456 if( is_array( $legacy_indexes = $xmls->dict
->MetaIndexes( $this->name
) ) ) {
457 foreach( $legacy_indexes as $index => $index_details ) {
458 $sql[] = $xmls->dict
->DropIndexSQL( $index, $this->name
);
462 // remove fields to be dropped from table object
463 foreach( $this->drop_field
as $field ) {
464 unset( $this->fields
[$field] );
468 if( is_array( $legacy_fields = $xmls->dict
->MetaColumns( $this->name
) ) ) {
470 if( $this->drop_table
) {
471 $sql[] = $xmls->dict
->DropTableSQL( $this->name
);
476 // drop any existing fields not in schema
477 foreach( $legacy_fields as $field_id => $field ) {
478 if( !isset( $this->fields
[$field_id] ) ) {
479 $sql[] = $xmls->dict
->DropColumnSQL( $this->name
, '`'.$field->name
.'`' );
482 // if table doesn't exist
484 if( $this->drop_table
) {
488 $legacy_fields = array();
491 // Loop through the field specifier array, building the associative array for the field options
494 foreach( $this->fields
as $field_id => $finfo ) {
495 // Set an empty size if it isn't supplied
496 if( !isset( $finfo['SIZE'] ) ) {
500 // Initialize the field array with the type and size
501 $fldarray[$field_id] = array(
502 'NAME' => $finfo['NAME'],
503 'TYPE' => $finfo['TYPE'],
504 'SIZE' => $finfo['SIZE']
507 // Loop through the options array and add the field options.
508 if( isset( $finfo['OPTS'] ) ) {
509 foreach( $finfo['OPTS'] as $opt ) {
510 // Option has an argument.
511 if( is_array( $opt ) ) {
513 $value = $opt[key( $opt )];
514 $fldarray[$field_id][$key] = $value;
515 // Option doesn't have arguments
517 $fldarray[$field_id][$opt] = $opt;
523 if( empty( $legacy_fields ) ) {
524 // Create the new table
525 $sql[] = $xmls->dict
->CreateTableSQL( $this->name
, $fldarray, $this->opts
);
526 logMsg( end( $sql ), 'Generated CreateTableSQL' );
528 // Upgrade an existing table
529 logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" );
530 switch( $xmls->upgrade
) {
531 // Use ChangeTableSQL
533 logMsg( 'Generated ChangeTableSQL (ALTERing table)' );
534 $sql[] = $xmls->dict
->ChangeTableSQL( $this->name
, $fldarray, $this->opts
);
537 logMsg( 'Doing upgrade REPLACE (testing)' );
538 $sql[] = $xmls->dict
->DropTableSQL( $this->name
);
539 $sql[] = $xmls->dict
->CreateTableSQL( $this->name
, $fldarray, $this->opts
);
547 foreach( $this->indexes
as $index ) {
548 $sql[] = $index->create( $xmls );
555 * Marks a field or table for destruction
558 if( isset( $this->current_field
) ) {
559 // Drop the current field
560 logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" );
561 // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field );
562 $this->drop_field
[$this->current_field
] = $this->current_field
;
564 // Drop the current table
565 logMsg( "Dropping table '{$this->name}'" );
566 // $this->drop_table = $xmls->dict->DropTableSQL( $this->name );
567 $this->drop_table
= TRUE;
573 * Creates an index object in ADOdb's datadict format
575 * This class stores information about a database index. As charactaristics
576 * of the index are loaded from the external source, methods and properties
577 * of this class are used to build up the index description in ADOdb's
583 class dbIndex
extends dbObject
{
586 * @var string Index name
591 * @var array Index options: Index-level options
596 * @var array Indexed fields: Table columns included in this index
598 var $columns = array();
601 * @var boolean Mark index for destruction
607 * Initializes the new dbIndex object.
609 * @param object $parent Parent object
610 * @param array $attributes Attributes
614 function dbIndex( &$parent, $attributes = NULL ) {
615 $this->parent
=& $parent;
617 $this->name
= $this->prefix ($attributes['NAME']);
621 * XML Callback to process start elements
623 * Processes XML opening tags.
624 * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH.
628 function _tag_open( &$parser, $tag, $attributes ) {
629 $this->currentElement
= strtoupper( $tag );
631 switch( $this->currentElement
) {
641 $this->addIndexOpt( $this->currentElement
);
644 // print_r( array( $tag, $attributes ) );
649 * XML Callback to process CDATA elements
651 * Processes XML cdata.
655 function _tag_cdata( &$parser, $cdata ) {
656 switch( $this->currentElement
) {
659 $this->addField( $cdata );
667 * XML Callback to process end elements
671 function _tag_close( &$parser, $tag ) {
672 $this->currentElement
= '';
674 switch( strtoupper( $tag ) ) {
676 xml_set_object( $parser, $this->parent
);
682 * Adds a field to the index
684 * @param string $name Field name
685 * @return string Field list
687 function addField( $name ) {
688 $this->columns
[$this->FieldID( $name )] = $name;
690 // Return the field list
691 return $this->columns
;
695 * Adds options to the index
697 * @param string $opt Comma-separated list of index options.
698 * @return string Option list
700 function addIndexOpt( $opt ) {
701 $this->opts
[] = $opt;
703 // Return the options list
708 * Generates the SQL that will create the index in the database
710 * @param object $xmls adoSchema object
711 * @return array Array containing index creation SQL
713 function create( &$xmls ) {
718 // eliminate any columns that aren't in the table
719 foreach( $this->columns
as $id => $col ) {
720 if( !isset( $this->parent
->fields
[$id] ) ) {
721 unset( $this->columns
[$id] );
725 return $xmls->dict
->CreateIndexSQL( $this->name
, $this->parent
->name
, $this->columns
, $this->opts
);
729 * Marks an index for destruction
737 * Creates the SQL to execute a list of provided SQL queries
742 class dbQuerySet
extends dbObject
{
745 * @var array List of SQL queries
747 var $queries = array();
750 * @var string String used to build of a query line by line
755 * @var string Query prefix key
760 * @var boolean Auto prefix enable (TRUE)
762 var $prefixMethod = 'AUTO';
765 * Initializes the query set.
767 * @param object $parent Parent object
768 * @param array $attributes Attributes
770 function dbQuerySet( &$parent, $attributes = NULL ) {
771 $this->parent
=& $parent;
773 // Overrides the manual prefix key
774 if( isset( $attributes['KEY'] ) ) {
775 $this->prefixKey
= $attributes['KEY'];
778 $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ?
strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : '';
780 // Enables or disables automatic prefix prepending
781 switch( $prefixMethod ) {
783 $this->prefixMethod
= 'AUTO';
786 $this->prefixMethod
= 'MANUAL';
789 $this->prefixMethod
= 'NONE';
792 $this->prefixMethod
= 'AUTO';
797 * XML Callback to process start elements. Elements currently
798 * processed are: QUERY.
802 function _tag_open( &$parser, $tag, $attributes ) {
803 $this->currentElement
= strtoupper( $tag );
805 switch( $this->currentElement
) {
807 // Create a new query in a SQL queryset.
808 // Ignore this query set if a platform is specified and it's different than the
809 // current connection platform.
810 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
813 $this->discardQuery();
817 // print_r( array( $tag, $attributes ) );
822 * XML Callback to process CDATA elements
824 function _tag_cdata( &$parser, $cdata ) {
825 switch( $this->currentElement
) {
826 // Line of queryset SQL data
828 $this->buildQuery( $cdata );
836 * XML Callback to process end elements
840 function _tag_close( &$parser, $tag ) {
841 $this->currentElement
= '';
843 switch( strtoupper( $tag ) ) {
845 // Add the finished query to the open query set.
849 $this->parent
->addSQL( $this->create( $this->parent
) );
850 xml_set_object( $parser, $this->parent
);
859 * Re-initializes the query.
861 * @return boolean TRUE
863 function newQuery() {
870 * Discards the existing query.
872 * @return boolean TRUE
874 function discardQuery() {
875 unset( $this->query
);
881 * Appends a line to a query that is being built line by line
883 * @param string $data Line of SQL data or NULL to initialize a new query
884 * @return string SQL query string.
886 function buildQuery( $sql = NULL ) {
887 if( !isset( $this->query
) ) {
891 if( empty( $sql ) ) {
895 if( !empty( $this->query
) ) {
899 $this->query
.= trim( $sql );
905 * Adds a completed query to the query list
907 * @return string SQL of added query
909 function addQuery() {
910 if( !isset( $this->query
) ) {
914 $this->queries
[] = $this->query
;
915 $return = $this->query
;
917 unset( $this->query
);
923 * Creates and returns the current query set
925 * @param object $xmls adoSchema object
926 * @return array Query set
928 function create( &$xmls ) {
929 foreach( $this->queries
as $id => $query ) {
930 switch( $this->prefixMethod
) {
932 // Enable auto prefix replacement
934 // Process object prefix.
935 // Evaluate SQL statements to prepend prefix to objects
936 $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query );
937 $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query );
938 $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query );
940 // SELECT statements aren't working yet
941 #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data );
944 // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX.
945 // If prefixKey is not set, we use the default constant XMLS_PREFIX
946 if( isset( $this->prefixKey
) AND( $this->prefixKey
!== '' ) ) {
947 // Enable prefix override
948 $query = str_replace( $this->prefixKey
, $xmls->objectPrefix
, $query );
950 // Use default replacement
951 $query = str_replace( XMLS_PREFIX
, $xmls->objectPrefix
, $query );
955 $this->queries
[$id] = trim( $query );
958 // Return the query set array
959 return $this->queries
;
963 * Rebuilds the query with the prefix attached to any objects
965 * @param string $regex Regex used to add prefix
966 * @param string $query SQL query string
967 * @param string $prefix Prefix to be appended to tables, indices, etc.
968 * @return string Prefixed SQL query string.
970 function prefixQuery( $regex, $query, $prefix = NULL ) {
971 if( !isset( $prefix ) ) {
975 if( preg_match( $regex, $query, $match ) ) {
976 $preamble = $match[1];
977 $postamble = $match[5];
978 $objectList = explode( ',', $match[3] );
979 // $prefix = $prefix . '_';
983 foreach( $objectList as $object ) {
984 if( $prefixedList !== '' ) {
985 $prefixedList .= ', ';
988 $prefixedList .= $prefix . trim( $object );
991 $query = $preamble . ' ' . $prefixedList . ' ' . $postamble;
999 * Loads and parses an XML file, creating an array of "ready-to-run" SQL statements
1001 * This class is used to load and parse the XML file, to create an array of SQL statements
1002 * that can be used to build a database, and to build the database using the SQL array.
1004 * @tutorial getting_started.pkg
1006 * @author Richard Tango-Lowy & Dan Cech
1007 * @version $Revision$
1014 * @var array Array containing SQL queries to generate all objects
1020 * @var object ADOdb connection object
1026 * @var object ADOdb Data Dictionary
1032 * @var string Current XML element
1035 var $currentElement = '';
1038 * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database
1044 * @var string Optional object prefix
1047 var $objectPrefix = '';
1050 * @var long Original Magic Quotes Runtime value
1056 * @var long System debug
1062 * @var string Regular expression to find schema version
1065 var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/';
1068 * @var string Current schema version
1074 * @var int Success of last Schema execution
1079 * @var bool Execute SQL inline as it is generated
1084 * @var bool Continue SQL execution if errors occur
1086 var $continueOnError;
1089 * Creates an adoSchema object
1091 * Creating an adoSchema object is the first step in processing an XML schema.
1092 * The only parameter is an ADOdb database connection object, which must already
1093 * have been created.
1095 * @param object $db ADOdb database connection object.
1097 function adoSchema( &$db ) {
1098 // Initialize the environment
1099 $this->mgq
= get_magic_quotes_runtime();
1100 set_magic_quotes_runtime(0);
1102 $this->debug
= $this->db
->debug
;
1104 $this->dict
= NewDataDictionary( $this->db
);
1105 $this->sqlArray
= array();
1106 $this->schemaVersion
= XMLS_SCHEMA_VERSION
;
1107 $this->executeInline( XMLS_EXECUTE_INLINE
);
1108 $this->continueOnError( XMLS_CONTINUE_ON_ERROR
);
1109 $this->setUpgradeMethod();
1113 * Sets the method to be used for upgrading an existing database
1115 * Use this method to specify how existing database objects should be upgraded.
1116 * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to
1117 * alter each database object directly, REPLACE attempts to rebuild each object
1118 * from scratch, BEST attempts to determine the best upgrade method for each
1119 * object, and NONE disables upgrading.
1121 * This method is not yet used by AXMLS, but exists for backward compatibility.
1122 * The ALTER method is automatically assumed when the adoSchema object is
1123 * instantiated; other upgrade methods are not currently supported.
1125 * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE)
1126 * @returns string Upgrade method used
1128 function SetUpgradeMethod( $method = '' ) {
1129 if( !is_string( $method ) ) {
1133 $method = strtoupper( $method );
1135 // Handle the upgrade methods
1138 $this->upgrade
= $method;
1141 $this->upgrade
= $method;
1144 $this->upgrade
= 'ALTER';
1147 $this->upgrade
= 'NONE';
1150 // Use default if no legitimate method is passed.
1151 $this->upgrade
= XMLS_DEFAULT_UPGRADE_METHOD
;
1154 return $this->upgrade
;
1158 * Enables/disables inline SQL execution.
1160 * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution),
1161 * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode
1162 * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema()
1163 * to apply the schema to the database.
1165 * @param bool $mode execute
1166 * @return bool current execution mode
1168 * @see ParseSchema(), ExecuteSchema()
1170 function ExecuteInline( $mode = NULL ) {
1171 if( is_bool( $mode ) ) {
1172 $this->executeInline
= $mode;
1175 return $this->executeInline
;
1179 * Enables/disables SQL continue on error.
1181 * Call this method to enable or disable continuation of SQL execution if an error occurs.
1182 * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs.
1183 * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing
1184 * of the schema will continue.
1186 * @param bool $mode execute
1187 * @return bool current continueOnError mode
1189 * @see addSQL(), ExecuteSchema()
1191 function ContinueOnError( $mode = NULL ) {
1192 if( is_bool( $mode ) ) {
1193 $this->continueOnError
= $mode;
1196 return $this->continueOnError
;
1200 * Loads an XML schema from a file and converts it to SQL.
1202 * Call this method to load the specified schema (see the DTD for the proper format) from
1203 * the filesystem and generate the SQL necessary to create the database described.
1204 * @see ParseSchemaString()
1206 * @param string $file Name of XML schema file.
1207 * @return array Array of SQL queries, ready to execute
1209 function ParseSchema( $filename ) {
1210 return $this->ParseSchemaString( $this->ConvertSchemaFile ( $filename ) );
1214 * Loads an XML schema from a file and converts it to SQL.
1216 * Call this method to load the specified schema from a file (see the DTD for the proper format)
1217 * and generate the SQL necessary to create the database described by the schema.
1219 * @param string $file Name of XML schema file.
1220 * @return array Array of SQL queries, ready to execute.
1222 * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString()
1223 * @see ParseSchema(), ParseSchemaString()
1225 function ParseSchemaFile( $filename ) {
1227 if( !($fp = fopen( $filename, 'r' )) ) {
1228 // die( 'Unable to open file' );
1232 // do version detection here
1233 if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion
) {
1237 $xmlParser = $this->create_parser();
1240 while( $data = fread( $fp, 4096 ) ) {
1241 if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) {
1243 "XML error: %s at line %d",
1244 xml_error_string( xml_get_error_code( $xmlParser) ),
1245 xml_get_current_line_number( $xmlParser)
1250 xml_parser_free( $xmlParser );
1252 return $this->sqlArray
;
1256 * Converts an XML schema string to SQL.
1258 * Call this method to parse a string containing an XML schema (see the DTD for the proper format)
1259 * and generate the SQL necessary to create the database described by the schema.
1260 * @see ParseSchema()
1262 * @param string $xmlstring XML schema string.
1263 * @return array Array of SQL queries, ready to execute.
1265 function ParseSchemaString( $xmlstring ) {
1266 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
1270 // do version detection here
1271 if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion
) {
1275 $xmlParser = $this->create_parser();
1279 if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) {
1281 "XML error: %s at line %d",
1282 xml_error_string( xml_get_error_code( $xmlParser) ),
1283 xml_get_current_line_number( $xmlParser)
1287 xml_parser_free( $xmlParser );
1288 return $this->sqlArray
;
1292 * Applies the current XML schema to the database (post execution).
1294 * Call this method to apply the current schema (generally created by calling
1295 * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes,
1296 * and executing other SQL specified in the schema) after parsing.
1297 * @see ParseSchema(), ParseSchemaString(), ExecuteInline()
1299 * @param array $sqlArray Array of SQL statements that will be applied rather than
1300 * the current schema.
1301 * @param boolean $continueOnErr Continue to apply the schema even if an error occurs.
1302 * @returns integer 0 if failure, 1 if errors, 2 if successful.
1304 function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) {
1305 if( !is_bool( $continueOnErr ) ) {
1306 $continueOnErr = $this->ContinueOnError();
1309 if( !isset( $sqlArray ) ) {
1310 $sqlArray = $this->sqlArray
;
1313 if( !is_array( $sqlArray ) ) {
1316 $this->success
= $this->dict
->ExecuteSQLArray( $sqlArray, $continueOnErr );
1319 return $this->success
;
1323 * Returns the current SQL array.
1325 * Call this method to fetch the array of SQL queries resulting from
1326 * ParseSchema() or ParseSchemaString().
1328 * @param string $format Format: HTML, TEXT, or NONE (PHP array)
1329 * @return array Array of SQL statements or FALSE if an error occurs
1331 function PrintSQL( $format = 'NONE' ) {
1332 return $this->getSQL( $format, $sqlArray );
1336 * Saves the current SQL array to the local filesystem as a list of SQL queries.
1338 * Call this method to save the array of SQL queries (generally resulting from a
1339 * parsed XML schema) to the filesystem.
1341 * @param string $filename Path and name where the file should be saved.
1342 * @return boolean TRUE if save is successful, else FALSE.
1344 function SaveSQL( $filename = './schema.sql' ) {
1346 if( !isset( $sqlArray ) ) {
1347 $sqlArray = $this->sqlArray
;
1349 if( !isset( $sqlArray ) ) {
1353 touch($filename); // php bug
1354 $fp = fopen( $filename, "w" );
1356 foreach( $sqlArray as $key => $query ) {
1357 fwrite( $fp, $query . ";\n" );
1363 * Create an xml parser
1365 * @return object PHP XML parser object
1369 function &create_parser() {
1370 // Create the parser
1371 $xmlParser = xml_parser_create();
1372 xml_set_object( $xmlParser, $this );
1374 // Initialize the XML callback functions
1375 xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' );
1376 xml_set_character_data_handler( $xmlParser, '_tag_cdata' );
1382 * XML Callback to process start elements
1386 function _tag_open( &$parser, $tag, $attributes ) {
1387 switch( strtoupper( $tag ) ) {
1389 $this->obj
= new dbTable( $this, $attributes );
1390 xml_set_object( $parser, $this->obj
);
1393 if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) {
1394 $this->obj
= new dbQuerySet( $this, $attributes );
1395 xml_set_object( $parser, $this->obj
);
1399 // print_r( array( $tag, $attributes ) );
1405 * XML Callback to process CDATA elements
1409 function _tag_cdata( &$parser, $cdata ) {
1413 * XML Callback to process end elements
1418 function _tag_close( &$parser, $tag ) {
1423 * Converts an XML schema string to the specified DTD version.
1425 * Call this method to convert a string containing an XML schema to a different AXMLS
1426 * DTD version. For instance, to convert a schema created for an pre-1.0 version for
1427 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
1428 * parameter is specified, the schema will be converted to the current DTD version.
1429 * If the newFile parameter is provided, the converted schema will be written to the specified
1431 * @see ConvertSchemaFile()
1433 * @param string $schema String containing XML schema that will be converted.
1434 * @param string $newVersion DTD version to convert to.
1435 * @param string $newFile File name of (converted) output file.
1436 * @return string Converted XML schema or FALSE if an error occurs.
1438 function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) {
1440 // grab current version
1441 if( !( $version = $this->SchemaStringVersion( $schema ) ) ) {
1445 if( !isset ($newVersion) ) {
1446 $newVersion = $this->schemaVersion
;
1449 if( $version == $newVersion ) {
1452 // Fail if XSLT extension is not available
1453 if( ! function_exists( 'xslt_create' ) ) {
1457 $xsl_file = dirname( __FILE__
) . '/xsl/convert-' . $version . '-' . $newVersion . '.xsl';
1460 if( !is_readable( $xsl_file ) ) {
1464 $arguments = array (
1466 '/_xsl' => file_get_contents ($xsl_file)
1469 // create an XSLT processor
1470 $xh = xslt_create ();
1472 // set error handler
1473 xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
1475 // process the schema
1476 $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
1481 if( is_string ($newFile) ) {
1482 touch($newFile); // php bug
1483 if ( $fp = fopen( $newFile, 'w' ) ) {
1484 fwrite ($fp, $result);
1493 * Converts an XML schema file to the specified DTD version.
1495 * Call this method to convert the specified XML schema file to a different AXMLS
1496 * DTD version. For instance, to convert a schema created for an pre-1.0 version for
1497 * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version
1498 * parameter is specified, the schema will be converted to the current DTD version.
1499 * If the newFile parameter is provided, the converted schema will be written to the specified
1501 * @see ConvertSchemaString()
1503 * @param string $filename Name of XML schema file that will be converted.
1504 * @param string $newVersion DTD version to convert to.
1505 * @param string $newFile File name of (converted) output file.
1506 * @return string Converted XML schema or FALSE if an error occurs.
1508 function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) {
1510 // grab current version
1511 if( !( $version = $this->SchemaFileVersion( $filename ) ) ) {
1515 if( !isset ($newVersion) ) {
1516 $newVersion = $this->schemaVersion
;
1519 if( $version == $newVersion ) {
1520 $result = file_get_contents( $filename );
1522 // remove unicode BOM if present
1523 if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) {
1524 $result = substr( $result, 3 );
1527 // Fail if XSLT extension is not available
1528 if( ! function_exists( 'xslt_create' ) ) {
1532 $xsl_file = dirname( __FILE__
) . '/xsl/convert-' . $version . '-' . $newVersion . '.xsl';
1535 if( !is_readable( $xsl_file ) ) {
1539 $arguments = array (
1540 '/_xml' => file_get_contents ($filename),
1541 '/_xsl' => file_get_contents ($xsl_file)
1544 // create an XSLT processor
1545 $xh = xslt_create ();
1547 // set error handler
1548 xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler'));
1550 // process the schema
1551 $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments);
1556 if( is_string ($newFile) ) {
1557 touch($newFile); // php bug
1558 if ( $fp = fopen( $newFile, 'w' ) ) {
1559 fwrite ($fp, $result);
1568 * Processes XSLT transformation errors
1570 * @param object $parser XML parser object
1571 * @param integer $errno Error number
1572 * @param integer $level Error level
1573 * @param array $fields Error information fields
1577 function xslt_error_handler( $parser, $errno, $level, $fields ) {
1578 if( is_array( $fields ) ) {
1580 'Message Type' => ucfirst( $fields['msgtype'] ),
1581 'Message Code' => $fields['code'],
1582 'Message' => $fields['msg'],
1583 'Error Number' => $errno,
1587 switch( $fields['URI'] ) {
1589 $msg['Input'] = 'XML';
1592 $msg['Input'] = 'XSL';
1595 $msg['Input'] = $fields['URI'];
1598 $msg['Line'] = $fields['line'];
1601 'Message Type' => 'Error',
1602 'Error Number' => $errno,
1604 'Fields' => var_export( $fields, TRUE )
1608 $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n"
1611 foreach( $msg as $label => $details ) {
1612 $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n";
1615 $error_details .= '</table>';
1617 trigger_error( $error_details, E_USER_ERROR
);
1621 * Returns the AXMLS Schema Version of the requested XML schema file.
1623 * Call this method to obtain the AXMLS DTD version of the requested XML schema file.
1624 * @see SchemaStringVersion()
1626 * @param string $filename AXMLS schema file
1627 * @return string Schema version number or FALSE on error
1629 function SchemaFileVersion( $filename ) {
1631 if( !($fp = fopen( $filename, 'r' )) ) {
1632 // die( 'Unable to open file' );
1637 while( $data = fread( $fp, 4096 ) ) {
1638 if( preg_match( $this->versionRegex
, $data, $matches ) ) {
1639 return !empty( $matches[2] ) ?
$matches[2] : XMLS_DEFAULT_SCHEMA_VERSION
;
1647 * Returns the AXMLS Schema Version of the provided XML schema string.
1649 * Call this method to obtain the AXMLS DTD version of the provided XML schema string.
1650 * @see SchemaFileVersion()
1652 * @param string $xmlstring XML schema string
1653 * @return string Schema version number or FALSE on error
1655 function SchemaStringVersion( $xmlstring ) {
1656 if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) {
1660 if( preg_match( $this->versionRegex
, $xmlstring, $matches ) ) {
1661 return !empty( $matches[2] ) ?
$matches[2] : XMLS_DEFAULT_SCHEMA_VERSION
;
1668 * Extracts an XML schema from an existing database.
1670 * Call this method to create an XML schema string from an existing database.
1671 * If the data parameter is set to TRUE, AXMLS will include the data from the database
1674 * @param boolean $data Include data in schema dump
1675 * @return string Generated XML schema
1677 function ExtractSchema( $data = FALSE ) {
1678 $old_mode = $this->db
->SetFetchMode( ADODB_FETCH_NUM
);
1680 $schema = '<?xml version="1.0"?>' . "\n"
1681 . '<schema version="' . $this->schemaVersion
. '">' . "\n";
1683 if( is_array( $tables = $this->db
->MetaTables( 'TABLES' ) ) ) {
1684 foreach( $tables as $table ) {
1685 $schema .= ' <table name="' . $table . '">' . "\n";
1687 // grab details from database
1688 $rs = $this->db
->Execute( 'SELECT * FROM ' . $table . ' WHERE -1' );
1689 $fields = $this->db
->MetaColumns( $table );
1690 $indexes = $this->db
->MetaIndexes( $table );
1692 if( is_array( $fields ) ) {
1693 foreach( $fields as $details ) {
1697 if( $details->max_length
> 0 ) {
1698 $extra .= ' size="' . $details->max_length
. '"';
1701 if( $details->primary_key
) {
1702 $content[] = '<PRIMARY/>';
1703 } elseif( $details->not_null
) {
1704 $content[] = '<NOTNULL/>';
1707 if( $details->has_default
) {
1708 $content[] = '<DEFAULT value="' . $details->default_value
. '"/>';
1711 if( $details->auto_increment
) {
1712 $content[] = '<AUTOINCREMENT/>';
1715 // this stops the creation of 'R' columns,
1716 // AUTOINCREMENT is used to create auto columns
1717 $details->primary_key
= 0;
1718 $type = $rs->MetaType( $details );
1720 $schema .= ' <field name="' . $details->name
. '" type="' . $type . '"' . $extra . '>';
1722 if( !empty( $content ) ) {
1723 $schema .= "\n " . implode( "\n ", $content ) . "\n ";
1726 $schema .= '</field>' . "\n";
1730 if( is_array( $indexes ) ) {
1731 foreach( $indexes as $index => $details ) {
1732 $schema .= ' <index name="' . $index . '">' . "\n";
1734 if( $details['unique'] ) {
1735 $schema .= ' <UNIQUE/>' . "\n";
1738 foreach( $details['columns'] as $column ) {
1739 $schema .= ' <col>' . $column . '</col>' . "\n";
1742 $schema .= ' </index>' . "\n";
1747 $rs = $this->db
->Execute( 'SELECT * FROM ' . $table );
1749 if( is_object( $rs ) ) {
1750 $schema .= ' <data>' . "\n";
1752 while( $row = $rs->FetchRow() ) {
1753 $schema .= ' <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n";
1756 $schema .= ' </data>' . "\n";
1760 $schema .= ' </table>' . "\n";
1764 $this->db
->SetFetchMode( $old_mode );
1766 $schema .= '</schema>';
1771 * Sets a prefix for database objects
1773 * Call this method to set a standard prefix that will be prepended to all database tables
1774 * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix.
1776 * @param string $prefix Prefix that will be prepended.
1777 * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix.
1778 * @return boolean TRUE if successful, else FALSE
1780 function SetPrefix( $prefix = '', $underscore = TRUE ) {
1783 case empty( $prefix ):
1784 logMsg( 'Cleared prefix' );
1785 $this->objectPrefix
= '';
1788 case strlen( $prefix ) > XMLS_PREFIX_MAXLEN
:
1789 // prefix contains invalid characters
1790 case !preg_match( '/^[a-z][a-z0-9]+$/i', $prefix ):
1791 logMsg( 'Invalid prefix: ' . $prefix );
1795 if( $underscore AND substr( $prefix, -1 ) != '_' ) {
1800 logMsg( 'Set prefix: ' . $prefix );
1801 $this->objectPrefix
= $prefix;
1806 * Returns an object name with the current prefix prepended.
1808 * @param string $name Name
1809 * @return string Prefixed name
1813 function prefix( $name = '' ) {
1815 if( !empty( $this->objectPrefix
) ) {
1816 // Prepend the object prefix to the table name
1817 // prepend after quote if used
1818 return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix
. '$2', $name );
1821 // No prefix set. Use name provided.
1826 * Checks if element references a specific platform
1828 * @param string $platform Requested platform
1829 * @returns boolean TRUE if platform check succeeds
1833 function supportedPlatform( $platform = NULL ) {
1834 $regex = '/^(\w*\|)*' . $this->db
->databaseType
. '(\|\w*)*$/';
1836 if( !isset( $platform ) OR preg_match( $regex, $platform ) ) {
1837 logMsg( "Platform $platform is supported" );
1840 logMsg( "Platform $platform is NOT supported" );
1846 * Clears the array of generated SQL.
1850 function clearSQL() {
1851 $this->sqlArray
= array();
1855 * Adds SQL into the SQL array.
1857 * @param mixed $sql SQL to Add
1858 * @return boolean TRUE if successful, else FALSE.
1862 function addSQL( $sql = NULL ) {
1863 if( is_array( $sql ) ) {
1864 foreach( $sql as $line ) {
1865 $this->addSQL( $line );
1871 if( is_string( $sql ) ) {
1872 $this->sqlArray
[] = $sql;
1874 // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL.
1875 if( $this->ExecuteInline() && ( $this->success
== 2 ||
$this->ContinueOnError() ) ) {
1876 $saved = $this->db
->debug
;
1877 $this->db
->debug
= $this->debug
;
1878 $ok = $this->db
->Execute( $sql );
1879 $this->db
->debug
= $saved;
1882 if( $this->debug
) {
1883 ADOConnection
::outp( $this->db
->ErrorMsg() );
1897 * Gets the SQL array in the specified format.
1899 * @param string $format Format
1904 function getSQL( $format = NULL, $sqlArray = NULL ) {
1905 if( !is_array( $sqlArray ) ) {
1906 $sqlArray = $this->sqlArray
;
1909 if( !is_array( $sqlArray ) ) {
1913 switch( strtolower( $format ) ) {
1916 return !empty( $sqlArray ) ?
implode( ";\n\n", $sqlArray ) . ';' : '';
1918 return !empty( $sqlArray ) ?
nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : '';
1921 return $this->sqlArray
;
1925 * Destroys an adoSchema object.
1927 * Call this method to clean up after an adoSchema object that is no longer in use.
1928 * @deprecated adoSchema now cleans up automatically.
1930 function Destroy() {
1931 set_magic_quotes_runtime( $this->mgq
);
1937 * Message logging function
1941 function logMsg( $msg, $title = NULL ) {
1945 if( isset( $title ) ) {
1946 echo '<h3>' . htmlentities( $title ) . '</h3>';
1949 if( is_object( $this ) ) {
1950 echo '[' . get_class( $this ) . '] ';