Skip to content

Commit

Permalink
MDL-29314 enforce index size limits
Browse files Browse the repository at this point in the history
  • Loading branch information
skodak committed Sep 16, 2011
1 parent 9cfaebb commit b2cfdcf
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
109 changes: 109 additions & 0 deletions lib/ddl/simpletest/testddl.php
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,115 @@ public function test_reserved_words() {
$this->assertTrue(count($reserved) > 1);
}

public function test_index_max_bytes() {
$DB = $this->tdb;
$dbman = $DB->get_manager();

$maxstr = '';
for($i=0; $i<255; $i++) {
$maxstr .= ''; // random long string that should fix exactly the limit for one char column
}

$table = new xmldb_table('testtable');
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_index('name', XMLDB_INDEX_NOTUNIQUE, array('name'));

// Drop if exists
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
$dbman->create_table($table);
$tablename = $table->getName();
$this->tables[$tablename] = $table;

$rec = new stdClass();
$rec->name = $maxstr;

$id = $DB->insert_record($tablename, $rec);
$this->assertTrue(!empty($id));

$rec = $DB->get_record($tablename, array('id'=>$id));
$this->assertIdentical($rec->name, $maxstr);

$dbman->drop_table($table);


$table = new xmldb_table('testtable');
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name', XMLDB_TYPE_CHAR, 255+1, null, XMLDB_NOTNULL, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_index('name', XMLDB_INDEX_NOTUNIQUE, array('name'));

try {
$dbman->create_table($table);
$this->assertTrue(false);
} catch (Exception $e) {
$this->assertTrue($e instanceof coding_exception);
}
}

public function test_index_composed_max_bytes() {
$DB = $this->tdb;
$dbman = $DB->get_manager();

$maxstr = '';
for($i=0; $i<200; $i++) {
$maxstr .= '';
}
$reststr = '';
for($i=0; $i<133; $i++) {
$reststr .= '';
}

$table = new xmldb_table('testtable');
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name1', XMLDB_TYPE_CHAR, 200, null, XMLDB_NOTNULL, null);
$table->add_field('name2', XMLDB_TYPE_CHAR, 133, null, XMLDB_NOTNULL, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_index('name1-name2', XMLDB_INDEX_NOTUNIQUE, array('name1','name2'));

// Drop if exists
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
$dbman->create_table($table);
$tablename = $table->getName();
$this->tables[$tablename] = $table;

$rec = new stdClass();
$rec->name1 = $maxstr;
$rec->name2 = $reststr;

$id = $DB->insert_record($tablename, $rec);
$this->assertTrue(!empty($id));

$rec = $DB->get_record($tablename, array('id'=>$id));
$this->assertIdentical($rec->name1, $maxstr);
$this->assertIdentical($rec->name2, $reststr);


$table = new xmldb_table('testtable');
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('name1', XMLDB_TYPE_CHAR, 201, null, XMLDB_NOTNULL, null);
$table->add_field('name2', XMLDB_TYPE_CHAR, 133, null, XMLDB_NOTNULL, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_index('name1-name2', XMLDB_INDEX_NOTUNIQUE, array('name1','name2'));

// Drop if exists
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}

try {
$dbman->create_table($table);
$this->assertTrue(false);
} catch (Exception $e) {
$this->assertTrue($e instanceof coding_exception);
}
}

// Following methods are not supported == Do not test
/*
public function testRenameIndex() {
Expand Down
3 changes: 3 additions & 0 deletions lib/ddl/sql_generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ public function getCreateTableSQL($xmldb_table) {
* needed to create it (in array)
*/
public function getCreateIndexSQL($xmldb_table, $xmldb_index) {
if ($error = $xmldb_index->validateDefinition($xmldb_table)) {
throw new coding_exception($error);
}

$unique = '';
$suffix = 'ix';
Expand Down
85 changes: 85 additions & 0 deletions lib/xmldb/xmldb_index.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ class xmldb_index extends xmldb_object {
var $unique;
var $fields;

/**
* Note:
* - MySQL: MyISAM has a limit of 1000 bytes for any key including composed, InnoDB has limit 3500 bytes.
*
* @const max length of composed indexes, one utf-8 char is 3 bytes in the worst case
*/
const INDEX_COMPOSED_MAX_BYTES = 999;

/**
* Note:
* - MySQL: InnoDB limits size of index on single column to 767bytes (256 chars)
*
* @const single column index length limit, one utf-8 char is 3 bytes in the worst case
*/
const INDEX_MAX_BYTES = 765;

/**
* Creates one new xmldb_index
*/
Expand Down Expand Up @@ -270,6 +286,75 @@ function readableInfo() {

return $o;
}

/**
* Validates the index restrictions.
*
* The error message should not be localised because it is intended for developers,
* end users and admins should never see these problems!
*
* @param xmldb_table $xmldb_table optional when object is table
* @return string null if ok, error message if problem found
*/
function validateDefinition(xmldb_table $xmldb_table=null) {
if (!$xmldb_table) {
return 'Invalid xmldb_index->validateDefinition() call, $xmldb_table si required.';
}

$total = 0;
foreach ($this->getFields() as $fieldname) {
if (!$field = $xmldb_table->getField($fieldname)) {
// argh, we do not have the fields loaded yet, this should not happen during install
continue;
}

switch ($field->getType()) {
case XMLDB_TYPE_INTEGER:
$total += 8; // big int
break;

case XMLDB_TYPE_NUMBER:
$total += 12; // this is just a guess
break;

case XMLDB_TYPE_FLOAT:
$total += 8; // double precision
break;

case XMLDB_TYPE_CHAR:
if ($field->getLength() > self::INDEX_MAX_BYTES / 3) {
return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: XMLDB_TYPE_CHAR field "'.$field->getName().'" can not be indexed because it is too long.'
.' Limit is '.(self::INDEX_MAX_BYTES/3).' chars.';
}
$total += ($field->getLength() * 3); // the most complex utf-8 chars have 3 bytes
break;

case XMLDB_TYPE_TEXT:
return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: XMLDB_TYPE_TEXT field "'.$field->getName().'" can not be indexed';
break;

case XMLDB_TYPE_BINARY:
return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: XMLDB_TYPE_BINARY field "'.$field->getName().'" can not be indexed';
break;

case XMLDB_TYPE_DATETIME:
$total += 8; // this is just a guess
break;

case XMLDB_TYPE_TIMESTAMP:
$total += 8; // this is just a guess
break;
}
}

if ($total > self::INDEX_COMPOSED_MAX_BYTES) {
return 'Invalid index definition in table {'.$xmldb_table->getName(). '}: the composed index on fields "'.implode(',', $this->getFields()).'" is too long.'
.' Limit is '.self::INDEX_COMPOSED_MAX_BYTES.' bytes / '.(self::INDEX_COMPOSED_MAX_BYTES/3).' chars.';
}

return null;
}

}

/// TODO: Delete for 2.1 (deprecated in 2.0).
Expand Down
13 changes: 13 additions & 0 deletions lib/xmldb/xmldb_object.php
Original file line number Diff line number Diff line change
Expand Up @@ -478,4 +478,17 @@ function comma2array($string) {

return $arr;
}

/**
* Validates the definition of objects and returns error message.
*
* The error message should not be localised because it is intended for developers,
* end users and admins should never see these problems!
*
* @param xmldb_table $xmldb_table optional when object is table
* @return string null if ok, error message if problem found
*/
function validateDefinition(xmldb_table $xmldb_table=null) {
return null;
}
}

0 comments on commit b2cfdcf

Please sign in to comment.