diff --git a/src/Pluf/DB/Schema.php b/src/Pluf/DB/Schema.php index d24ff2b..e975c1c 100644 --- a/src/Pluf/DB/Schema.php +++ b/src/Pluf/DB/Schema.php @@ -82,6 +82,22 @@ class Pluf_DB_Schema } /** + * Creates the constraints for the current model. + * This should be done _after_ all tables of all models have been created. + * + * @throws Exception + */ + function createConstraints() + { + $sql = $this->schema->getSqlCreateConstraints($this->model); + foreach ($sql as $k => $query) { + if (false === $this->con->execute($query)) { + throw new Exception($this->con->getError()); + } + } + } + + /** * Drop the tables and indexes for the current model. * * @return mixed True if success or database error. @@ -98,6 +114,24 @@ class Pluf_DB_Schema } /** + * Drops the constraints for the current model. + * This should be done _before_ all tables of all models are dropped. + * + * @throws Exception + * @return boolean + */ + function dropConstraints() + { + $sql = $this->schema->getSqlDeleteConstraints($this->model); + foreach ($sql as $k => $query) { + if (false === $this->con->execute($query)) { + throw new Exception($this->con->getError()); + } + } + return true; + } + + /** * Given a column name or a string with column names in the format * "column1, column2, column3", returns the escaped correctly * quoted column names. This is good for index creation. diff --git a/src/Pluf/DB/Schema/MySQL.php b/src/Pluf/DB/Schema/MySQL.php index 2a5b4e2..28e692c 100644 --- a/src/Pluf/DB/Schema/MySQL.php +++ b/src/Pluf/DB/Schema/MySQL.php @@ -76,8 +76,6 @@ class Pluf_DB_Schema_MySQL $this->con = $con; } - - /** * Get the SQL to generate the tables of the given model. * @@ -127,12 +125,11 @@ class Pluf_DB_Schema_MySQL $manytomany[] = $col; } } - $sql .= "\n".'primary key (`id`)'; - $sql .= "\n".') ENGINE=MyISAM'; - $sql .=' DEFAULT CHARSET=utf8;'; + $sql .= "\n".'PRIMARY KEY (`id`))'; + $sql .= 'ENGINE=InnoDB DEFAULT CHARSET=utf8;'; $tables[$this->con->pfx.$model->_a['table']] = $sql; - - //Now for the many to many + + // Now for the many to many foreach ($manytomany as $many) { $omodel = new $cols[$many]['model'](); $hay = array(strtolower($model->_a['model']), strtolower($omodel->_a['model'])); @@ -141,8 +138,8 @@ class Pluf_DB_Schema_MySQL $sql = 'CREATE TABLE `'.$this->con->pfx.$table.'` ('; $sql .= "\n".'`'.strtolower($model->_a['model']).'_id` '.$this->mappings['foreignkey'].' default 0,'; $sql .= "\n".'`'.strtolower($omodel->_a['model']).'_id` '.$this->mappings['foreignkey'].' default 0,'; - $sql .= "\n".'primary key (`'.strtolower($model->_a['model']).'_id`, `'.strtolower($omodel->_a['model']).'_id`)'; - $sql .= "\n".') ENGINE=MyISAM'; + $sql .= "\n".'PRIMARY KEY ('.strtolower($model->_a['model']).'_id, '.strtolower($omodel->_a['model']).'_id)'; + $sql .= "\n".') ENGINE=InnoDB'; $sql .=' DEFAULT CHARSET=utf8;'; $tables[$this->con->pfx.$table] = $sql; } @@ -162,23 +159,23 @@ class Pluf_DB_Schema_MySQL if (!isset($val['col'])) { $val['col'] = $idx; } - $index[$this->con->pfx.$model->_a['table'].'_'.$idx] = + $index[$this->con->pfx.$model->_a['table'].'_'.$idx] = sprintf('CREATE INDEX `%s` ON `%s` (%s);', - $idx, $this->con->pfx.$model->_a['table'], + $idx, $this->con->pfx.$model->_a['table'], Pluf_DB_Schema::quoteColumn($val['col'], $this->con)); } foreach ($model->_a['cols'] as $col => $val) { $field = new $val['type'](); if ($field->type == 'foreignkey') { - $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_foreignkey'] = + $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_foreignkey'] = sprintf('CREATE INDEX `%s` ON `%s` (`%s`);', $col.'_foreignkey_idx', $this->con->pfx.$model->_a['table'], $col); } if (isset($val['unique']) and $val['unique'] == true) { - $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_unique'] = + $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_unique'] = sprintf('CREATE UNIQUE INDEX `%s` ON `%s` (%s);', - $col.'_unique_idx', - $this->con->pfx.$model->_a['table'], + $col.'_unique_idx', + $this->con->pfx.$model->_a['table'], Pluf_DB_Schema::quoteColumn($col, $this->con) ); } @@ -187,6 +184,70 @@ class Pluf_DB_Schema_MySQL } /** + * Workaround for which limits the + * length of foreign key identifiers to 64 characters. + * + * @param string + * @return string + */ + function getShortenedFKeyName($name) + { + if (strlen($name) <= 64) { + return $name; + } + return substr($name, 0, 55).'_'.substr(md5($name), 0, 8); + } + + /** + * Get the SQL to create the constraints for the given model + * + * @param Object Model + * @return array Array of SQL strings ready to execute. + */ + function getSqlCreateConstraints($model) + { + $table = $this->con->pfx.$model->_a['table']; + $constraints = array(); + $alter_tbl = 'ALTER TABLE '.$table; + $cols = $model->_a['cols']; + $manytomany = array(); + + foreach ($cols as $col => $val) { + $field = new $val['type'](); + // remember these for later + if ($field->type == 'manytomany') { + $manytomany[] = $col; + } + if ($field->type == 'foreignkey') { + // Add the foreignkey constraints + $referto = new $val['model'](); + $constraints[] = $alter_tbl.' ADD CONSTRAINT '.$this->getShortenedFKeyName($table.'_'.$col.'_fkey').' + FOREIGN KEY ('.$this->con->qn($col).') + REFERENCES '.$this->con->pfx.$referto->_a['table'].' (id) + ON DELETE NO ACTION ON UPDATE NO ACTION'; + } + } + + // Now for the many to many + foreach ($manytomany as $many) { + $omodel = new $cols[$many]['model'](); + $hay = array(strtolower($model->_a['model']), strtolower($omodel->_a['model'])); + sort($hay); + $table = $this->con->pfx.$hay[0].'_'.$hay[1].'_assoc'; + $alter_tbl = 'ALTER TABLE '.$table; + $constraints[] = $alter_tbl.' ADD CONSTRAINT '.$this->getShortenedFKeyName($table.'_fkey1').' + FOREIGN KEY ('.strtolower($model->_a['model']).'_id) + REFERENCES '.$this->con->pfx.$model->_a['table'].' (id) + ON DELETE NO ACTION ON UPDATE NO ACTION'; + $constraints[] = $alter_tbl.' ADD CONSTRAINT '.$this->getShortenedFKeyName($table.'_fkey2').' + FOREIGN KEY ('.strtolower($omodel->_a['model']).'_id) + REFERENCES '.$this->con->pfx.$omodel->_a['table'].' (id) + ON DELETE NO ACTION ON UPDATE NO ACTION'; + } + return $constraints; + } + + /** * Get the SQL to drop the tables corresponding to the model. * * @param Object Model @@ -204,7 +265,7 @@ class Pluf_DB_Schema_MySQL $manytomany[] = $col; } } - + //Now for the many to many foreach ($manytomany as $many) { $omodel = new $cols[$many]['model'](); @@ -214,6 +275,45 @@ class Pluf_DB_Schema_MySQL $sql .= ', `'.$this->con->pfx.$table.'`'; } return array($sql); + } + + /** + * Get the SQL to drop the constraints for the given model + * + * @param Object Model + * @return array Array of SQL strings ready to execute. + */ + function getSqlDeleteConstraints($model) + { + $table = $this->con->pfx.$model->_a['table']; + $constraints = array(); + $alter_tbl = 'ALTER TABLE '.$table; + $cols = $model->_a['cols']; + $manytomany = array(); + foreach ($cols as $col => $val) { + $field = new $val['type'](); + // remember these for later + if ($field->type == 'manytomany') { + $manytomany[] = $col; + } + if ($field->type == 'foreignkey') { + // Add the foreignkey constraints + $referto = new $val['model'](); + $constraints[] = $alter_tbl.' DROP CONSTRAINT '.$this->getShortenedFKeyName($table.'_'.$col.'_fkey'); + } + } + + // Now for the many to many + foreach ($manytomany as $many) { + $omodel = new $cols[$many]['model'](); + $hay = array(strtolower($model->_a['model']), strtolower($omodel->_a['model'])); + sort($hay); + $table = $this->con->pfx.$hay[0].'_'.$hay[1].'_assoc'; + $alter_tbl = 'ALTER TABLE '.$table; + $constraints[] = $alter_tbl.' DROP CONSTRAINT '.$this->getShortenedFKeyName($table.'_fkey1'); + $constraints[] = $alter_tbl.' DROP CONSTRAINT '.$this->getShortenedFKeyName($table.'_fkey2'); + } + return $constraints; } } diff --git a/src/Pluf/DB/Schema/PostgreSQL.php b/src/Pluf/DB/Schema/PostgreSQL.php index f7204f1..2b41a59 100644 --- a/src/Pluf/DB/Schema/PostgreSQL.php +++ b/src/Pluf/DB/Schema/PostgreSQL.php @@ -93,7 +93,6 @@ class Pluf_DB_Schema_PostgreSQL $manytomany = array(); $query = 'CREATE TABLE '.$this->con->pfx.$model->_a['table'].' ('; $sql_col = array(); - $constraints = array(); foreach ($cols as $col => $val) { $field = new $val['type'](); if ($field->type != 'manytomany') { @@ -112,21 +111,12 @@ class Pluf_DB_Schema_PostgreSQL } else { $manytomany[] = $col; } - if ($field->type == 'foreignkey') { - // Add the foreignkey constraints - $referto = new $val['model'](); - $_c = 'CONSTRAINT '.$this->con->pfx.$model->_a['table'].'_'.$col.'_fkey FOREIGN KEY ('.$this->con->qn($col).') - REFERENCES '.$this->con->pfx.$referto->_a['table'].' (id) MATCH SIMPLE - ON UPDATE NO ACTION ON DELETE NO ACTION'; - $constraints[] = $_c; - } } $sql_col[] = 'CONSTRAINT '.$this->con->pfx.$model->_a['table'].'_pkey PRIMARY KEY (id)'; - $sql_col = array_merge($sql_col, $constraints); $query = $query."\n".implode(",\n", $sql_col)."\n".');'; $tables[$this->con->pfx.$model->_a['table']] = $query; - //Now for the many to many - //FIXME add index on the second column + // Now for the many to many + // FIXME add index on the second column foreach ($manytomany as $many) { $omodel = new $cols[$many]['model'](); $hay = array(strtolower($model->_a['model']), strtolower($omodel->_a['model'])); @@ -135,7 +125,7 @@ class Pluf_DB_Schema_PostgreSQL $sql = 'CREATE TABLE '.$this->con->pfx.$table.' ('; $sql .= "\n".strtolower($model->_a['model']).'_id '.$this->mappings['foreignkey'].' default 0,'; $sql .= "\n".strtolower($omodel->_a['model']).'_id '.$this->mappings['foreignkey'].' default 0,'; - $sql .= "\n".'CONSTRAINT '.$this->con->pfx.$table.'_pkey PRIMARY KEY ('.strtolower($model->_a['model']).'_id, '.strtolower($omodel->_a['model']).'_id)'; + $sql .= "\n".'CONSTRAINT '.$this->getShortenedIdentifierName($this->con->pfx.$table.'_pkey').' PRIMARY KEY ('.strtolower($model->_a['model']).'_id, '.strtolower($omodel->_a['model']).'_id)'; $sql .= "\n".');'; $tables[$this->con->pfx.$table] = $sql; } @@ -161,20 +151,20 @@ class Pluf_DB_Schema_PostgreSQL $unique = ''; } - $index[$this->con->pfx.$model->_a['table'].'_'.$idx] = + $index[$this->con->pfx.$model->_a['table'].'_'.$idx] = sprintf('CREATE '.$unique.'INDEX %s ON %s (%s);', - $this->con->pfx.$model->_a['table'].'_'.$idx, - $this->con->pfx.$model->_a['table'], + $this->con->pfx.$model->_a['table'].'_'.$idx, + $this->con->pfx.$model->_a['table'], Pluf_DB_Schema::quoteColumn($val['col'], $this->con) ); } foreach ($model->_a['cols'] as $col => $val) { $field = new $val['type'](); if (isset($val['unique']) and $val['unique'] == true) { - $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_unique'] = + $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_unique'] = sprintf('CREATE UNIQUE INDEX %s ON %s (%s);', - $this->con->pfx.$model->_a['table'].'_'.$col.'_unique_idx', - $this->con->pfx.$model->_a['table'], + $this->con->pfx.$model->_a['table'].'_'.$col.'_unique_idx', + $this->con->pfx.$model->_a['table'], Pluf_DB_Schema::quoteColumn($col, $this->con) ); } @@ -183,6 +173,69 @@ class Pluf_DB_Schema_PostgreSQL } /** + * All identifiers in Postgres must not exceed 64 characters in length. + * + * @param string + * @return string + */ + function getShortenedIdentifierName($name) + { + if (strlen($name) <= 64) { + return $name; + } + return substr($name, 0, 55).'_'.substr(md5($name), 0, 8); + } + + /** + * Get the SQL to create the constraints for the given model + * + * @param Object Model + * @return array Array of SQL strings ready to execute. + */ + function getSqlCreateConstraints($model) + { + $table = $this->con->pfx.$model->_a['table']; + $constraints = array(); + $alter_tbl = 'ALTER TABLE '.$table; + $cols = $model->_a['cols']; + $manytomany = array(); + + foreach ($cols as $col => $val) { + $field = new $val['type'](); + // remember these for later + if ($field->type == 'manytomany') { + $manytomany[] = $col; + } + if ($field->type == 'foreignkey') { + // Add the foreignkey constraints + $referto = new $val['model'](); + $constraints[] = $alter_tbl.' ADD CONSTRAINT '.$this->getShortenedIdentifierName($table.'_'.$col.'_fkey').' + FOREIGN KEY ('.$this->con->qn($col).') + REFERENCES '.$this->con->pfx.$referto->_a['table'].' (id) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION'; + } + } + + // Now for the many to many + foreach ($manytomany as $many) { + $omodel = new $cols[$many]['model'](); + $hay = array(strtolower($model->_a['model']), strtolower($omodel->_a['model'])); + sort($hay); + $table = $this->con->pfx.$hay[0].'_'.$hay[1].'_assoc'; + $alter_tbl = 'ALTER TABLE '.$table; + $constraints[] = $alter_tbl.' ADD CONSTRAINT '.$this->getShortenedIdentifierName($table.'_fkey1').' + FOREIGN KEY ('.strtolower($model->_a['model']).'_id) + REFERENCES '.$this->con->pfx.$model->_a['table'].' (id) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION'; + $constraints[] = $alter_tbl.' ADD CONSTRAINT '.$this->getShortenedIdentifierName($table.'_fkey2').' + FOREIGN KEY ('.strtolower($omodel->_a['model']).'_id) + REFERENCES '.$this->con->pfx.$omodel->_a['table'].' (id) MATCH SIMPLE + ON UPDATE NO ACTION ON DELETE NO ACTION'; + } + return $constraints; + } + + /** * Get the SQL to drop the tables corresponding to the model. * * @param Object Model @@ -200,7 +253,7 @@ class Pluf_DB_Schema_PostgreSQL $manytomany[] = $col; } } - + //Now for the many to many foreach ($manytomany as $many) { $omodel = new $cols[$many]['model'](); @@ -211,5 +264,45 @@ class Pluf_DB_Schema_PostgreSQL } return $sql; } + + /** + * Get the SQL to drop the constraints for the given model + * + * @param Object Model + * @return array Array of SQL strings ready to execute. + */ + function getSqlDeleteConstraints($model) + { + $table = $this->con->pfx.$model->_a['table']; + $constraints = array(); + $alter_tbl = 'ALTER TABLE '.$table; + $cols = $model->_a['cols']; + $manytomany = array(); + + foreach ($cols as $col => $val) { + $field = new $val['type'](); + // remember these for later + if ($field->type == 'manytomany') { + $manytomany[] = $col; + } + if ($field->type == 'foreignkey') { + // Add the foreignkey constraints + $referto = new $val['model'](); + $constraints[] = $alter_tbl.' DROP CONSTRAINT '.$this->getShortenedIdentifierName($table.'_'.$col.'_fkey'); + } + } + + // Now for the many to many + foreach ($manytomany as $many) { + $omodel = new $cols[$many]['model'](); + $hay = array(strtolower($model->_a['model']), strtolower($omodel->_a['model'])); + sort($hay); + $table = $this->con->pfx.$hay[0].'_'.$hay[1].'_assoc'; + $alter_tbl = 'ALTER TABLE '.$table; + $constraints[] = $alter_tbl.' DROP CONSTRAINT '.$this->getShortenedIdentifierName($table.'_fkey1'); + $constraints[] = $alter_tbl.' DROP CONSTRAINT '.$this->getShortenedIdentifierName($table.'_fkey2'); + } + return $constraints; + } } diff --git a/src/Pluf/DB/Schema/SQLite.php b/src/Pluf/DB/Schema/SQLite.php index 02f1b8b..71dd1ae 100644 --- a/src/Pluf/DB/Schema/SQLite.php +++ b/src/Pluf/DB/Schema/SQLite.php @@ -129,7 +129,7 @@ class Pluf_DB_Schema_SQLite } $query = $query."\n".implode(",\n", $sql_col)."\n".');'; $tables[$this->con->pfx.$model->_a['table']] = $query; - + //Now for the many to many foreach ($manytomany as $many) { $omodel = new $cols[$many]['model'](); @@ -147,6 +147,18 @@ class Pluf_DB_Schema_SQLite } /** + * SQLite cannot add foreign key constraints to already existing tables, + * so we skip their creation completely. + * + * @param Object Model + * @return array + */ + function getSqlCreateConstraints($model) + { + return array(); + } + + /** * Get the SQL to generate the indexes of the given model. * * @param Object Model @@ -160,28 +172,28 @@ class Pluf_DB_Schema_SQLite $val['col'] = $idx; } $unique = (isset($val['type']) && ($val['type'] == 'unique')) ? 'UNIQUE ' : ''; - $index[$this->con->pfx.$model->_a['table'].'_'.$idx] = + $index[$this->con->pfx.$model->_a['table'].'_'.$idx] = sprintf('CREATE %sINDEX %s ON %s (%s);', $unique, - $this->con->pfx.$model->_a['table'].'_'.$idx, - $this->con->pfx.$model->_a['table'], + $this->con->pfx.$model->_a['table'].'_'.$idx, + $this->con->pfx.$model->_a['table'], Pluf_DB_Schema::quoteColumn($val['col'], $this->con) ); } foreach ($model->_a['cols'] as $col => $val) { $field = new $val['type'](); if ($field->type == 'foreignkey') { - $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_foreignkey'] = + $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_foreignkey'] = sprintf('CREATE INDEX %s ON %s (%s);', - $this->con->pfx.$model->_a['table'].'_'.$col.'_foreignkey_idx', - $this->con->pfx.$model->_a['table'], + $this->con->pfx.$model->_a['table'].'_'.$col.'_foreignkey_idx', + $this->con->pfx.$model->_a['table'], Pluf_DB_Schema::quoteColumn($col, $this->con)); } if (isset($val['unique']) and $val['unique'] == true) { - $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_unique'] = + $index[$this->con->pfx.$model->_a['table'].'_'.$col.'_unique'] = sprintf('CREATE UNIQUE INDEX %s ON %s (%s);', - $this->con->pfx.$model->_a['table'].'_'.$col.'_unique_idx', - $this->con->pfx.$model->_a['table'], + $this->con->pfx.$model->_a['table'].'_'.$col.'_unique_idx', + $this->con->pfx.$model->_a['table'], Pluf_DB_Schema::quoteColumn($col, $this->con) ); } @@ -207,7 +219,7 @@ class Pluf_DB_Schema_SQLite $manytomany[] = $col; } } - + //Now for the many to many foreach ($manytomany as $many) { $omodel = new $cols[$many]['model'](); @@ -217,7 +229,18 @@ class Pluf_DB_Schema_SQLite $sql[] = 'DROP TABLE IF EXISTS '.$this->con->pfx.$table; } return $sql; + } + /** + * SQLite cannot drop foreign keys from existing tables, + * so we skip their deletion completely. + * + * @param Object Model + * @return array + */ + function getSqlDeleteConstraints($model) + { + return array(); } }