3 * Unit tests for (some of) ddl lib.
4 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
7 if (!defined('MOODLE_INTERNAL')) {
8 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
11 require_once($CFG->libdir . '/adminlib.php');
13 class ddl_test extends UnitTestCase {
14 private $tables = array();
15 private $records= array();
17 public static $includecoverage = array('lib/ddl');
18 public static $excludecoverage = array('lib/ddl/simpletest');
20 public function setUp() {
21 global $CFG, $DB, $UNITTEST;
23 if (isset($UNITTEST->func_test_db)) {
24 $this->tdb = $UNITTEST->func_test_db;
29 unset($CFG->xmldbreconstructprevnext); // remove this unhack ;-)
31 $dbman = $this->tdb->get_manager();
33 $table = new xmldb_table('test_table0');
34 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
35 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
36 $table->add_field('type', XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'general');
37 $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null);
38 $table->add_field('intro', XMLDB_TYPE_TEXT, 'small', null, XMLDB_NOTNULL, null, null);
39 $table->add_field('logo', XMLDB_TYPE_BINARY, 'big', null, null, null);
40 $table->add_field('assessed', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
41 $table->add_field('assesstimestart', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
42 $table->add_field('assesstimefinish', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
43 $table->add_field('scale', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
44 $table->add_field('maxbytes', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
45 $table->add_field('forcesubscribe', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
46 $table->add_field('trackingtype', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '1');
47 $table->add_field('rsstype', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
48 $table->add_field('rssarticles', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
49 $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
50 $table->add_field('grade', XMLDB_TYPE_NUMBER, '20,0', XMLDB_UNSIGNED, null, null, null);
51 $table->add_field('percent', XMLDB_TYPE_NUMBER, '5,2', null, null, null, 66.6);
52 $table->add_field('warnafter', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
53 $table->add_field('blockafter', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
54 $table->add_field('blockperiod', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
55 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
56 $table->add_key('course', XMLDB_KEY_UNIQUE, array('course'));
57 $table->add_index('type-name', XMLDB_INDEX_UNIQUE, array('type', 'name'));
58 $table->add_index('rsstype', XMLDB_INDEX_NOTUNIQUE, array('rsstype'));
59 $table->setComment("This is a test'n drop table. You can drop it safely");
61 $this->tables[$table->getName()] = $table;
63 // Define 2 initial records for this table
64 $this->records[$table->getName()] = array(
69 'intro' => 'first record'),
74 'intro' => 'second record'));
76 // Second, smaller table
77 $table = new xmldb_table ('test_table1');
78 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
79 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
80 $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, null, null, 'Moodle');
81 $table->add_field('secondname', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, null);
82 $table->add_field('thirdname', XMLDB_TYPE_CHAR, '30', null, null, null, ''); // nullable column with empty default
83 $table->add_field('intro', XMLDB_TYPE_TEXT, 'medium', null, XMLDB_NOTNULL, null, null);
84 $table->add_field('avatar', XMLDB_TYPE_BINARY, 'medium', null, null, null, null);
85 $table->add_field('grade', XMLDB_TYPE_NUMBER, '20,10', null, null, null);
86 $table->add_field('gradefloat', XMLDB_TYPE_FLOAT, '20,0', XMLDB_UNSIGNED, null, null, null);
87 $table->add_field('percentfloat', XMLDB_TYPE_FLOAT, '5,2', null, null, null, 99.9);
88 $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
89 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
90 $table->add_key('course', XMLDB_KEY_FOREIGN_UNIQUE, array('course'), 'test_table0', array('course'));
91 $table->setComment("This is a test'n drop table. You can drop it safely");
93 $this->tables[$table->getName()] = $table;
95 // Define 2 initial records for this table
96 $this->records[$table->getName()] = array(
99 'secondname' => 'first record', // > 10 cc, please don't modify. Some tests below depend of this
100 'intro' => 'first record'),
103 'secondname' => 'second record', // > 10 cc, please don't modify. Some tests below depend of this
104 'intro' => 'second record'));
106 // make sure no tables are present!
110 public function tearDown() {
111 $dbman = $this->tdb->get_manager();
113 // drop custom test tables
114 for ($i=0; $i<3; $i++) {
115 $table = new xmldb_table('test_table_cust'.$i);
116 if ($dbman->table_exists($table)) {
117 $dbman->drop_table($table);
121 // drop default tables
122 foreach ($this->tables as $table) {
123 if ($dbman->table_exists($table)) {
124 $dbman->drop_table($table);
129 private function create_deftable($tablename) {
130 $dbman = $this->tdb->get_manager();
132 if (!isset($this->tables[$tablename])) {
136 $table = $this->tables[$tablename];
138 if ($dbman->table_exists($table)) {
139 $dbman->drop_table($table);
141 $dbman->create_table($table);
147 * Fill the given test table with some records, as far as
148 * DDL behaviour must be tested both with real data and
151 private function fill_deftable($tablename) {
152 $DB = $this->tdb; // do not use global $DB!
153 $dbman = $this->tdb->get_manager();
155 if (!isset($this->records[$tablename])) {
159 if ($dbman->table_exists($tablename)) {
160 foreach ($this->records[$tablename] as $row) {
161 $DB->insert_record($tablename, $row);
167 return count($this->records[$tablename]);
171 * Test behaviour of table_exists()
173 public function test_table_exists() {
174 $DB = $this->tdb; // do not use global $DB!
175 $dbman = $this->tdb->get_manager();
177 // first make sure it returns false if table does not exist
178 $table = $this->tables['test_table0'];
180 ob_start(); // hide debug warning
182 $result = $DB->get_records('test_table0');
183 } catch (dml_read_exception $e) {
188 $this->assertFalse($result);
190 $this->assertFalse($dbman->table_exists('test_table0')); // by name
191 $this->assertFalse($dbman->table_exists($table)); // by xmldb_table
193 // create table and test again
194 $dbman->create_table($table);
196 $this->assertTrue($DB->get_records('test_table0') === array());
197 $this->assertTrue($dbman->table_exists('test_table0')); // by name
198 $this->assertTrue($dbman->table_exists($table)); // by xmldb_table
200 // drop table and test again
201 $dbman->drop_table($table);
203 ob_start(); // hide debug warning
205 $result = $DB->get_records('test_table0');
206 } catch (dml_read_exception $e) {
211 $this->assertFalse($result);
213 $this->assertFalse($dbman->table_exists('test_table0')); // by name
214 $this->assertFalse($dbman->table_exists($table)); // by xmldb_table
218 * Test behaviour of create_table()
220 public function test_create_table() {
221 $DB = $this->tdb; // do not use global $DB!
222 $dbman = $this->tdb->get_manager();
225 $table = $this->tables['test_table1'];
227 $dbman->create_table($table);
228 $this->assertTrue($dbman->table_exists($table));
230 // basic get_tables() test
231 $tables = $DB->get_tables();
232 $this->assertTrue(array_key_exists('test_table1', $tables));
234 // basic get_columns() tests
235 $columns = $DB->get_columns('test_table1');
236 $this->assertEqual($columns['id']->meta_type, 'R');
237 $this->assertEqual($columns['course']->meta_type, 'I');
238 $this->assertEqual($columns['name']->meta_type, 'C');
239 $this->assertEqual($columns['secondname']->meta_type, 'C');
240 $this->assertEqual($columns['thirdname']->meta_type, 'C');
241 $this->assertEqual($columns['intro']->meta_type, 'X');
242 $this->assertEqual($columns['avatar']->meta_type, 'B');
243 $this->assertEqual($columns['grade']->meta_type, 'N');
244 $this->assertEqual($columns['percentfloat']->meta_type, 'N');
245 $this->assertEqual($columns['userid']->meta_type, 'I');
247 $this->assertTrue($columns['course']->has_default);
248 $this->assertEqual($columns['course']->default_value, 0);
249 $this->assertTrue($columns['name']->has_default);
250 $this->assertEqual($columns['name']->default_value, 'Moodle');
251 $this->assertTrue($columns['secondname']->has_default);
252 $this->assertEqual($columns['secondname']->default_value, '');
253 $this->assertTrue($columns['thirdname']->has_default);
254 $this->assertEqual($columns['thirdname']->default_value, '');
255 $this->assertTrue($columns['percentfloat']->has_default);
256 $this->assertEqual($columns['percentfloat']->default_value, 99.9);
257 $this->assertTrue($columns['userid']->has_default);
258 $this->assertEqual($columns['userid']->default_value, 0);
260 // basic get_indexes() test
261 $indexes = $DB->get_indexes('test_table1');
262 $courseindex = reset($indexes);
263 $this->assertEqual($courseindex['unique'], 1);
264 $this->assertEqual($courseindex['columns'][0], 'course');
266 // check sequence returns 1 for first insert
267 $rec = (object)array(
269 'secondname' => 'not important',
270 'intro' => 'not important');
271 $this->assertIdentical($DB->insert_record('test_table1', $rec), 1);
273 // check defined defaults are working ok
274 $dbrec = $DB->get_record('test_table1', array('id' => 1));
275 $this->assertEqual($dbrec->name, 'Moodle');
276 $this->assertEqual($dbrec->thirdname, '');
278 // check exceptions if multiple R columns
279 $table = new xmldb_table ('test_table2');
280 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
281 $table->add_field('rid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
282 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
283 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
284 $table->add_key('primaryx', XMLDB_KEY_PRIMARY, array('id'));
285 $table->setComment("This is a test'n drop table. You can drop it safely");
287 $this->tables[$table->getName()] = $table;
290 $dbman->create_table($table);
291 $this->fail('Exception expected');
292 } catch (Exception $e) {
293 $this->assertTrue($e instanceof ddl_exception);
296 // check exceptions missing primary key on R column
297 $table = new xmldb_table ('test_table2');
298 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
299 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
300 $table->setComment("This is a test'n drop table. You can drop it safely");
302 $this->tables[$table->getName()] = $table;
305 $dbman->create_table($table);
306 $this->fail('Exception expected');
307 } catch (Exception $e) {
308 $this->assertTrue($e instanceof ddl_exception);
314 * Test behaviour of drop_table()
316 public function test_drop_table() {
317 $DB = $this->tdb; // do not use global $DB!
318 $dbman = $this->tdb->get_manager();
320 // initially table doesn't exist
321 $this->assertFalse($dbman->table_exists('test_table0'));
323 // create table with contents
324 $table = $this->create_deftable('test_table0');
325 $this->assertTrue($dbman->table_exists('test_table0'));
327 // fill the table with some records before dropping it
328 $this->fill_deftable('test_table0');
330 // drop by xmldb_table object
331 $dbman->drop_table($table);
332 $this->assertFalse($dbman->table_exists('test_table0'));
334 // basic get_tables() test
335 $tables = $DB->get_tables();
336 $this->assertFalse(array_key_exists('test_table0', $tables));
338 try { // columns cache must be empty, so sentence throw exception
339 $columns = $DB->get_columns('test_table0');
340 } catch (dml_read_exception $e) {
343 $this->assertFalse($columns);
345 try { /// throw exception
346 $indexes = $DB->get_indexes('test_table0');
347 } catch (dml_read_exception $e) {
350 $this->assertFalse($indexes);
354 * Test behaviour of rename_table()
356 public function test_rename_table() {
357 $DB = $this->tdb; // do not use global $DB!
358 $dbman = $this->tdb->get_manager();
360 $table = $this->create_deftable('test_table1');
362 // fill the table with some records before renaming it
363 $insertedrows = $this->fill_deftable('test_table1');
365 $this->assertFalse($dbman->table_exists('test_table_cust1'));
366 $dbman->rename_table($table, 'test_table_cust1');
367 $this->assertTrue($dbman->table_exists('test_table_cust1'));
369 // check sequence returns $insertedrows + 1 for this insert (after rename)
370 $rec = (object)array(
372 'secondname' => 'not important',
373 'intro' => 'not important');
374 $this->assertIdentical($DB->insert_record('test_table_cust1', $rec), $insertedrows + 1);
378 * Test behaviour of field_exists()
380 public function test_field_exists() {
381 $dbman = $this->tdb->get_manager();
383 $table = $this->create_deftable('test_table0');
386 // Give a nonexistent table as first param (throw exception)
388 $dbman->field_exists('nonexistenttable', 'id');
389 $this->assertTrue(false);
390 } catch (Exception $e) {
391 $this->assertTrue($e instanceof moodle_exception);
394 // Give a nonexistent field as second param (return false)
395 $this->assertFalse($dbman->field_exists('test_table0', 'nonexistentfield'));
397 // Correct string params
398 $this->assertTrue($dbman->field_exists('test_table0', 'id'));
401 $realfield = $table->getField('id');
403 // Give a nonexistent table as first param (throw exception)
404 $nonexistenttable = new xmldb_table('nonexistenttable');
406 $dbman->field_exists($nonexistenttable, $realfield);
407 $this->assertTrue(false);
408 } catch (Exception $e) {
409 $this->assertTrue($e instanceof moodle_exception);
412 // Give a nonexistent field as second param (return false)
413 $nonexistentfield = new xmldb_field('nonexistentfield');
414 $this->assertFalse($dbman->field_exists($table, $nonexistentfield));
416 // Correct object params
417 $this->assertTrue($dbman->field_exists($table, $realfield));
419 // Mix string and object params
421 $this->assertTrue($dbman->field_exists($table, 'id'));
422 $this->assertTrue($dbman->field_exists('test_table0', $realfield));
423 // Non existing tables (throw exception)
425 $this->assertFalse($dbman->field_exists($nonexistenttable, 'id'));
426 $this->assertTrue(false);
427 } catch (Exception $e) {
428 $this->assertTrue($e instanceof moodle_exception);
431 $this->assertFalse($dbman->field_exists('nonexistenttable', $realfield));
432 $this->assertTrue(false);
433 } catch (Exception $e) {
434 $this->assertTrue($e instanceof moodle_exception);
436 // Non existing fields (return false)
437 $this->assertFalse($dbman->field_exists($table, 'nonexistentfield'));
438 $this->assertFalse($dbman->field_exists('test_table0', $nonexistentfield));
442 * Test behaviour of add_field()
444 public function test_add_field() {
445 $DB = $this->tdb; // do not use global $DB!
446 $dbman = $this->tdb->get_manager();
448 $table = $this->create_deftable('test_table1');
450 // fill the table with some records before adding fields
451 $this->fill_deftable('test_table1');
453 /// add one not null field without specifying default value (throws ddl_exception)
454 $field = new xmldb_field('onefield');
455 $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
457 $dbman->add_field($table, $field);
458 $this->assertTrue(false);
459 } catch (Exception $e) {
460 $this->assertTrue($e instanceof ddl_exception);
463 /// add one existing field (throws ddl_exception)
464 $field = new xmldb_field('course');
465 $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 2);
467 $dbman->add_field($table, $field);
468 $this->assertTrue(false);
469 } catch (Exception $e) {
470 $this->assertTrue($e instanceof ddl_exception);
473 // TODO: add one field with invalid type, must throw exception
474 // TODO: add one text field with default, must throw exception
475 // TODO: add one binary field with default, must throw exception
477 /// add one integer field and check it
478 $field = new xmldb_field('oneinteger');
479 $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 2);
480 $dbman->add_field($table, $field);
481 $this->assertTrue($dbman->field_exists($table, 'oneinteger'));
482 $columns = $DB->get_columns('test_table1');
483 $this->assertEqual($columns['oneinteger']->name ,'oneinteger');
484 $this->assertEqual($columns['oneinteger']->not_null , true);
485 // max_length and scale cannot be checked under all DBs at all for integer fields
486 $this->assertEqual($columns['oneinteger']->primary_key , false);
487 $this->assertEqual($columns['oneinteger']->binary , false);
488 $this->assertEqual($columns['oneinteger']->has_default , true);
489 $this->assertEqual($columns['oneinteger']->default_value, 2);
490 $this->assertEqual($columns['oneinteger']->meta_type ,'I');
491 $this->assertEqual($DB->get_field('test_table1', 'oneinteger', array(), IGNORE_MULTIPLE), 2); //check default has been applied
493 /// add one numeric field and check it
494 $field = new xmldb_field('onenumber');
495 $field->set_attributes(XMLDB_TYPE_NUMBER, '6,3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 2.55);
496 $dbman->add_field($table, $field);
497 $this->assertTrue($dbman->field_exists($table, 'onenumber'));
498 $columns = $DB->get_columns('test_table1');
499 $this->assertEqual($columns['onenumber']->name ,'onenumber');
500 $this->assertEqual($columns['onenumber']->max_length , 6);
501 $this->assertEqual($columns['onenumber']->scale , 3);
502 $this->assertEqual($columns['onenumber']->not_null , true);
503 $this->assertEqual($columns['onenumber']->primary_key , false);
504 $this->assertEqual($columns['onenumber']->binary , false);
505 $this->assertEqual($columns['onenumber']->has_default , true);
506 $this->assertEqual($columns['onenumber']->default_value, 2.550);
507 $this->assertEqual($columns['onenumber']->meta_type ,'N');
508 $this->assertEqual($DB->get_field('test_table1', 'onenumber', array(), IGNORE_MULTIPLE), 2.550); //check default has been applied
510 /// add one float field and check it (not official type - must work as number)
511 $field = new xmldb_field('onefloat');
512 $field->set_attributes(XMLDB_TYPE_FLOAT, '6,3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 3.550);
513 $dbman->add_field($table, $field);
514 $this->assertTrue($dbman->field_exists($table, 'onefloat'));
515 $columns = $DB->get_columns('test_table1');
516 $this->assertEqual($columns['onefloat']->name ,'onefloat');
517 $this->assertEqual($columns['onefloat']->not_null , true);
518 // max_length and scale cannot be checked under all DBs at all for float fields
519 $this->assertEqual($columns['onefloat']->primary_key , false);
520 $this->assertEqual($columns['onefloat']->binary , false);
521 $this->assertEqual($columns['onefloat']->has_default , true);
522 $this->assertEqual($columns['onefloat']->default_value, 3.550);
523 $this->assertEqual($columns['onefloat']->meta_type ,'N');
524 // Just rounding DB information to 7 decimal digits. Fair enough to test 3.550 and avoids one nasty bug
525 // in MSSQL core returning wrong floats (http://social.msdn.microsoft.com/Forums/en-US/sqldataaccess/thread/5e08de63-16bb-4f24-b645-0cf8fc669de3)
526 // In any case, floats aren't officially supported by Moodle, with number/decimal type being the correct ones, so
527 // this isn't a real problem at all.
528 $this->assertEqual(round($DB->get_field('test_table1', 'onefloat', array(), IGNORE_MULTIPLE), 7), 3.550); //check default has been applied
530 /// add one char field and check it
531 $field = new xmldb_field('onechar');
532 $field->set_attributes(XMLDB_TYPE_CHAR, '25', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 'Nice dflt!');
533 $dbman->add_field($table, $field);
534 $this->assertTrue($dbman->field_exists($table, 'onechar'));
535 $columns = $DB->get_columns('test_table1');
536 $this->assertEqual($columns['onechar']->name ,'onechar');
537 $this->assertEqual($columns['onechar']->max_length , 25);
538 $this->assertEqual($columns['onechar']->scale , null);
539 $this->assertEqual($columns['onechar']->not_null , true);
540 $this->assertEqual($columns['onechar']->primary_key , false);
541 $this->assertEqual($columns['onechar']->binary , false);
542 $this->assertEqual($columns['onechar']->has_default , true);
543 $this->assertEqual($columns['onechar']->default_value,'Nice dflt!');
544 $this->assertEqual($columns['onechar']->meta_type ,'C');
545 $this->assertEqual($DB->get_field('test_table1', 'onechar', array(), IGNORE_MULTIPLE), 'Nice dflt!'); //check default has been applied
547 /// add one text field and check it
548 $field = new xmldb_field('onetext');
549 $field->set_attributes(XMLDB_TYPE_TEXT);
550 $dbman->add_field($table, $field);
551 $this->assertTrue($dbman->field_exists($table, 'onetext'));
552 $columns = $DB->get_columns('test_table1');
553 $this->assertEqual($columns['onetext']->name ,'onetext');
554 $this->assertEqual($columns['onetext']->max_length , -1);
555 $this->assertEqual($columns['onetext']->scale , null);
556 $this->assertEqual($columns['onetext']->not_null , false);
557 $this->assertEqual($columns['onetext']->primary_key , false);
558 $this->assertEqual($columns['onetext']->binary , false);
559 $this->assertEqual($columns['onetext']->has_default , false);
560 $this->assertEqual($columns['onetext']->default_value, null);
561 $this->assertEqual($columns['onetext']->meta_type ,'X');
563 /// add one binary field and check it
564 $field = new xmldb_field('onebinary');
565 $field->set_attributes(XMLDB_TYPE_BINARY);
566 $dbman->add_field($table, $field);
567 $this->assertTrue($dbman->field_exists($table, 'onebinary'));
568 $columns = $DB->get_columns('test_table1');
569 $this->assertEqual($columns['onebinary']->name ,'onebinary');
570 $this->assertEqual($columns['onebinary']->max_length , -1);
571 $this->assertEqual($columns['onebinary']->scale , null);
572 $this->assertEqual($columns['onebinary']->not_null , false);
573 $this->assertEqual($columns['onebinary']->primary_key , false);
574 $this->assertEqual($columns['onebinary']->binary , true);
575 $this->assertEqual($columns['onebinary']->has_default , false);
576 $this->assertEqual($columns['onebinary']->default_value, null);
577 $this->assertEqual($columns['onebinary']->meta_type ,'B');
579 // TODO: check datetime type. Although unused should be fully supported.
583 * Test behaviour of drop_field()
585 public function test_drop_field() {
586 $DB = $this->tdb; // do not use global $DB!
587 $dbman = $this->tdb->get_manager();
589 $table = $this->create_deftable('test_table0');
591 // fill the table with some records before dropping fields
592 $this->fill_deftable('test_table0');
594 // drop field with simple xmldb_field having indexes, must return exception
595 $field = new xmldb_field('type'); // Field has indexes and default clause
596 $this->assertTrue($dbman->field_exists($table, 'type'));
598 $dbman->drop_field($table, $field);
599 $this->assertTrue(false);
600 } catch (Exception $e) {
601 $this->assertTrue($e instanceof ddl_dependency_exception);
603 $this->assertTrue($dbman->field_exists($table, 'type')); // continues existing, drop aborted
605 // drop field with complete xmldb_field object and related indexes, must return exception
606 $field = $table->getField('course'); // Field has indexes and default clause
607 $this->assertTrue($dbman->field_exists($table, $field));
609 $dbman->drop_field($table, $field);
610 $this->assertTrue(false);
611 } catch (Exception $e) {
612 $this->assertTrue($e instanceof ddl_dependency_exception);
614 $this->assertTrue($dbman->field_exists($table, $field)); // continues existing, drop aborted
616 // drop one non-existing field, must return exception
617 $field = new xmldb_field('nonexistingfield');
618 $this->assertFalse($dbman->field_exists($table, $field));
620 $dbman->drop_field($table, $field);
621 $this->assertTrue(false);
622 } catch (Exception $e) {
623 $this->assertTrue($e instanceof ddl_field_missing_exception);
626 // drop field with simple xmldb_field, not having related indexes
627 $field = new xmldb_field('forcesubscribe'); // Field has default clause
628 $this->assertTrue($dbman->field_exists($table, 'forcesubscribe'));
629 $dbman->drop_field($table, $field);
630 $this->assertFalse($dbman->field_exists($table, 'forcesubscribe'));
633 // drop field with complete xmldb_field object, not having related indexes
634 $field = new xmldb_field('trackingtype'); // Field has default clause
635 $this->assertTrue($dbman->field_exists($table, $field));
636 $dbman->drop_field($table, $field);
637 $this->assertFalse($dbman->field_exists($table, $field));
641 * Test behaviour of change_field_type()
643 public function test_change_field_type() {
644 $DB = $this->tdb; // do not use global $DB!
645 $dbman = $this->tdb->get_manager();
647 // create table with indexed field and not indexed field to
648 // perform tests in both fields, both having defaults
649 $table = new xmldb_table('test_table_cust0');
650 $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
651 $table->add_field('onenumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '2');
652 $table->add_field('anothernumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '4');
653 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
654 $table->add_index('onenumber', XMLDB_INDEX_NOTUNIQUE, array('onenumber'));
655 $dbman->create_table($table);
657 $record = new stdClass();
658 $record->onenumber = 2;
659 $record->anothernumber = 4;
660 $recoriginal = $DB->insert_record('test_table_cust0', $record);
662 // change column from integer to varchar. Must return exception because of dependent index
663 $field = new xmldb_field('onenumber');
664 $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'test');
666 $dbman->change_field_type($table, $field);
667 $this->assertTrue(false);
668 } catch (Exception $e) {
669 $this->assertTrue($e instanceof ddl_dependency_exception);
671 // column continues being integer 10 not null default 2
672 $columns = $DB->get_columns('test_table_cust0');
673 $this->assertEqual($columns['onenumber']->meta_type, 'I');
674 //TODO: check the rest of attributes
676 // change column from integer to varchar. Must work because column has no dependencies
677 $field = new xmldb_field('anothernumber');
678 $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'test');
679 $dbman->change_field_type($table, $field);
680 // column is char 30 not null default 'test' now
681 $columns = $DB->get_columns('test_table_cust0');
682 $this->assertEqual($columns['anothernumber']->meta_type, 'C');
683 //TODO: check the rest of attributes
685 // change column back from char to integer
686 $field = new xmldb_field('anothernumber');
687 $field->set_attributes(XMLDB_TYPE_INTEGER, '8', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '5');
688 $dbman->change_field_type($table, $field);
689 // column is integer 8 not null default 5 now
690 $columns = $DB->get_columns('test_table_cust0');
691 $this->assertEqual($columns['anothernumber']->meta_type, 'I');
692 //TODO: check the rest of attributes
694 // change column once more from integer to char
695 $field = new xmldb_field('anothernumber');
696 $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, "test'n drop");
697 $dbman->change_field_type($table, $field);
698 // column is char 30 not null default "test'n drop" now
699 $columns = $DB->get_columns('test_table_cust0');
700 $this->assertEqual($columns['anothernumber']->meta_type, 'C');
701 //TODO: check the rest of attributes
703 // insert one string value and try to convert to integer. Must throw exception
704 $record = new stdClass();
705 $record->onenumber = 7;
706 $record->anothernumber = 'string value';
707 $rectodrop = $DB->insert_record('test_table_cust0', $record);
708 $field = new xmldb_field('anothernumber');
709 $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '5');
711 $dbman->change_field_type($table, $field);
712 $this->assertTrue(false);
713 } catch (Exception $e) {
714 $this->assertTrue($e instanceof ddl_change_structure_exception);
716 // column continues being char 30 not null default "test'n drop" now
717 $this->assertEqual($columns['anothernumber']->meta_type, 'C');
718 //TODO: check the rest of attributes
719 $DB->delete_records('test_table_cust0', array('id' => $rectodrop)); // Delete the string record
721 // change the column from varchar to float
722 $field = new xmldb_field('anothernumber');
723 $field->set_attributes(XMLDB_TYPE_FLOAT, '20,10', XMLDB_UNSIGNED, null, null, null);
724 $dbman->change_field_type($table, $field);
725 // column is float 20,10 null default null
726 $columns = $DB->get_columns('test_table_cust0');
727 $this->assertEqual($columns['anothernumber']->meta_type, 'N'); // floats are seen as number
728 //TODO: check the rest of attributes
730 // change the column back from float to varchar
731 $field = new xmldb_field('anothernumber');
732 $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'test');
733 $dbman->change_field_type($table, $field);
734 // column is char 20 not null default "test" now
735 $columns = $DB->get_columns('test_table_cust0');
736 $this->assertEqual($columns['anothernumber']->meta_type, 'C');
737 //TODO: check the rest of attributes
739 // change the column from varchar to number
740 $field = new xmldb_field('anothernumber');
741 $field->set_attributes(XMLDB_TYPE_NUMBER, '20,10', XMLDB_UNSIGNED, null, null, null);
742 $dbman->change_field_type($table, $field);
743 // column is number 20,10 null default null now
744 $columns = $DB->get_columns('test_table_cust0');
745 $this->assertEqual($columns['anothernumber']->meta_type, 'N');
746 //TODO: check the rest of attributes
748 // change the column from number to integer
749 $field = new xmldb_field('anothernumber');
750 $field->set_attributes(XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, null, null, null);
751 $dbman->change_field_type($table, $field);
752 // column is integer 2 null default null now
753 $columns = $DB->get_columns('test_table_cust0');
754 $this->assertEqual($columns['anothernumber']->meta_type, 'I');
755 //TODO: check the rest of attributes
757 // change the column from integer to text
758 $field = new xmldb_field('anothernumber');
759 $field->set_attributes(XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null);
760 $dbman->change_field_type($table, $field);
761 // column is char text not null default null
762 $columns = $DB->get_columns('test_table_cust0');
763 $this->assertEqual($columns['anothernumber']->meta_type, 'X');
765 // change the column back from text to number
766 $field = new xmldb_field('anothernumber');
767 $field->set_attributes(XMLDB_TYPE_NUMBER, '20,10', XMLDB_UNSIGNED, null, null, null);
768 $dbman->change_field_type($table, $field);
769 // column is number 20,10 null default null now
770 $columns = $DB->get_columns('test_table_cust0');
771 $this->assertEqual($columns['anothernumber']->meta_type, 'N');
772 //TODO: check the rest of attributes
774 // change the column from number to text
775 $field = new xmldb_field('anothernumber');
776 $field->set_attributes(XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null);
777 $dbman->change_field_type($table, $field);
778 // column is char text not null default "test" now
779 $columns = $DB->get_columns('test_table_cust0');
780 $this->assertEqual($columns['anothernumber']->meta_type, 'X');
781 //TODO: check the rest of attributes
783 // change the column back from text to integer
784 $field = new xmldb_field('anothernumber');
785 $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 10);
786 $dbman->change_field_type($table, $field);
787 // column is integer 10 not null default 10
788 $columns = $DB->get_columns('test_table_cust0');
789 $this->assertEqual($columns['anothernumber']->meta_type, 'I');
790 //TODO: check the rest of attributes
792 // check original value has survived to all the type changes
793 $this->assertTrue($rec = $DB->get_record('test_table_cust0', array('id' => $recoriginal)));
794 $this->assertEqual($rec->anothernumber, 4);
796 $dbman->drop_table($table);
797 $this->assertFalse($dbman->table_exists($table));
801 * Test behaviour of test_change_field_precision()
803 public function test_change_field_precision() {
804 $DB = $this->tdb; // do not use global $DB!
805 $dbman = $this->tdb->get_manager();
807 $table = $this->create_deftable('test_table1');
809 // fill the table with some records before dropping fields
810 $this->fill_deftable('test_table1');
812 // change text field from medium to big
813 $field = new xmldb_field('intro');
814 $field->set_attributes(XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL, null, null);
815 $dbman->change_field_precision($table, $field);
816 $columns = $DB->get_columns('test_table1');
817 // cannot check the text type, only the metatype
818 $this->assertEqual($columns['intro']->meta_type, 'X');
819 //TODO: check the rest of attributes
821 // change char field from 30 to 20
822 $field = new xmldb_field('secondname');
823 $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, null);
824 $dbman->change_field_precision($table, $field);
825 $columns = $DB->get_columns('test_table1');
826 $this->assertEqual($columns['secondname']->meta_type, 'C');
827 //TODO: check the rest of attributes
829 // change char field from 20 to 10, having contents > 10cc. Throw exception
830 $field = new xmldb_field('secondname');
831 $field->set_attributes(XMLDB_TYPE_CHAR, '10', null, XMLDB_NOTNULL, null, null);
833 $dbman->change_field_precision($table, $field);
834 $this->assertTrue(false);
835 } catch (Exception $e) {
836 $this->assertTrue($e instanceof ddl_change_structure_exception);
838 // No changes in field specs at all
839 $columns = $DB->get_columns('test_table1');
840 $this->assertEqual($columns['secondname']->meta_type, 'C');
841 //TODO: check the rest of attributes
843 // change number field from 20,10 to 10,2
844 $field = new xmldb_field('grade');
845 $field->set_attributes(XMLDB_TYPE_NUMBER, '10,2', null, null, null, null);
846 $dbman->change_field_precision($table, $field);
847 $columns = $DB->get_columns('test_table1');
848 $this->assertEqual($columns['grade']->meta_type, 'N');
849 //TODO: check the rest of attributes
851 // change integer field from 10 to 2
852 $field = new xmldb_field('userid');
853 $field->set_attributes(XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
854 $dbman->change_field_precision($table, $field);
855 $columns = $DB->get_columns('test_table1');
856 $this->assertEqual($columns['userid']->meta_type, 'I');
857 //TODO: check the rest of attributes
859 // change the column from integer (2) to integer (6) (forces change of type in some DBs)
860 $field = new xmldb_field('userid');
861 $field->set_attributes(XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, null, null, null);
862 $dbman->change_field_precision($table, $field);
863 // column is integer 6 null default null now
864 $columns = $DB->get_columns('test_table1');
865 $this->assertEqual($columns['userid']->meta_type, 'I');
866 //TODO: check the rest of attributes
868 // insert one record with 6-digit field
869 $record = new stdClass();
870 $record->course = 10;
871 $record->secondname = 'third record';
872 $record->intro = 'third record';
873 $record->userid = 123456;
874 $DB->insert_record('test_table1', $record);
875 // change integer field from 6 to 2, contents are bigger. must throw exception
876 $field = new xmldb_field('userid');
877 $field->set_attributes(XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
879 $dbman->change_field_precision($table, $field);
880 $this->assertTrue(false);
881 } catch (Exception $e) {
882 $this->assertTrue($e instanceof ddl_change_structure_exception);
884 // No changes in field specs at all
885 $columns = $DB->get_columns('test_table1');
886 $this->assertEqual($columns['userid']->meta_type, 'I');
887 //TODO: check the rest of attributes
889 // change integer field from 10 to 3, in field used by index. must throw exception.
890 $field = new xmldb_field('course');
891 $field->set_attributes(XMLDB_TYPE_INTEGER, '3', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
893 $dbman->change_field_precision($table, $field);
894 $this->assertTrue(false);
895 } catch (Exception $e) {
896 $this->assertTrue($e instanceof ddl_dependency_exception);
898 // No changes in field specs at all
899 $columns = $DB->get_columns('test_table1');
900 $this->assertEqual($columns['course']->meta_type, 'I');
901 //TODO: check the rest of attributes
904 public function testChangeFieldSign() {
905 $dbman = $this->tdb->get_manager();
906 // TODO: verify the signed is changed in db
908 $table = $this->create_deftable('test_table1');
909 $field = new xmldb_field('grade');
910 $field->set_attributes(XMLDB_TYPE_NUMBER, '10,2', XMLDB_UNSIGNED, null, null, null);
911 $dbman->change_field_unsigned($table, $field);
913 $field = new xmldb_field('grade');
914 $field->set_attributes(XMLDB_TYPE_NUMBER, '10,2', null, null, null, null);
915 $dbman->change_field_unsigned($table, $field);
918 public function testChangeFieldNullability() {
919 $DB = $this->tdb; // do not use global $DB!
920 $dbman = $this->tdb->get_manager();
922 $table = new xmldb_table('test_table_cust0');
923 $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
924 $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, null);
925 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
926 $dbman->create_table($table);
928 $record = new stdClass();
929 $record->name = NULL;
931 ob_start(); // hide debug warning
933 $result = $DB->insert_record('test_table_cust0', $record, false);
934 } catch (dml_write_exception $e) {
938 $this->assertFalse($result);
940 $field = new xmldb_field('name');
941 $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, null, null, null);
942 $dbman->change_field_notnull($table, $field);
944 $this->assertTrue($DB->insert_record('test_table_cust0', $record, false));
946 // TODO: add some tests with existing data in table
947 $DB->delete_records('test_table_cust0');
949 $field = new xmldb_field('name');
950 $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, null);
951 $dbman->change_field_notnull($table, $field);
953 ob_start(); // hide debug warning
955 $result = $DB->insert_record('test_table_cust0', $record, false);
956 } catch (dml_write_exception $e) {
960 $this->assertFalse($result);
962 $dbman->drop_table($table);
965 public function testChangeFieldDefault() {
966 $DB = $this->tdb; // do not use global $DB!
967 $dbman = $this->tdb->get_manager();
969 $table = new xmldb_table('test_table_cust0');
970 $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
971 $table->add_field('onenumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
972 $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'Moodle');
973 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
974 $dbman->create_table($table);
976 $field = new xmldb_field('name');
977 $field->set_attributes(XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'Moodle2');
978 $dbman->change_field_default($table, $field);
980 $record = new stdClass();
981 $record->onenumber = 666;
982 $id = $DB->insert_record('test_table_cust0', $record);
984 $record = $DB->get_record('test_table_cust0', array('id'=>$id));
985 $this->assertEqual($record->name, 'Moodle2');
988 $field = new xmldb_field('onenumber');
989 $field->set_attributes(XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, 666);
990 $dbman->change_field_default($table, $field);
992 $record = new stdClass();
993 $record->name = 'something';
994 $id = $DB->insert_record('test_table_cust0', $record);
996 $record = $DB->get_record('test_table_cust0', array('id'=>$id));
997 $this->assertEqual($record->onenumber, '666');
999 $dbman->drop_table($table);
1002 public function testAddUniqueIndex() {
1003 $DB = $this->tdb; // do not use global $DB!
1004 $dbman = $this->tdb->get_manager();
1006 $table = new xmldb_table('test_table_cust0');
1007 $table->add_field('id', XMLDB_TYPE_INTEGER, '2', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1008 $table->add_field('onenumber', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
1009 $table->add_field('name', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'Moodle');
1010 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1011 $dbman->create_table($table);
1013 $record = new stdClass();
1014 $record->onenumber = 666;
1015 $record->name = 'something';
1016 $DB->insert_record('test_table_cust0', $record, false);
1018 $index = new xmldb_index('onenumber-name');
1019 $index->set_attributes(XMLDB_INDEX_UNIQUE, array('onenumber', 'name'));
1020 $dbman->add_index($table, $index);
1022 ob_start(); // hide debug warning
1024 $result = $DB->insert_record('test_table_cust0', $record, false);
1025 } catch (dml_write_exception $e) {
1029 $this->assertFalse($result);
1031 $dbman->drop_table($table);
1034 public function testAddNonUniqueIndex() {
1035 $dbman = $this->tdb->get_manager();
1037 $table = $this->create_deftable('test_table1');
1038 $index = new xmldb_index('secondname');
1039 $index->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('course', 'name'));
1040 $dbman->add_index($table, $index);
1043 public function testFindIndexName() {
1044 $dbman = $this->tdb->get_manager();
1046 $table = $this->create_deftable('test_table1');
1047 $index = new xmldb_index('secondname');
1048 $index->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('course', 'name'));
1049 $dbman->add_index($table, $index);
1051 //DBM Systems name their indices differently - do not test the actual index name
1052 $result = $dbman->find_index_name($table, $index);
1053 $this->assertTrue(!empty($result));
1055 $nonexistentindex = new xmldb_index('nonexistentindex');
1056 $nonexistentindex->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('name'));
1057 $this->assertFalse($dbman->find_index_name($table, $nonexistentindex));
1060 public function testDropIndex() {
1061 $DB = $this->tdb; // do not use global $DB!
1063 $dbman = $this->tdb->get_manager();
1065 $table = $this->create_deftable('test_table1');
1066 $index = new xmldb_index('secondname');
1067 $index->set_attributes(XMLDB_INDEX_NOTUNIQUE, array('course', 'name'));
1068 $dbman->add_index($table, $index);
1070 $dbman->drop_index($table, $index);
1071 $this->assertFalse($dbman->find_index_name($table, $index));
1073 // Test we are able to drop indexes having hyphens. MDL-22804
1074 // Create index with hyphens (by hand)
1075 $indexname = 'test-index-with-hyphens';
1076 switch ($DB->get_dbfamily()) {
1078 $indexname = '`' . $indexname . '`';
1081 $indexname = '"' . $indexname . '"';
1083 $stmt = "CREATE INDEX {$indexname} ON {$DB->get_prefix()}test_table1 (course, name)";
1084 $DB->change_database_structure($stmt);
1085 $this->assertTrue($dbman->find_index_name($table, $index));
1086 // Index created, let's drop it using db manager stuff
1087 $index = new xmldb_index('indexname', XMLDB_INDEX_NOTUNIQUE, array('course', 'name'));
1088 $dbman->drop_index($table, $index);
1089 $this->assertFalse($dbman->find_index_name($table, $index));
1092 public function testAddUniqueKey() {
1093 $dbman = $this->tdb->get_manager();
1095 $table = $this->create_deftable('test_table1');
1096 $key = new xmldb_key('id-course-grade');
1097 $key->set_attributes(XMLDB_KEY_UNIQUE, array('id', 'course', 'grade'));
1098 $dbman->add_key($table, $key);
1101 public function testAddForeignUniqueKey() {
1102 $dbman = $this->tdb->get_manager();
1104 $table = $this->create_deftable('test_table1');
1105 $this->create_deftable('test_table0');
1107 $key = new xmldb_key('course');
1108 $key->set_attributes(XMLDB_KEY_FOREIGN_UNIQUE, array('course'), 'test_table0', array('id'));
1109 $dbman->add_key($table, $key);
1112 public function testDropKey() {
1113 $dbman = $this->tdb->get_manager();
1115 $table = $this->create_deftable('test_table1');
1116 $this->create_deftable('test_table0');
1118 $key = new xmldb_key('course');
1119 $key->set_attributes(XMLDB_KEY_FOREIGN_UNIQUE, array('course'), 'test_table0', array('id'));
1120 $dbman->add_key($table, $key);
1122 $dbman->drop_key($table, $key);
1125 public function testAddForeignKey() {
1126 $dbman = $this->tdb->get_manager();
1128 $table = $this->create_deftable('test_table1');
1129 $this->create_deftable('test_table0');
1131 $key = new xmldb_key('course');
1132 $key->set_attributes(XMLDB_KEY_FOREIGN, array('course'), 'test_table0', array('id'));
1133 $dbman->add_key($table, $key);
1136 public function testDropForeignKey() {
1137 $dbman = $this->tdb->get_manager();
1139 $table = $this->create_deftable('test_table1');
1140 $this->create_deftable('test_table0');
1142 $key = new xmldb_key('course');
1143 $key->set_attributes(XMLDB_KEY_FOREIGN, array('course'), 'test_table0', array('id'));
1144 $dbman->add_key($table, $key);
1146 $dbman->drop_key($table, $key);
1150 * Test behaviour of drop_enum_from_field() and related functions (find_check_constraint_name
1151 * and check_constraint_exists). Needed to be able to drop existing "enum" fields in the upgrade
1152 * from 1.9 to 2.0, will be completely deleted for Moodle 2.1
1154 * Because we already have dropped support for creation of enum fields in 2.0, we are going to
1155 * create them here "manually" (hardcoded DB-dependent SQL). Just to be able to test the
1156 * find and drop functions properly.
1158 * TODO: Drop this tests completely from Moodle 2.1
1160 public function test_drop_enum_from_field() {
1161 $DB = $this->tdb; // do not use global $DB!
1162 $dbman = $this->tdb->get_manager();
1164 // Create normal table, no enums.
1165 $table = new xmldb_table('test_table_cust0');
1166 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1167 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
1168 $field = new xmldb_field('type');
1169 $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'general');
1170 $table->addField($field);
1171 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1172 $dbman->create_table($table);
1174 $this->assertTrue($dbman->table_exists($table));
1175 $this->assertTrue($dbman->field_exists($table, $field));
1177 // Check table hasn't enums at all
1178 $this->assertFalse($dbman->check_constraint_exists($table, $field));
1179 $this->assertFalse($dbman->find_check_constraint_name($table, $field));
1181 $this->assertFalse($dbman->drop_enum_from_field($table, $field)); // This just outputs debug warning if field hasn't enums
1185 $record = new stdClass();
1186 $record->course = 666;
1187 $record->type = 'qanda';
1188 $this->assertTrue($DB->insert_record('test_table_cust0', $record, false));
1190 // Hackery starts here, depending of the db family we are testing... execute
1191 // the needed SQL statements to get the "type" field defined as enum
1193 switch ($DB->get_dbfamily()) {
1194 case 'mysql': // It's ENUM field for mysql
1195 $stmt = "ALTER TABLE {$DB->get_prefix()}test_table_cust0 MODIFY type ENUM ('general', 'qanda', 'moodle') NOT NULL DEFAULT 'general'";
1197 default: // It's check constraint for "normal" DBs
1198 $stmt = "ALTER TABLE {$DB->get_prefix()}test_table_cust0 ADD CONSTRAINT ttcu0_ck CHECK (type IN ('general', 'qanda', 'moodle'))";
1200 $DB->change_database_structure($stmt);
1202 // Check table has enums now
1203 $this->assertTrue($dbman->check_constraint_exists($table, $field));
1204 $this->assertTrue($dbman->find_check_constraint_name($table, $field));
1206 // Removing an enum value
1207 $dbman->drop_enum_from_field($table, $field);
1209 // Chech table hasn't enum anymore
1210 $this->assertFalse($dbman->check_constraint_exists($table, $field));
1211 $this->assertFalse($dbman->find_check_constraint_name($table, $field));
1213 $dbman->drop_table($table);
1216 public function testRenameField() {
1217 $DB = $this->tdb; // do not use global $DB!
1218 $dbman = $this->tdb->get_manager();
1220 $table = $this->create_deftable('test_table0');
1221 $field = new xmldb_field('type');
1222 $field->set_attributes(XMLDB_TYPE_CHAR, '20', null, XMLDB_NOTNULL, null, 'general', 'course');
1224 $dbman->rename_field($table, $field, 'newfieldname');
1226 $columns = $DB->get_columns('test_table0');
1228 $this->assertFalse(array_key_exists('type', $columns));
1229 $this->assertTrue(array_key_exists('newfieldname', $columns));
1233 public function testIndexExists() {
1234 // Skipping: this is just a test of find_index_name
1237 public function testFindKeyName() {
1238 $dbman = $this->tdb->get_manager();
1240 $table = $this->create_deftable('test_table0');
1241 $key = $table->getKey('primary');
1243 // With Mysql, the return value is actually "mdl_test_id_pk"
1244 $result = $dbman->find_key_name($table, $key);
1245 $this->assertTrue(!empty($result));
1248 public function testDeleteTablesFromXmldbFile() {
1250 $dbman = $this->tdb->get_manager();
1252 $this->create_deftable('test_table1');
1254 $this->assertTrue($dbman->table_exists('test_table1'));
1256 // feed nonexistent file
1258 ob_start(); // hide debug warning
1259 $dbman->delete_tables_from_xmldb_file('fpsoiudfposui');
1261 $this->assertTrue(false);
1262 } catch (Exception $e) {
1264 $this->assertTrue($e instanceof moodle_exception);
1267 // Real file but invalid xml file
1269 if (!empty($CFG->xmldbdisablenextprevchecking)) {
1270 $CFG->xmldbdisablenextprevchecking = false;
1274 ob_start(); // hide debug warning
1275 $dbman->delete_tables_from_xmldb_file($CFG->libdir . '/ddl/simpletest/fixtures/invalid.xml');
1276 $this->assertTrue(false);
1278 } catch (Exception $e) {
1280 $this->assertTrue($e instanceof moodle_exception);
1283 $CFG->xmldbdisablenextprevchecking = true;
1286 // Check that the table has not been deleted from DB
1287 $this->assertTrue($dbman->table_exists('test_table1'));
1289 // Real and valid xml file
1290 $dbman->delete_tables_from_xmldb_file($CFG->libdir . '/ddl/simpletest/fixtures/xmldb_table.xml');
1292 // Check that the table has been deleted from DB
1293 $this->assertFalse($dbman->table_exists('test_table1'));
1296 public function testInstallFromXmldbFile() {
1298 $dbman = $this->tdb->get_manager();
1300 // feed nonexistent file
1302 ob_start(); // hide debug warning
1303 $dbman->install_from_xmldb_file('fpsoiudfposui');
1305 $this->assertTrue(false);
1306 } catch (Exception $e) {
1308 $this->assertTrue($e instanceof moodle_exception);
1311 // Real but invalid xml file
1313 if (!empty($CFG->xmldbdisablenextprevchecking)) {
1314 $CFG->xmldbdisablenextprevchecking = false;
1318 ob_start(); // hide debug warning
1319 $dbman->install_from_xmldb_file($CFG->libdir.'/ddl/simpletest/fixtures/invalid.xml');
1321 $this->assertTrue(false);
1322 } catch (Exception $e) {
1324 $this->assertTrue($e instanceof moodle_exception);
1327 $CFG->xmldbdisablenextprevchecking = true;
1330 // Check that the table has not yet been created in DB
1331 $this->assertFalse($dbman->table_exists('test_table1'));
1333 // Real and valid xml file
1334 $dbman->install_from_xmldb_file($CFG->libdir.'/ddl/simpletest/fixtures/xmldb_table.xml');
1335 $this->assertTrue($dbman->table_exists('test_table1'));
1338 public function test_temp_tables() {
1339 $DB = $this->tdb; // do not use global $DB!
1340 $dbman = $this->tdb->get_manager();
1342 // Create temp table0
1343 $table0 = $this->tables['test_table0'];
1344 $dbman->create_temp_table($table0);
1345 $this->assertTrue($dbman->table_exists('test_table0'));
1347 // Try to create temp table with same name, must throw exception
1348 $dupetable = $this->tables['test_table0'];
1350 $dbman->create_temp_table($dupetable);
1351 $this->assertTrue(false);
1352 } catch (Exception $e) {
1353 $this->assertTrue($e instanceof ddl_exception);
1356 // Try to create table with same name, must throw exception
1357 $dupetable = $this->tables['test_table0'];
1359 $dbman->create_table($dupetable);
1360 $this->assertTrue(false);
1361 } catch (Exception $e) {
1362 $this->assertTrue($e instanceof ddl_exception);
1365 // Create another temp table1
1366 $table1 = $this->tables['test_table1'];
1367 $dbman->create_temp_table($table1);
1368 $this->assertTrue($dbman->table_exists('test_table1'));
1370 // Get columns and perform some basic tests
1371 $columns = $DB->get_columns('test_table1');
1372 $this->assertEqual(count($columns), 11);
1373 $this->assertTrue($columns['name'] instanceof database_column_info);
1374 $this->assertEqual($columns['name']->max_length, 30);
1375 $this->assertTrue($columns['name']->has_default);
1376 $this->assertEqual($columns['name']->default_value, 'Moodle');
1378 // Insert some records
1379 $inserted = $this->fill_deftable('test_table1');
1380 $records = $DB->get_records('test_table1');
1381 $this->assertEqual(count($records), $inserted);
1382 $this->assertEqual($records[1]->course, $this->records['test_table1'][0]->course);
1383 $this->assertEqual($records[1]->secondname, $this->records['test_table1'][0]->secondname);
1384 $this->assertEqual($records[2]->intro, $this->records['test_table1'][1]->intro);
1387 $dbman->drop_temp_table($table1);
1388 $this->assertFalse($dbman->table_exists('test_table1'));
1390 // Try to drop non-existing temp table, must throw exception
1391 $noetable = $this->tables['test_table1'];
1393 $dbman->drop_temp_table($noetable);
1394 $this->assertTrue(false);
1395 } catch (Exception $e) {
1396 $this->assertTrue($e instanceof ddl_table_missing_exception);
1399 // Fill/modify/delete a few table0 records
1403 $dbman->drop_temp_table($table0);
1404 $this->assertFalse($dbman->table_exists('test_table0'));
1406 // Have dropped all these temp tables here, to avoid conflicts with other (normal tables) tests!
1409 public function test_concurrent_temp_tables() {
1410 $DB = $this->tdb; // do not use global $DB!
1411 $dbman = $this->tdb->get_manager();
1414 $record1 = (object)array(
1416 'secondname' => '11 important',
1417 'intro' => '111 important');
1418 $record2 = (object)array(
1420 'secondname' => '22 important',
1421 'intro' => '222 important');
1423 // Create temp table1 and insert 1 record (in DB)
1424 $table = $this->tables['test_table1'];
1425 $dbman->create_temp_table($table);
1426 $this->assertTrue($dbman->table_exists('test_table1'));
1427 $inserted = $DB->insert_record('test_table1', $record1);
1429 // Switch to new connection
1430 $cfg = $DB->export_dbconfig();
1431 if (!isset($cfg->dboptions)) {
1432 $cfg->dboptions = array();
1434 $DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
1435 $DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
1436 $dbman2 = $DB2->get_manager();
1437 $this->assertFalse($dbman2->table_exists('test_table1')); // Temp table not exists in DB2
1439 // Create temp table1 and insert 1 record (in DB2)
1440 $table = $this->tables['test_table1'];
1441 $dbman2->create_temp_table($table);
1442 $this->assertTrue($dbman2->table_exists('test_table1'));
1443 $inserted = $DB2->insert_record('test_table1', $record2);
1445 $dbman2->drop_temp_table($table); // Drop temp table before closing DB2
1446 $this->assertFalse($dbman2->table_exists('test_table1'));
1447 $DB2->dispose(); // Close DB2
1449 $this->assertTrue($dbman->table_exists('test_table1')); // Check table continues existing for DB
1450 $dbman->drop_temp_table($table); // Drop temp table
1451 $this->assertFalse($dbman->table_exists('test_table1'));
1454 public function test_reset_sequence() {
1456 $dbman = $DB->get_manager();
1458 $table = new xmldb_table('testtable');
1459 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1460 $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
1461 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1464 if ($dbman->table_exists($table)) {
1465 $dbman->drop_table($table);
1467 $dbman->create_table($table);
1468 $tablename = $table->getName();
1469 $this->tables[$tablename] = $table;
1471 $record = (object)array('id'=>666, 'course'=>10);
1472 $DB->import_record('testtable', $record);
1473 $DB->delete_records('testtable');
1475 $dbman->reset_sequence($table); // using xmldb object
1476 $this->assertEqual(1, $DB->insert_record('testtable', (object)array('course'=>13)));
1478 $DB->import_record('testtable', $record);
1479 $dbman->reset_sequence($tablename); // using string
1480 $this->assertEqual(667, $DB->insert_record('testtable', (object)array('course'=>13)));
1482 $dbman->drop_table($table);
1485 public function test_reserved_words() {
1486 $reserved = sql_generator::getAllReservedWords();
1487 $this->assertTrue(count($reserved) > 1);
1490 public function test_index_max_bytes() {
1492 $dbman = $DB->get_manager();
1495 for($i=0; $i<255; $i++) {
1496 $maxstr .= '言'; // random long string that should fix exactly the limit for one char column
1499 $table = new xmldb_table('testtable');
1500 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1501 $table->add_field('name', XMLDB_TYPE_CHAR, 255, null, XMLDB_NOTNULL, null);
1502 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1503 $table->add_index('name', XMLDB_INDEX_NOTUNIQUE, array('name'));
1506 if ($dbman->table_exists($table)) {
1507 $dbman->drop_table($table);
1509 $dbman->create_table($table);
1510 $tablename = $table->getName();
1511 $this->tables[$tablename] = $table;
1513 $rec = new stdClass();
1514 $rec->name = $maxstr;
1516 $id = $DB->insert_record($tablename, $rec);
1517 $this->assertTrue(!empty($id));
1519 $rec = $DB->get_record($tablename, array('id'=>$id));
1520 $this->assertIdentical($rec->name, $maxstr);
1522 $dbman->drop_table($table);
1525 $table = new xmldb_table('testtable');
1526 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1527 $table->add_field('name', XMLDB_TYPE_CHAR, 255+1, null, XMLDB_NOTNULL, null);
1528 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1529 $table->add_index('name', XMLDB_INDEX_NOTUNIQUE, array('name'));
1532 $dbman->create_table($table);
1533 $this->assertTrue(false);
1534 } catch (Exception $e) {
1535 $this->assertTrue($e instanceof coding_exception);
1539 public function test_index_composed_max_bytes() {
1541 $dbman = $DB->get_manager();
1544 for($i=0; $i<200; $i++) {
1548 for($i=0; $i<133; $i++) {
1552 $table = new xmldb_table('testtable');
1553 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1554 $table->add_field('name1', XMLDB_TYPE_CHAR, 200, null, XMLDB_NOTNULL, null);
1555 $table->add_field('name2', XMLDB_TYPE_CHAR, 133, null, XMLDB_NOTNULL, null);
1556 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1557 $table->add_index('name1-name2', XMLDB_INDEX_NOTUNIQUE, array('name1','name2'));
1560 if ($dbman->table_exists($table)) {
1561 $dbman->drop_table($table);
1563 $dbman->create_table($table);
1564 $tablename = $table->getName();
1565 $this->tables[$tablename] = $table;
1567 $rec = new stdClass();
1568 $rec->name1 = $maxstr;
1569 $rec->name2 = $reststr;
1571 $id = $DB->insert_record($tablename, $rec);
1572 $this->assertTrue(!empty($id));
1574 $rec = $DB->get_record($tablename, array('id'=>$id));
1575 $this->assertIdentical($rec->name1, $maxstr);
1576 $this->assertIdentical($rec->name2, $reststr);
1579 $table = new xmldb_table('testtable');
1580 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1581 $table->add_field('name1', XMLDB_TYPE_CHAR, 201, null, XMLDB_NOTNULL, null);
1582 $table->add_field('name2', XMLDB_TYPE_CHAR, 133, null, XMLDB_NOTNULL, null);
1583 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1584 $table->add_index('name1-name2', XMLDB_INDEX_NOTUNIQUE, array('name1','name2'));
1587 if ($dbman->table_exists($table)) {
1588 $dbman->drop_table($table);
1592 $dbman->create_table($table);
1593 $this->assertTrue(false);
1594 } catch (Exception $e) {
1595 $this->assertTrue($e instanceof coding_exception);
1599 public function test_char_size_limit() {
1601 $dbman = $DB->get_manager();
1603 $table = new xmldb_table('testtable');
1604 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1605 $table->add_field('name', XMLDB_TYPE_CHAR, xmldb_field::CHAR_MAX_LENGTH, null, XMLDB_NOTNULL, null);
1606 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1609 if ($dbman->table_exists($table)) {
1610 $dbman->drop_table($table);
1612 $dbman->create_table($table);
1613 $tablename = $table->getName();
1614 $this->tables[$tablename] = $table;
1616 // this has to work in all DBs
1618 for($i=0; $i<xmldb_field::CHAR_MAX_LENGTH; $i++) {
1619 $maxstr .= 'a'; // ascii only
1622 $rec = new stdClass();
1623 $rec->name = $maxstr;
1625 $id = $DB->insert_record($tablename, $rec);
1626 $this->assertTrue(!empty($id));
1628 $rec = $DB->get_record($tablename, array('id'=>$id));
1629 $this->assertIdentical($rec->name, $maxstr);
1632 // Following test is supposed to fail in oracle
1634 for($i=0; $i<xmldb_field::CHAR_MAX_LENGTH; $i++) {
1635 $maxstr .= '言'; // random long string that should fix exactly the limit for one char column
1638 $rec = new stdClass();
1639 $rec->name = $maxstr;
1642 $id = $DB->insert_record($tablename, $rec);
1643 $this->assertTrue(!empty($id));
1645 $rec = $DB->get_record($tablename, array('id'=>$id));
1646 $this->assertIdentical($rec->name, $maxstr);
1647 } catch (dml_write_exception $e) {
1648 if ($DB->get_dbfamily() === 'oracle') {
1649 $this->fail('Oracle does not support text fields larger than 4000 bytes, this is not a big problem for mostly ascii based languages');
1656 $table = new xmldb_table('testtable');
1657 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
1658 $table->add_field('name', XMLDB_TYPE_CHAR, xmldb_field::CHAR_MAX_LENGTH+1, null, XMLDB_NOTNULL, null);
1659 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
1662 if ($dbman->table_exists($table)) {
1663 $dbman->drop_table($table);
1665 $tablename = $table->getName();
1666 $this->tables[$tablename] = $table;
1669 $dbman->create_table($table);
1670 $this->assertTrue(false);
1671 } catch (Exception $e) {
1672 $this->assertTrue($e instanceof coding_exception);
1676 // Following methods are not supported == Do not test
1678 public function testRenameIndex() {
1680 $dbman = $this->tdb->get_manager();
1682 $table = $this->create_deftable('test_table0');
1683 $index = new xmldb_index('course');
1684 $index->set_attributes(XMLDB_INDEX_UNIQUE, array('course'));
1686 $this->assertTrue($dbman->rename_index($table, $index, 'newindexname'));
1689 public function testRenameKey() {
1691 $dbman = $this->tdb->get_manager();
1693 $table = $this->create_deftable('test_table0');
1694 $key = new xmldb_key('course');
1695 $key->set_attributes(XMLDB_KEY_UNIQUE, array('course'));
1697 $this->assertTrue($dbman->rename_key($table, $key, 'newkeyname'));