Triggers Oracle

Triggers Oracle

by Francisco Marín Hernández -
Number of replies: 7
Sorry by the cuality of my language, but i'm spanish smile.

I have Moodle over Oracle, and i'm very interested about knowing how to create triggers in a Oracle database without having problems with function "insert_record" of the library "dmllib.php".

I have been investigating about that and i have discovered the problem is related with the generation of sequences for index and the class XMLDBTable.

I would like to know more than this because i'm very obfuscated :P.

Thank you a lot.




-----------------------
Triggers en Oracle
Average of ratings: -
In reply to Francisco Marín Hernández

Re: Triggers Oracle

by Eloy Lafuente (stronk7) -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Peer reviewers Picture of Plugin developers Picture of Testers
Hi Francisco,

when you define one XMLDBField in Moodle one of the attributes you can configure is the "sequence" one. In fact all table in Moodle MUST have one "id" field with that "sequence" attribute set to true.

Then, when the table is going to be created, XMLDBGenerators (different for each DB supported) will process such "sequence" attribute, generating the needed SQL to handle it.

For example, MySQL will, simply, add one "auto_increment" clause to the SQL generated, or Oracle will create one sequence and one trigger to handle it in a cross-db way.

Ciao smile
In reply to Eloy Lafuente (stronk7)

Re: Triggers Oracle

by Francisco Marín Hernández -
OK, thank you very much Eloy. Sincerely, thank you by the velocity of your answer. Now i see this clearer, but... I need to specify my problem a few more.

My Moodle can't create a course if my database Oracle has a trigger (of type AFTER INSERT) that inserts into another table (gc_plazos_inscripcion) which use ID of the new corresponding course row.

Exactly, this is the case:

CREATE OR REPLACE TRIGGER trigger_plazo_ins_vacio
AFTER INSERT OR DELETE ON m_course
FOR EACH ROW
BEGIN
IF inserting THEN
INSERT INTO gc_plazos_inscripcion (idplazocurso) VALUES (:NEW.id);
ELSIF deleting THEN
DELETE gc_plazos_inscripcion WHERE idplazocurso = :OLD.id;
END IF;
END;
/

With this trigger appear the famous white page after complete the form for a new course. I know that is related with the functions XMLDB of the function insert_record of dmllib.

But... are not the triggers to other level (to the level of DB) different than Moodle? I think that Oracle and administration of his tables wouldn't be related with Moodle. Really I don't understand.


In reply to Francisco Marín Hernández

Re: Triggers Oracle

by Eloy Lafuente (stronk7) -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Peer reviewers Picture of Plugin developers Picture of Testers
Well,

Moodle creates one "BEFORE INSERT" trigger that, if fact, only performs some action if there is one record being inserted and its "id" field haven't been defined. Normally insert_record() will fetch it directly from sequence and set it properly so the trigger haven't too much to do (because "id" has been defined).

What I'm not able to imagine is why your "AFTER INSERT" trigger collides with this at all. IMO is perfect to have both triggers defined and running.

Could be possible that your insert statement in gc_plazos_inscripcion is failing for any reason and that causes the blank page and the course nor being really inserted?

You should enable debug levels everywhere (apache, moodle...) and some Oracle SQL traces too in order to see what's happening exactly. If the page is failing somewhere should be a "nice" error logged.

Ciao smile


In reply to Francisco Marín Hernández

Re: Triggers Oracle

by Francisco Marín Hernández -
Hi Eloy, I'm going to response to my self sonrisa. I thought do it in english but... what I'm going to write is too long:

Creación de Triggers Oracle para Moodle 2.0

En Moodle 2.0 sobre Oracle podemos crear triggers directamente sobre la base de datos para cualquier implementación que estemos haciendo (módulos, bloques...). Sin embargo, debemos tener en cuenta los siguientes aspectos:

  1. Al contrario que en MySQL, en Oracle no podemos especificar un campo de una tabla como autoincrementable, lo que resulta útil para la genernación de identificadores de las tuplas (en Moodle siempre se les llama ID). Esto implica el uso de triggers, creados por defecto, que, cada vez que se hace una inserción, se disparan generando un nuevo identificador a partir de una secuencia.

  2. Moodle se abstrae del tipo de la base de datos sobre la que trabaja y maneja las inserciones con objetos XMLDBTable. Con esto el sistema puede trabajar sobre cualquier base de datos que pueda no ser MySQL (la que exige por defecto).

Sabiendo esto, aparentemente la creación de nuestros propios triggers no implica ningún problema sobre el funcionamiento del sistema, pues el manejo de triggers es un trabajo del SGBD Oracle y no de Moodle.

Sin embargo, sí existe un problema al crear triggers: Cuando queremos hacer una inserción sobre una tabla del sistema (por ejemplo, moodle_course) a la que le hemos asociado un trigger propio, la función encargada de la inserción genera el siguiente problema:

Esta función trabaja con un objeto XMLDBTable con el que realiza la inserción. A este se le asocia la nueva tupla a insertar asignándole además el identificador real que tendrá la nueva tupla en la base de datos. Para conocer dicho identificador, se lanza una consulta sobre la base de datos para tomar el cuerpo del trigger asociado a la tabla y así manejar desde el código PHP la sentencia encargada de la creación del nuevo ID. Concretamente, la función es “getSequenceFromDB” de moodle/lib/xmldb/classes/generator/oci8po/oci8po.class.php:

function getSequenceFromDB($xmldb_table) {

$tablename = strtoupper($this->getTableName($xmldb_table));

$sequencename = false;

if ($trigger = get_record_sql("SELECT trigger_name, trigger_body

FROM user_triggers

WHERE table_name = '{$tablename}'")) {

/// If trigger found, regexp it looking for the sequence name

preg_match('/.*SELECT (.*)\.nextval/i', $trigger->trigger_body, $matches);

if (isset($matches[1])) {

$sequencename = $matches[1];

}

}

return $sequencename;

}

Si la observamos, vemos que se usa la función “get_record_sql” que solo devuelve la primera tupla encontrada por la consulta lanzada. Esto es un problema para nosotros, pues estamos en la situación en la que tenemos una tabla con dos triggers asociados: el asignador por defecto para la generación de ID y el que nosotros hemos creado. Entonces, esta consulta podría devolver el cuerpo del trigger que hemos creado y no el que existe por defecto. Lo que probocaría un error (página en blanco) dado que es implosible generar un nuevo identificador.


¿Qué solución le damos a esto?

Necesitamos distinguir el trigger encargado de la generación de IDs del resto. Esto se puede hacer de muchísimas maneras, pero la que yo propongo es esta:

Modificamos la consulta lanzada con la función “get_record_sql” de manera que solo se obtenga el trigger correspondiente a la creación del nuevo ID. Esto lo podemos hacer porque sabemos que Moodle asigna al nombre de los triggers de este tipo un nombre de la forma “*_ID_TRG”. Así nos quedaría la consulta de la siguiente forma:

SELECT trigger_name, trigger_body

FROM user_triggers

WHERE table_name = '{$tablename}'

AND trigger_name like '%ID_TRG'

Con esto podemos crear Triggers Oracle en Moodle 2.0 sin problemas. La solución es muy cómoda aunque para aplicarla hay que suponer que Moodle siempre asignará a los triggers de este tipo un nombre de la forma “*_ID_TRG”, además de, evidentemente, nosotros tener siempre presente que no debemos crear triggers cuyo nombre termine como estos. Seguro que hay soluciones mejores y menos “arriesgadas”, algo de lo que espero tener constancia.


Notas:

  1. Con esta solución, podemos crear triggers de tipo AFTER y BEFORE sin problemas.

  2. Para el caso concreto de la inserción de un nuevo curso por parte de Moodle una vez rellenado el formulario, la secuencia completa de invocaciones hasta obtener el nombre del trigger generador es:

    1. edit.php: create_course($data), línea 98 aprox.

    2. lib.php: insert_record('course', $data), línea 2819 aprox.

    3. dmllib.php: find_sequence_name($xmldb_table);, línea 1496 aprox.

    4. ddllib.php: getSequenceFromDB($CFG->dbtype, $CFG->prefix); línea 590 aprox

    5. XMLDBTable.class.php: getSequenceFromDB($this); línea 1076 aprox.

    6. oci8po.class.php: get_record_sql("SELECT...”); línea 541 aprox.

  3. En el intérprete SQL podemos imprimir los errores al crear un trigger con la sentencia “show errors” inmediatamente debajo del código del trigger.

  4. Podríamos no tocar la consulta comentada. Dejar el código tal y como está. Pero esto nos obliga a incluir código a nuestro trigger para desde este generar el nuevo ID. Sin embargo, esta solución solo sirve si el nuestro es de tipo BEFORE INSERT.

    Ésto tendríamos que añadir:

    IF :NEW.id IS NULL THEN

    SELECT <secuencia asociada a la tabla>.nextval INTO :NEW.id FROM dual;

END IF;

Para el caso de inserciones en la tabla de cursos, la secuencia es: <prefijo>_cour_id_seq

Para conocer los triggers asociados a una determinada tabla:

SELECT trigger_name, trigger_type, trigger_body

FROM user_triggers

WHERE table_name = '<nombre de la tabla>';

In reply to Francisco Marín Hernández

Re: Triggers Oracle

by Eloy Lafuente (stronk7) -
Picture of Core developers Picture of Documentation writers Picture of Moodle HQ Picture of Peer reviewers Picture of Plugin developers Picture of Testers
Hi Francisco,

really nice report explaining the problem perfectly! cool (although I'd recommend you not to use Spanish in these forums, mainly because a lot of people will read this message and won't be able to help you) wink

Anyway, summarizing, current detection of triggers (and sequences) under Oracle XMLDB generator causes no other (custom) triggers to be added to the tables in order to perform integrations with other systems.

I've reported that in the Tracker with issue number MDL-14567. Also, it's fixed now sorpresa and current detection of triggers (and sequences) is more accurate, allowing other triggers to work together with Moodle ones under Oracle. This has been fixed for Moodle 1.9 and 2.0. Yes

Ciao (y muchas gracias!) smile
In reply to Eloy Lafuente (stronk7)

Re: Triggers Oracle

by Francisco Marín Hernández -
Good idea Eloy!! I'm glad for your answer reporting this "bug" in the Moodle Tracker. In future I will post it again in english.

Thanks again!! :D
In reply to Eloy Lafuente (stronk7)

Re: Triggers Oracle

by Francisco Marín Hernández -
Good idea Eloy!! I'm glad for your answer reporting this "bug" in the Moodle Tracker. In future I will post it again in english.

Thanks again!! :D