User:๐Ÿ˜‚/Abstract schema

From mediawiki.org

This is all very early in the design process. We may decide this sucks and start over. Not intended to be used by any sane person right now. You've been warned.


<?php
/**
 * Schema - Abstracted database schema for MediaWiki
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * @author Chad Horohoe <chad@anyonecanedit.org>
 * @todo Handle custom table options, eg: MyISAM for searchindex, MAX_ROWS, etc
 * @todo Handle lengths on indexes, eg: el_from, el_to(40)
 * @todo FOLLOWING TABLES NEED WORK:
 *		-searchindex, hitcounter (custom table options)
 *		-externallinks, ipblocks, oldimage (indexes)
 */

abstract class Schema {

	/**
	 * Field types
	 */
	const TYPE_INT       = 1;
	const TYPE_VARCHAR   = 2;
	const TYPE_DATETIME  = 3; // On Postgres this is DATETIME. MySQL we use binary(14)
	const TYPE_TINYTEXT  = 4;
	const TYPE_BLOB      = 5;
	const TYPE_BINARY    = 6;
	const TYPE_VARBINARY = 7;
	const TYPE_BOOL      = 8;
	const TYPE_ENUM      = 9;

	// Final SQL to be output
	private $outputSql = '';

	// If at any point we fail, set this to false
	protected $isOk = true;

	// The prefix used for all tables
	protected $tblPrefix = '';

	// Any options for the table creation. Things like ENGINE=InnoDB
	protected $tblOptions = '';

	/**
	 * The actual database definition itself. A multi-dimensional associative
	 * array containing the tables and rows. The top-level keys are the table
	 * names (without prefixes). The value for this is a 3-tuple:
	 * 1) prefix - being the prefix for all the columns in the table
	 *    (eg: "cl" for categorylinks)
	 * 2) columns - an array of column name => definition, where definition is
	 *    an associative array of properties and their values
	 * 3) indexes - an array of index name => array of columns to index
	 *
	 */
	private $tables = array(
		'user' => array(
			'prefix' => 'user',
			'fields' => array(
				'id' => array(
					'type'           => self::TYPE_INT,
					'null'           => false,
					'auto-increment' => true,
					'primary-key'    => true,
					'signed'         => false,
				),
				'name' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'null'    => false,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
				'real_name' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'null'    => false,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
				'password' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'new_password' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'newpass_time' => array(
					'type' => self::TYPE_DATETIME,
				),
				'email' => array(
					'type' => self::TYPE_TINYTEXT,
					'null' => false,
				),
				'options' => array(
					'type' => self::TYPE_BLOB,
					'null' => false,
				),
				'touched' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'token' => array(
					'type'    => self::TYPE_BINARY,
					'length'  => 32,
					'null'    => false,
					'default' => '',
				),
				'email_authenticated' => array(
					'type' => self::TYPE_DATETIME,
				),
				'email_token' => array(
					'type'   => self::TYPE_BINARY,
					'length' => 32,
				),
				'email_token_expires' => array(
					'type' => self::TYPE_DATETIME,
				),
				'registration' => array(
					'type' => self::TYPE_DATETIME,
				),
				'editcount' => array(
					'type' => self::TYPE_INT,
				),
			),
			'indexes' => array(
				'user_name' => array(
					'UNIQUE', 'name',
				),
				'user_email_token' => array(
					'email_token',
				),
			)
		),
		'user_groups' => array(
			'prefix' => 'ug',
			'fields' => array(
				'user' => array(
					'type'        => self::TYPE_INT,
					'null'        => false,
					'primary-key' => true,
					'default'     => 0,
					'signed'      => false,
				),
				'group' => array(
					'type'    => self::TYPE_VARBINARY,
					'length'  => 16,
					'null'    => false,
					'default' => '',
				),
			),
			'indexes' => array(
				'ug_user_group' => array(
					'UNIQUE', 'user', 'group',
				),
				'ug_group' => array(
					'group',
				),
			),
		),
		'user_newtalk' => array(
			'prefix' => 'user',
			'fields' => array(
				'id' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'ip' => array(
					'type'    => self::TYPE_VARBINARY,
					'null'    => false,
					'length'  => 40,
					'default' => '',
				),
				'last_timestamp' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
			),
			'indexes' => array(
				'un_user_id' => array(
					'id',
				),
				'un_user_ip' => array(
					'ip',
				),
			)
		),
		'user_properties' => array(
			'prefix' => 'up',
			'fields' => array(
				'id' => array(
					'type' => self::TYPE_INT,
					'null' => false,
				),
				'property' => array(
					'type'   => self::TYPE_VARBINARY,
					'null'   => false,
					'length' => 32,
				),
				'value' => array(
					'type' => self::TYPE_BLOB,
				),
			),
			'indexes' => array(
				'user_properties_user_property' => array(
					'UNIQUE', 'user', 'property',
				),
				'user_properties_property' => array(
					'property',
				),
			),
		),
		'page' => array(
			'prefix' => 'page',
			'fields' => array(
				'id' => array(
					'type'           => self::TYPE_INT,
					'null'           => false,
					'auto-increment' => true,
					'primary-key'    => true,
					'signed'         => false,
				),
				'namespace' => array(
					'type' => self::TYPE_INT,
					'null' => false,
				),
				'title' => array(
					'type'   => self::TYPE_VARCHAR,
					'length' => 255,
					'binary' => true,
					'null'   => false,
				),
				'restrictions' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'counter' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'big',
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'is_redirect' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'tiny',
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'is_new' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'tiny',
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'random' => array(
					'type' => 'real unsigned',
					'null' => false,
				),
				'touched' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'latest' => array(
					'type'   => self::TYPE_INT,
					'null'   => false,
					'signed' => false,
				),
				'len' => array(
					'type'   => self::TYPE_INT,
					'null'   => false,
					'signed' => false,
				),
			),
			'indexes' => array(
				'name_title' => array(
					'UNIQUE', 'namespace', 'title',
				),
				'page_random' => array(
					'random',
				),
				'page_len' => array(
					'len',
				),
			),
		),
		'revision' => array(
			'prefix' => 'rev',
			'fields' => array(
				'id' => array(
					'type'           => self::TYPE_INT,
					'null'           => false,
					'auto-increment' => true,
					'primary-key'    => true,
					'signed'         => false,
				),
				'page' => array(
					'type'   => self::TYPE_INT,
					'null'   => false,
					'signed' => false,
				),
				'text_id' => array(
					'type'   => self::TYPE_INT,
					'null'   => false,
					'signed' => false,
				),
				'comment' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'user' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'user_text' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
				'timestamp' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'minor_edit' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'tiny',
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'deleted' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'tiny',
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'len' => array(
					'type'    => self::TYPE_INT,
					'default' => null,
					'signed'  => false,
				),
			),
			'indexes' => array(
				'rev_page_id' => array(
					'UNIQUE', 'page','id',
				),
				'rev_timestamp' => array(
					'timestamp',
				),
				'page_timestamp' => array(
					'page', 'timestamp',
				),
				'user_timestamp' => array(
					'user', 'timestamp',
				),
				'usertext_timestamp' => array(
					'user_text', 'timestamp',
				),
			),
		),
		'text' => array(
			'prefix' => 'old',
			'fields' => array(
				'id' => array(
					'type'           => self::TYPE_INT,
					'null'           => false,
					'auto-increment' => true,
					'primary-key'    => true,
					'signed'         => false,
				),
				'text' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'medium',
					'null'   => false,
				),
				'flags' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
			),
			'indexes' => array(),
		),
		'archive' => array(
			'prefix' => 'ar',
			'fields' => array(
				'namespace' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'title' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
				'text' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'medium',
					'null'   => false,
				),
				'comment' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'user' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'user_text' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
				),
				'timestamp' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'minor_edit' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'tiny',
					'null'    => false,
					'default' => 0,
				),
				'flags' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'rev_id' => array(
					'type'   => self::TYPE_INT,
					'signed' => false,
				),
				'text_id' => array(
					'type'   => self::TYPE_INT,
					'signed' => false,
				),
				'deleted' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'tiny',
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'len' => array(
					'type'   => self::TYPE_INT,
					'signed' => false,
				),
				'page_id' => array(
					'type'   => self::TYPE_INT,
					'signed' => false,
				),
				'parent_id' => array(
					'type'    => self::TYPE_INT,
					'default' => null,
					'signed'  => false,
				),
			),
			'indexes' => array(
				'name_title_timestamp' => array(
					'namespace', 'title', 'timestamp',
				),
				'ar_usertext_timestamp' => array(
					'user_text', 'timestamp',
				),
			),
		),
		'pagelinks' => array(
			'prefix' => 'pl',
			'fields' => array(
				'from' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'namespace' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'title' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
			),
			'indexes' => array(
				'pl_from' => array(
					'UNIQUE', 'from','namespace', 'title',
				),
				'pl_namespace' => array(
					'UNIQUE', 'namespace','title', 'from',
				),
			),
		),
		'templatelinks' => array(
			'prefix' => 'tl',
			'fields' => array(
				'from' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'namespace' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'title' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
			),
			'indexes' => array(
				'pl_from' => array(
					'UNIQUE', 'from','namespace', 'title',
				),
				'pl_namespace' => array(
					'UNIQUE', 'namespace','title', 'from',
				),
			),
		),
		'imagelinks' => array(
			'prefix' => 'il',
			'fields' => array(
				'from' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'to' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
			),
			'indexes' => array(
				'il_from' => array(
					'UNIQUE', 'from','to',
				),
				'il_namespace' => array(
					'UNIQUE', 'to', 'from',
				),
			),
		),
		'categorylinks' => array(
			'prefix' => 'cl',
			'fields' => array(
				'from' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
				'to' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
				'sortkey' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 70,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
			),
			'indexes' => array(
				'cl_from' => array(
					'UNIQUE', 'from','to',
				),
				'cl_sortkey' => array(
					'to', 'sortkey', 'from',
				),
				'cl_timestamp' => array(
					'to', 'timestamp',
				),
			),
		),
		'category' => array(
			'prefix' => 'cat',
			'fields' => array(
				'id' => array(
					'type'           => self::TYPE_INT,
					'null'           => false,
					'auto-increment' => true,
					'primary-key'    => true,
					'signed'         => false,
				),
				'title' => array(
					'type'   => self::TYPE_VARCHAR,
					'length' => 255,
					'binary' => true,
					'null'   => false,
				),
				'pages' => array(
					'type'    => self::TYPE_INT,
					'signed'  => true,
					'null'    => false,
					'default' => 0,
				),
				'subcats' => array(
					'type'    => self::TYPE_INT,
					'signed'  => true,
					'null'    => false,
					'default' => 0,
				),
				'files' => array(
					'type'    => self::TYPE_INT,
					'signed'  => true,
					'null'    => false,
					'default' => 0,
				),
				'hidden' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'tiny',
					'null'    => false,
					'default' => 0,
					'signed'  => false,
				),
			),
			'prefixes' => array(
				'cat_title' => array(
					'UNIQUE', 'title'
				),
				'cat_pages' => array(
					'pages'
				)
			),
		),
		'externallinks' => array(
			'prefix' => 'el',
			'fields' => array(
				'from' => array(
					'type'    => self::TYPE_INT,
					'default' => 0,
					'null'    => false,
					'signed'  => false,
				),
				'to' => array(
					'type' => self::TYPE_BLOB,
					'null' => false,
				),
				'index' => array(
					'type' => self::TYPE_BLOB,
					'null' => false,
				),
			),
			'indexes' => array(

			),
		),
		'externaluser' => array(
			'prefix' => 'eu',
			'fields' => array(
				'local_id' => array(
					'type'        => self::TYPE_INT,
					'null'        => false,
					'primary-key' => true,
					'signed'      => false,
				),
				'external_id' => array(
					'type'   => self::TYPE_VARCHAR,
					'length' => 255,
					'binary' => true,
					'null'   => false,
				),
			),
			'indexes' => array(
				'eu_external_id' => array(
					'UNIQUE', 'external_id'
				),
			),
		),
		'langlinks' => array(
			'prefix' => 'll',
			'fields' => array(
				'from' => array(
					'type'    => self::TYPE_INT,
					'default' => 0,
					'null'    => false,
					'signed'  => false,
				),
				'lang' => array(
					'type'    => self::TYPE_VARBINARY,
					'length'  => 20,
					'null'    => false,
					'default' => '',
				),
				'title' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
			),
			'indexes' => array(
				'll_from' => array(
					'UNIQUE', 'from', 'lang'
				),
				'll_lang' => array(
					'lang', 'title'
				),
			),
		),
		'site_stats' => array(
			'prefix' => 'ss',
			'fields' => array(
				'row_id' => array(
					'type'   => self::TYPE_INT,
					'signed' => false,
					'null'   => false,
				),
				'total_views' => array(
					'type'    => self::TYPE_INT,
					'signed'  => false,
					'length'  => 'big',
					'default' => 0,
				),
				'total_edits' => array(
					'type'    => self::TYPE_INT,
					'signed'  => false,
					'length'  => 'big',
					'default' => 0,
				),
				'good_articles' => array(
					'type'    => self::TYPE_INT,
					'signed'  => false,
					'length'  => 'big',
					'default' => 0,
				),
				'total_pages' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'big',
					'default' => -1,
				),
				'users' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'big',
					'default' => -1,
				),
				'active_users' => array(
					'type'    => self::TYPE_INT,
					'length'  => 'big',
					'default' => -1,
				),
				'admins' => array(
					'type'    => self::TYPE_INT,
					'default' => -1,
				),
				'images' => array(
					'type'    => self::TYPE_INT,
					'default' => 0,
				),
			),
			'indexes' => array(
				'ss_row_id' => array(
					'UNIQUE', 'row_id'
				)
			),
		),
		'hitcounter' => array(
			'prefix' => 'hc',
			'fields' => array(
				'id' => array(
					'type'   => self::TYPE_INT,
					'signed' => false,
					'null'   => false,
				),
			),
		),
		'ipblocks' => array(
			'prefix' => 'ipb',
			'fields' => array(
				'id' => array(
					'type'           => self::TYPE_INT,
					'null'           => false,
					'auto-increment' => true,
					'primary-key'    => true,
				),
				'address' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'user' => array(
					'type'    => self::TYPE_INT,
					'default' => 0,
					'null'    => false,
					'signed'  => false,
				),
				'by' => array(
					'type'    => self::TYPE_INT,
					'default' => 0,
					'null'    => false,
					'signed'  => false,
				),
				'by_text' => array(
					'type'    => self::TYPE_VARCHAR,
					'length'  => 255,
					'binary'  => true,
					'null'    => false,
					'default' => '',
				),
				'reason' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'timestamp' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'auto' => array(
					'type'    => self::TYPE_BOOL,
					'null'    => false,
					'default' => 0,
				),
				'anon_only' => array(
					'type'    => self::TYPE_BOOL,
					'null'    => false,
					'default' => 0,
				),
				'create_account' => array(
					'type'    => self::TYPE_BOOL,
					'null'    => false,
					'default' => 1,
				),
				'enable_autoblock' => array(
					'type'    => self::TYPE_BOOL,
					'null'    => false,
					'default' => 1,
				),
				'expiry' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'range_start' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'range_end' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'deleted' => array(
					'type'    => self::TYPE_BOOL,
					'null'    => false,
					'default' => 0,
				),
				'block_email' => array(
					'type'    => self::TYPE_BOOL,
					'null'    => false,
					'default' => 0,
				),
				'allow_usertalk' => array(
					'type'    => self::TYPE_BOOL,
					'null'    => false,
					'default' => 0,
				),
			),
			'indexes' => array(

			),
		),
		'image' => array(
			'prefix' => 'img',
			'fields' => array(
				'name' => array(
					'type'        => self::TYPE_VARCHAR,
					'length'      => 255,
					'binary'      => true,
					'null'        => false,
					'default'     => '',
					'primary-key' => true,
				),
				'size' => array(
					'type'    => self::TYPE_INT,
					'signed'  => false,
					'null'    => false,
					'default' => 0,
				),
				'width' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'height' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'metadata' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'medium',
					'null'   => false,
				),
				'bits' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'media_type' => array(
					'type'   => self::TYPE_ENUM,
					'values' => array(
						"UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", 
						"MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE"
					),
					'default' => null,
				),
				'major_mime' => array(
					'type'   => self::TYPE_ENUM,
					'null'   => false,
					'values' => array(
						"unknown", "application", "audio", "image", "text",
						"video", "message", "model", "multipart"
					),
					'default' => 'unknown',
				),
				'minor_mime' => array(
					'type'     => self::TYPE_VARBINARY,
					'length'   => 32,
					'null'     => false,
					'defaullt' => 'unknown',
				),
				'description' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'user' => array(
					'type'    => self::TYPE_INT,
					'signed'  => false,
					'null'    => false,
					'default' => 0,
				),
				'user_text' => array(
					'type'   => self::TYPE_VARCHAR,
					'length' => 255,
					'binary' => true,
					'null'   => false,
				),
				'timestamp' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'sha1' => array(
					'type'     => self::TYPE_VARBINARY,
					'length'   => 32,
					'null'     => false,
					'defaullt' => '',
				),
			),
			'indexes' => array(
				'img_usertext_timestamp' => array(
					'user_text', 'timestamp'
				),
				'img_size' => array(
					'size'
				),
				'img_timestamp' => array(
					'timestamp'
				),
				'img_sha1' => array(
					'sha1'
				),
			),
		),
		'oldimage' => array(
			'prefix' => 'oi',
			'fields' => array(
				'name' => array(
					'type'        => self::TYPE_VARCHAR,
					'length'      => 255,
					'binary'      => true,
					'null'        => false,
					'default'     => '',
					'primary-key' => true,
				),
				'archive_name' => array(
					'type'        => self::TYPE_VARCHAR,
					'length'      => 255,
					'binary'      => true,
					'null'        => false,
					'default'     => '',
					'primary-key' => true,
				),
				'size' => array(
					'type'    => self::TYPE_INT,
					'signed'  => false,
					'null'    => false,
					'default' => 0,
				),
				'width' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'height' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'metadata' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'medium',
					'null'   => false,
				),
				'bits' => array(
					'type'    => self::TYPE_INT,
					'null'    => false,
					'default' => 0,
				),
				'media_type' => array(
					'type'   => self::TYPE_ENUM,
					'values' => array(
						"UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO",
						"MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE"
					),
					'default' => null,
				),
				'major_mime' => array(
					'type'   => self::TYPE_ENUM,
					'null'   => false,
					'values' => array(
						"unknown", "application", "audio", "image", "text",
						"video", "message", "model", "multipart"
					),
					'default' => 'unknown',
				),
				'minor_mime' => array(
					'type'     => self::TYPE_VARBINARY,
					'length'   => 32,
					'null'     => false,
					'defaullt' => 'unknown',
				),
				'description' => array(
					'type'   => self::TYPE_BLOB,
					'length' => 'tiny',
					'null'   => false,
				),
				'user' => array(
					'type'    => self::TYPE_INT,
					'signed'  => false,
					'null'    => false,
					'default' => 0,
				),
				'user_text' => array(
					'type'   => self::TYPE_VARCHAR,
					'length' => 255,
					'binary' => true,
					'null'   => false,
				),
				'timestamp' => array(
					'type'    => self::TYPE_DATETIME,
					'null'    => false,
					'default' => '',
				),
				'sha1' => array(
					'type'     => self::TYPE_VARBINARY,
					'length'   => 32,
					'null'     => false,
					'defaullt' => '',
				),
			),
			'indexes' => array(
				'oi_usertext_timestamp' => array(
					'user_text', 'timestamp'
				),
				'oi_name_timestamp' => array(
					'name', 'timestamp'
				),
				'oi_name_archive_name' => array(
					'name', 'archive_name'
				),
				'oi_sha1' => array(
					'sha1'
				),
			),
		),
	);

	/**
	 * Constructor. A no-op right now, but might do something someday. We hide
	 * it so people don't try to make their own schema classes. Use a sane entry
	 * point, like newForType()
	 */
	private function __construct() {}

	/**
	 * Get a brand new Schema for a given DB type
	 *
	 * @param $type String A database type (eg: mysql, postgres, sqlite)
	 * @return Schema subclass
	 */
	public static function newForType( $type ) {
		$class = ucfirst( strtolower( $type ) ) . 'Schema';
		if ( !class_exists( $class ) ) {
			throw new Exception( "No such database class $class, given type $type" );
		}
		else {
			return new $class();
		}
	}

	/**
	 * Top-level create method. Loops the tables and passes them to the child
	 * classes for implementation
	 * @return boolean
	 */
	public function generateTables() {
		foreach( $this->tables as $name => $definition ) {
			$this->outputSql .= $this->defineTable( $name, $definition );
		}
		return $this->isOk;
	}

	/**
	 * Similar to generateTables(), but only generates SQL for tables that do not exist
	 * @param $db Database object
	 * @return boolean
	 */
	public function generateMissingTables( DatabaseBase $db ) {
		foreach( $this->tables as $name => $definition ) {
			if( $db->tableExists( $name ) ) {
				continue;
			}
			$this->outputSql .= $this->defineTable( $name, $definition );
		}
		return $this->isOk;
	}

	/**
	 * Get the final DBMS-specific SQL
	 * @return string
	 */
	public function getSql() {
		return $this->outputSql;
	}

	public function setTablePrefix( $prefix ) {
		$this->tblPrefix = $prefix;
	}

	public function setTableOptions( $opts ) {
		$this->tblOptions = $opts;
	}

	/**
	 * Get table options. Mostly for MySQL
	 * @return String
	 */
	protected function getTableOptions() {
		return $this->tblOptions;
	}

	/**
	 * Given an abstract table definition, return a DBMS-specficic command to
	 * create it. All child classes need to implement this
	 * @param $name The name of the table, like 'page' or 'revision'
	 * @param $definition Array An abstract table definition
	 */
	abstract protected function defineTable( $name, $definition );

	abstract protected function getFieldDefinition( $attributes );
}


class MysqlSchema extends Schema {
	protected function defineTable( $name, $def ) {
		$prefix = $def['prefix'];
		$tblName = $this->tblPrefix . $name;
		$sql = "CREATE TABLE `$tblName` (";
		foreach( $def['fields'] as $field => $attribs ) {
			$sql .= "\n\t{$prefix}_$field " . $this->getFieldDefinition( $attribs );
			if( isset( $attribs['null'] ) ) {
				$sql .= $attribs['null'] ? ' NULL ' : ' NOT NULL';
			}
			// Use array_key_exists() since 'default' might be set to null
			if( !is_array( $attribs ) ) {
				die( var_dump( $sql, $def ) );
			}
			if( array_key_exists( 'default', $attribs ) ) {
				if( $attribs['default'] === null ) {
					$sql .= ' default NULL';
				} else {
					$sql .= " default '" . $attribs['default'] . "'";
				}
			}
			if( isset( $attribs['primary-key'] ) && $attribs['primary-key'] ) {
				$sql .= " PRIMARY KEY";
			}
			if( isset( $attribs['auto-increment'] ) && $attribs['auto-increment'] ) {
				$sql .= " AUTO_INCREMENT";
			}
			$sql .= ",";
		}
		$sql = rtrim( $sql, ',' );
		$sql .= "\n) " . $this->getTableOptions() . ";\n";
		if( isset( $def['indexes'] ) ) {
			foreach( $def['indexes'] as $idx => $idxDef ) {
				if( $idxDef[0] === 'UNIQUE' ) {
					array_shift( $idxDef );
					$sql .= "CREATE UNIQUE INDEX ";
				} else {
					$sql .= "CREATE INDEX ";
				}
				$sql .= "{$prefix}_{$idx} ON $tblName (";
				foreach( $idxDef as $col ) {
					$sql .= "{$prefix}_{$col},";
				}
				$sql = rtrim( $sql, ',' );
				$sql .= ");\n";
			}
		}
		return $sql . "\n";
	}
	protected function getFieldDefinition( $attribs ) {
		$fieldType = $attribs['type'];
		$def = '';
		if( is_string( $fieldType ) ) {
			return $fieldType;
		}
		switch( $fieldType ) {
			case self::TYPE_INT:
				$def = 'int';
				if( isset( $attribs['length'] ) ) {
					$def = $attribs['length'] . $def;
				}
				if( isset( $attribs['signed'] ) ) {
					$def .= $attribs['signed'] ? ' signed' : ' unsigned';
				}
				break;
			case self::TYPE_VARCHAR:
				$def = 'varchar(' . $attribs['length'] . ')';
				break;
			case self::TYPE_DATETIME:
				$def = 'binary(14)';
				break;
			case self::TYPE_TINYTEXT:
				$def = 'tinytext';
				break;
			case self::TYPE_BLOB:
				$def = 'blob';
				if( isset( $attribs['length'] ) ) {
					$def = $attribs['length'] . $def;
				}
				break;
			case self::TYPE_BINARY:
				$def = 'binary(' . $attribs['length'] . ')';
				break;
			case self::TYPE_VARBINARY:
				$def = 'varbinary(' . $attribs['length'] . ')';
				break;
			case self::TYPE_BOOL:
				$def = 'bool';
				break;
			case self::TYPE_ENUM:
				$def = 'ENUM("' . implode( '", "', $attribs['values'] );
				$def = rtrim( $def, ', "' ) . '")';
				break;
			default:
				$this->isOk = false;
		}
		if( isset( $attribs['binary'] ) && $attribs['binary'] === true ) {
			$def = $def . ' binary';
		}
		return $def;
	}
}

echo( "\n" );
$schema = Schema::newForType( 'mysql' );
$schema->setTablePrefix( 'mw_' );
// $schema->setTableOptions( 'ENGINE=InnoDB, DEFAULT CHARSET=binary' );
if( $schema->generateTables() ) {
	echo( $schema->getSql() );
} else {
	echo( "Failed :(" );
}
echo( "\n\n" );