Development Guide

Data Retrieval and Manipulation

Data Retrieval and Manipulation

With release 5.2 ILIAS uses ilDBInterface to provide a database abstraction layer that gives full portability to run ILIAS on MySQL or PostgreSQL (removed with ILIAS 8).

Portability and Future Conventions

ILIAS requires that all table and field names use lower case characters a-z, underscore "_" and numbers 0-9. Multiple words should be separated by "_", e.g. "user_id".

Table names should start with the Service or Module ID, to make clear, which Service or Module is responsible for managing the table data e.g. "frm_posting".

ILIAS Database Object

ILIAS provides a global database object in variable $DIC->database() to access the database. All database functionality must be accessed through methods of this object. Some methods return ilPDOStatement objects. Instead of calling their methods directly, these ilPDOStatement objects should be passed to $DIC->database() again. Example:

// WRONG
$result = $DIC->database()->query(...);
$n = $result->numRows();

// CORRECT
$result = $DIC->database()->query(...);
$n = $DIC->database()->numRows($result);

Supported Column Types and Attributes

ilDBInterface supports a number of abstract column types and attributes. The following table lists the supported column types, their attributes and the mapping to the different DBMS column types.

ilDBInterface Type Supported by ILIAS Supported Attributes MySQL Mapping Oracle Mapping
text notnull, length (must be >0 <=4000), default, fixed varchar, char char, varchar2
integer notnull, length (must be 1, 2, 3, 4 or 8), tinyint, smallint, mediumint, int, bigint number(3), number(5), number(8), number(10), number(20)
unsigned must not be true, default
float notnull, default double number
date notnull, default date date
time notnull, default time date
timestamp notnull, default datetime date
clob notnull, default longtext clob

Queries

SELECT Queries

Queries are done with the $DIC->database()-query(...) method. All parameters must be quoted by using the $DIC->database()->quote(...) method. With ILIAS 4.0 you must pass a second parameter with the quote-Method, the (abstract ilDBInterface) type of the variable.

$result = $DIC->database()->query("SELECT * FROM usr_data");
$result = $DIC->database()->query("SELECT * FROM usr_data WHERE id = ".$DIC->database()->quote($id, "integer"));

Formatted Query

Using the quote method within the statements strings may look confusing if a larger number of parameters are included into the statement. An alternative is the $DIC->database()->queryF(...) method, that uses %s placeholders for the parameters to be inserted. After the statement you must pass two arrays containing the types and the values for each parameter to the method.

$result = $DIC->database()->queryF("SELECT * FROM desktop_item WHERE ".
        "item_id = %s AND type = %s AND user_id = %s",
        array("integer", "text", "integer"),
        array($a_item_id, $a_type, $a_usr_id));

Executing multiple similar SELECTS

Prepared statements can be used for multiple similar operations. They should be used only in rare cases, where a lot of data is process with similar queries. In most of the cases prepared statements will reduce performance (e.g. in MySQL 5.0 query cache cannot be used). Prepare queries are done using the $DIC->database()->prepare(...) and $DIC->database()->execute(...) methods. If the prepared query is not used anymore, use $DIC->database()->free(...) to deallocate the resources.

$statement = $DIC->database()->prepare("SELECT firstname FROM usr_data WHERE usr_id > ? AND usr_id < ?",
        array("integer", "integer"));
$result1 = $DIC->database()->execute($statement, array(1000, 2000));
$result2 = $DIC->database()->execute($statement, array(2000, 3000));
$DIC->database()->free($statement);

Fetching Result Row

To fetch a row from a result set you may either use $DIC->database()->fetchAssoc(...) or $DIC->database()->fetchObject(...), which will return a result row as PHP associative array or as PHP object.

while ($record = $DIC->database()->fetchAssoc($result))

or

while ($record = $DIC->database()->fetchObject($result))

Using Limits

Limits must be done with the setLimit() method of $DIC->database(). Limit is only allowed for SELECT queries, since it is not supported for data manipulation in ilDBInterface.

$DIC->database()->setLimit(10, 0); // limit result to 10 datasets beginning at offset 0
$result = $DIC->database()->queryF("SELECT firstname FROM usr_data WHERE usr_id > %s AND usr_id < %s",
        array("integer", "integer"), array(1000, 2000)
);
while (($row = $DIC->database()->fetchArray($result))) 
{
    echo $row["firstname"] . "\n";
}

Data Manipulation (INSERT/UPDATE/DELETE)

Data Manipulation

For any data manipulation like INSERT, UPDATE and DELETE, you should usually use the $DIC->database()->manipulate(...) operation.

$affected_rows = $DIC->database()->manipulate("DELETE FROM my_table");
$affected_rows = $DIC->database()->manipulate("DELETE FROM my_table WHERE id = ".$DIC->database()->quote($id, "integer"));

Formatted Data Manipulation

Using the quote method within the statements strings may look confusing if a larger number of parameters are included into the statement. An alternative is the $DIC->database()->manipulateF(...) method, that uses %s placeholders for the parameters to be inserted. After the statement you must pass two arrays containing the types and the values for each parameter to the method.

$DIC->database()->manipulateF("INSERT INTO desktop_item (item_id, type, user_id, parameters) VALUES ".
        " (%s,%s,%s,%s)",
        array("integer", "text", "integer", "text"),
        array($a_item_id,$a_type,$a_usr_id,$a_par));

Multiple similar data manipulation

Similar to queries we use $DIC->database()->prepareManip(...) and $DIC->database()->execute(...) for prepared statements. This can be useful, if multiple similar data manipulations should be executed. Please use this methods only in rare cases, if you are sure, that performance decline will be limited (or performance will be improved).

$statement = $DIC->database()->prepareManip("INSERT INTO usr_data (firstname, lastname) VALUES (?, ?)", 
        array("text", "text"));
$data = array("ILIAS", "Administrator");
$affectedRows = $DIC->database()->execute($statement, $data);
$DIC->database()->free($statement);

You can put data for multiple operations into an array and invoke $DIC->database()->executeMultiple(...) after $DIC->database()->prepareManip(...) for prepared statements.

$statement = $DIC->database()->prepareManip("INSERT INTO usr_data (id, firstname) VALUES (?,?)",
        array("integer", "text"));
$data = array(
        array(1, "Mike"),
        array(2, "Phil"));
$DIC->database()->executeMultiple($statement, $data);

The insert and update commands - Data manipulation when CLOB fields are involved

Insert and updates can also be done with special methods for these SQL data manipulation statements. If a CLOB field is involved in a data manipulation these commands must be used.

$DIC->database()->insert("table_name", array(
        "field1" =>        array("text", $a_val1),
        "field2" =>        array("text", $a_val2),
        "field3" =>        array("clob", $a_val3)
));
$DIC->database()->update("table_name", array(
        "field1" =>        array("text", $a_val1),
        "field2" =>        array("text", $a_val2),
        "field3" =>        array("clob", $a_val3)),
        array(
        "where1" =>        array("int", $a_where1),
        "where2" =>        array("text", $a_where2)
));

Database Updates

Please have a look into this document for directions on database migrations and schema updates are to be integrated into the system.

Creating, Modifying and Deleting Tables

Since ILIAS 4.0 tables will are defined in an abstracted way in ILIAS. Only the following $ilDB methods may be used, to create, modify and delete tables in the database update script. To create a new table, use $ilDB->createTable(...).

<?php
$fields = array(
    'text_32_fixed' => array(
        'type'     => 'text',
        'length'   => 32,
        'fixed' => true
    ),
    'integer_small' => array(
        'type'     => 'integer',
        'length'   => 2
    ),
    'date_' => array(
        'type' => 'date'
    ),
    'timestamp_' => array(
        'type' => 'timestamp'
    ),
    'clob_' => array(
        'type' => 'clob'
    ),
    'blob_' => array(
        'type' => 'blob'
    )
);

$ilDB->createTable("my_table", $fields);

?>

Renaming a table is done with $ilDB->renameTable(...).

$ilDB->renameTable("old_table_name", "new_table_name");

To delete a table, use $ilDB->dropTable(...).

$ilDB->dropTable("my_table");

Adding new table columns is done by the $ilDB->addTableColumn(...) method.

$ilDB->addTableColumn("my_table", "my_column", array("type" => "text", "length" => 20));

To modify a table use $ilDB->modifyTableColumn(...).

$ilDB->modifyTableColumn("my_table", "my_column", array("type" => "text", "length" => 30);

Renaming a column is done by $ilDB->renameTableColumn(...).

$ilDB->renameTableColumn("my_table", "old_column_name", "new_column_name");

To remove a table column, use $ilDB->dropTableColumn(...).

$ilDB->dropTableColumn("my_table", "column_name");

Primary Keys and Indices

To add a primary key, use the addPrimaryKey($a_table_name, $a_fields, $a_name = "pk") method of $ilDB.

$ilDB->addPrimaryKey("my_table", array("id"));

To drop a primary key, use function dropPrimaryKey($a_table, $a_name = "pk").

$ilDB->dropPrimaryKey("my_table");

Using indices is very similar. To create an index us addIndex($a_table, $a_fields, $a_name = "indx").

$ilDB->addIndex("my_table", array("id", "flag"), "id_flag");

To drop an index use dropIndex($a_table, $a_name = "indx").

$ilDB->dropIndex("my_table", "id_flag");

Foreign Keys

To add a foreign key, use the addForeignKey($foreign_key_name, $field_name, $table_name, $reference_field_name, $reference_table, $on_update = null, $on_delete = null) method of $ilDB.

$ilDB->addForeignKey('foreign_key', ['my_value'], 'my_table', ['other_value'], 'other_table');

The addForeignKey method also takes an ilForeignKeyConstraints for the on_update and on_delete arguments. But if you don't need these they can also set to null. If you want to use a constraint you have to choose one of the following:

CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT

Before you add a new foreign key, you should always check if this key already exists. This is done by the foreignKeyExists method.

$ilDB->foreignKeyExists('foreign_key', 'my_table');

To drop a foreign key, use function dropForeignKey("foreign_key_name", "table_name").

$ilDB->dropForeignKey('foreign_key', 'my_table');

Before you call this method you should also check if the foreign key exists, see foreignKeyExists.

Sequences / Auto Increments

To get rid of the MySQL specific auto_increment for unique ID's, ilDBInterface offeres sequences. A sequence is special database table which is created automatically by ilDBInterface which increments a sequence field in the database and uses it to create unique values. In ILIAS the name of a sequence should be the same as the corresponding database table. To create and drop sequences use createSequence($a_table_name, $a_start = 1) and dropSequence($a_table_name).

$ilDB->createSequence("my_table");
$ilDB->dropSequence("my_table");

To obtain the next ID of a sequence use nextId($a_table_name).

$id = $DIC->database()->nextID('usr_data');
$statement = $DIC->database()->prepare("INSERT INTO usr_data (usr_id, firstname, lastname) VALUES (?, ?, ?)",
        array("integer", "text", "text")
);
$data = array($id, "ILIAS", "Administrator");
$statement->execute($data);

Transactions

To use transaction, tables have to be converted to type InnoDB for MySQL first. The use of InnoDB and transactions is currently in a testing phase. We will introduce transactions for a smaller number of tables first to check the general influence on productive systems. Please contact the core team if you want to use transactions or the InnoDB table engine.

Transaction Handling

$DIC->database()->beginTransaction();

... // Data Manipulation Statements

if ($everything_ok)
{
      $DIC->database()->commit();
}
else
{
      $DIC->database()->rollback();
}

Reserved Words

Problem

The background of the investigation is the Mantis ticket [# 0021173: Database Installation (MySQL) step 5090] (https://www.ilias.de/mantis/view.php?id=21173). The database update script aborts at the point where a table with a column "type" is to be created. "type" is a word reserved by MySQL and thus can not be used in a query.

Investigations

ILIAS maintains a list of reserved words, since the introduction of ilDB. This list has not been updated yet, although MySQL has also introduced new reserved words with new versions of the DBMS.

The lists of the respective reserved words for MySQL can be found, for example, at https://dev.mysql.com/doc/refman/5.6/en/keywords.html (for version 5.6).

To find out which column and / or table names ILIAS uses that are actually reserved, the following queries can be used:

column names:

SELECT 
    column_name, table_name
FROM
    information_schema.columns

   JOIN mysql.help_keyword ON UPPER(column_name) = UPPER(name)
WHERE
    table_schema = 'ilias';

table name:

SELECT 
    table_name
FROM
    information_schema.tables

   JOIN mysql.help_keyword ON UPPER(table_name) = UPPER(name)
WHERE
    table_schema = 'ilias';

Currently there are 212 columns and 1 table in ILIAS, which are named with a reserved word.

Conclusion

The list of reserved words in ilDBInterface resp. the respective database classes (ilDBPdoMySQL, ...) do not need to be maintained, in particular a list of reserved words per database system makes no sense, as a global list would be necessary.

As the MySQL documentation describes, references to objects with reserved names only need to be quoted:

An identifier may be quoted or unquoted. If an identifier contains special characters or is a reserved word, you must quote it whenever you refer to it. (Exception: A reserved word that follows a period in a qualified name must be an identifier, so it need not be quoted.)

See https://dev.mysql.com/doc/refman/5.6/en/identifiers.html

The new database classes in ILIAS have been optimized so that all references to objects are quoted. Developers are encouraged to do this themselves with their own queries using:

...
$q = "SELECT * FROM " . $DIC->database()->quoteIdentifier('select');
...