Recabamos por distintos sitios para así traerte la respuesta a tu duda, si continúas con alguna pregunta déjanos la duda y respondemos con gusto, porque estamos para servirte.
Esta página muestra cómo usar disparadores para implementar la lógica de deshacer / rehacer para una aplicación que usa SQLite como formato de archivo de la aplicación.
Diseño orientado a objetos
Esta nota de diseño considera que la base de datos es una colección de objetos. Cada tabla SQL es una clase. Cada fila es una instancia de esa clase. Por supuesto, existen otras formas de interpretar un esquema de base de datos SQL, y las técnicas descritas aquí funcionan igualmente bien bajo interpretaciones alternativas, pero una vista orientada a objetos parece ser más natural para la mayoría de los programadores contemporáneos.
Capturar cambios mediante disparadores
La idea central es crear una tabla especial (llamada “UNDOLOG” en el ejemplo) que contenga la información necesaria para deshacer / rehacer cambios en la base de datos. Para cada clase (tabla) en la base de datos que quiere participar en deshacer / rehacer, se crean disparadores que hacen que se realicen entradas en la tabla UNDOLOG para cada DELETE, INSERT y UPDATE de la clase participante. Las entradas UNDOLOG consisten en sentencias SQL ordinarias que se pueden reproducir para revertir los cambios.
Por ejemplo, suponga que desea deshacer / rehacer en una clase (tabla) que se ve así:
CREATETABLE ex1(a,b,c);
Los activadores para registrar cambios en la tabla EX1 podrían tener este aspecto:
CREATETEMPTRIGGER ex1_it AFTERINSERTON ex1 BEGININSERTINTO undolog VALUES(NULL,'DELETE FROM ex1 WHERE rowid='||new.rowid);END;CREATETEMPTRIGGER ex1_ut AFTERUPDATEON ex1 BEGININSERTINTO undolog VALUES(NULL,'UPDATE ex1 SET a='||quote(old.a)||',b='||quote(old.b)||',c='||quote(old.c)||' WHERE rowid='||old.rowid);END;CREATETEMPTRIGGER ex1_dt BEFORE DELETEON ex1 BEGININSERTINTO undolog VALUES(NULL,'INSERT INTO ex1(rowid,a,b,c) VALUES('||old.rowid||','||quote(old.a)||','||quote(old.b)||','||quote(old.c)||')');END;
Después de cada INSERT en ex1, el disparador ex1_it construye el texto de una instrucción DELETE que deshará el INSERT. El disparador ex1_ut construye una instrucción UPDATE que deshará los efectos de una UPDATE. Y el disparador ex1_dt construye una declaración que deshará los efectos de un DELETE.
Tenga en cuenta el uso de la función SQL quote () en estos disparadores. La función quote () convierte su argumento en una forma apropiada para su inclusión en una declaración SQL. Los valores numéricos no se modifican. Las comillas simples se agregan antes y después de las cadenas y las comillas simples internas se escapan. Los valores BLOB se representan utilizando la notación BLOB hexadecimal estándar de SQL. El uso de la función quote () asegura que las sentencias SQL utilizadas para deshacer y rehacer estén siempre a salvo de la inyección SQL.
Creación automática de disparadores
Los activadores como los anteriores se pueden ingresar manualmente, pero eso es tedioso. Una característica importante de la técnica que se muestra a continuación es que los disparadores se generan automáticamente.
El lenguaje de implementación para el código de ejemplo es TCL, aunque puede hacer lo mismo fácilmente en otro lenguaje de programación. Recuerde que el código aquí es una demostración de la técnica, no un módulo desplegable que automáticamente hará todo por usted. El código de demostración que se muestra a continuación se deriva del código real en uso de producción. Pero deberá realizar cambios para adaptarlo a su aplicación.
Para activar la lógica de deshacer / rehacer, invoque el comando undo :: activar con todas las clases (tablas) que van a participar en el deshacer / rehacer como argumentos. Utilice undo :: deactivate, undo :: freeze y undo :: undozeze para controlar el estado del mecanismo de deshacer / rehacer.
El comando undo :: activar crea disparadores temporales en la base de datos que registran todos los cambios realizados en las tablas nombradas en los argumentos.
Interfaz de aplicación
Después de una secuencia de cambios que definen un solo paso deshacer / rehacer, invoque el comando undo :: barrera para definir el límite de ese paso. En un programa interactivo, puede llamar a undo :: event después de cualquier cambio y se llamará a undo :: barrera automáticamente como una devolución de llamada inactiva.
Cuando el usuario presiona el botón Deshacer, invoca deshacer :: deshacer. Invocar deshacer :: rehacer cuando el usuario presiona el botón Rehacer.
En cada llamada a undo :: undo o undo :: redo, el módulo undo / redo invoca automáticamente los métodos status_refresh y reload_all en todos los espacios de nombres de nivel superior. Estos métodos deben definirse para reconstruir la pantalla o actualizar el estado del programa en función de los cambios deshechos / rehechos en la base de datos.
El código de demostración a continuación incluye un método status_refresh que se atenúa o activa los botones Deshacer y Rehacer y las entradas del menú dependiendo de si hay algo que deshacer o rehacer. Deberá redefinir este método para controlar los botones Deshacer y Rehacer en su aplicación.
El código de demostración asume que la base de datos SQLite se abre y se utiliza como un objeto de base de datos llamado “db”.
Código de ejemplo
# Everything goes in a private namespace namespace eval ::undo # proc: ::undo::activate TABLE ...# title: Start up the undo/redo system## Arguments should be one or more database tables (in the database associated# with the handle "db") whose changes are to be recorded for undo/redo# purposes.#proc activate args variable _undo if $_undo(active) return eval _create_triggers db $args set _undo(undostack) set _undo(redostack) set _undo(active)1set _undo(freeze)-1 _start_interval # proc: ::undo::deactivate# title: Halt the undo/redo system and delete the undo/redo stacks#proc deactivate variable _undo if !$_undo(active) return _drop_triggers db set _undo(undostack) set _undo(redostack) set _undo(active)0set _undo(freeze)-1 # proc: ::undo::freeze# title: Stop accepting database changes into the undo stack## From the point when this routine is called up until the next unfreeze,# new database changes are rejected from the undo stack.#proc freeze variable _undo if !; hd_resolve_one info exists _undo(freeze); hd_puts returnif $_undo(freeze)>=0 error "recursive call to ::undo::freeze" set _undo(freeze) ; hd_resolve_one db one SELECTcoalesce(max(seq),0)FROM undolog; hd_puts # proc: ::undo::unfreeze# title: Begin accepting undo actions again.#proc unfreeze variable _undo if !; hd_resolve_one info exists _undo(freeze); hd_puts returnif $_undo(freeze)<0 error "called ::undo::unfreeze while not frozen" db eval "DELETE FROM undolog WHERE seq>$_undo(freeze)"set _undo(freeze)-1 # proc: ::undo::event# title: Something undoable has happened## This routine is called whenever an undoable action occurs. Arrangements# are made to invoke ::undo::barrier no later than the next idle moment.#proc event variable _undo if $_undo(pending)=="" set _undo(pending) ; hd_resolve_one after idle ::undo::barrier; hd_puts # proc: ::undo::barrier# title: Create an undo barrier right now.#proc barrier variable _undo catch after cancel $_undo(pending) set _undo(pending) if !$_undo(active) refresh return setend ; hd_resolve_one db one SELECTcoalesce(max(seq),0)FROM undolog; hd_puts if $_undo(freeze)>=0&& $end>$_undo(freeze) setend $_undo(freeze) setbegin $_undo(firstlog) _start_interval if $begin==$_undo(firstlog) refresh return lappend _undo(undostack) ; hd_resolve_one list $begin $end; hd_puts set _undo(redostack) refresh # proc: ::undo::undo# title: Do a single step of undo#proc undo _step undostack redostack # proc: ::undo::redo# title: Redo a single step#proc redo _step redostack undostack # proc: ::undo::refresh# title: Update the status of controls after a database change## The undo module calls this routine after any undo/redo in order to# cause controls gray out appropriately depending on the current state# of the database. This routine works by invoking the status_refresh# module in all top-level namespaces.#proc refresh set body foreach ns ; hd_resolve_one namespace children ::; hd_puts if ; hd_resolve_one info proc $ns::status_refresh; hd_puts =="" continue append body $ns::status_refreshn proc ::undo::refresh $body refresh # proc: ::undo::reload_all# title: Redraw everything based on the current database## The undo module calls this routine after any undo/redo in order to# cause the screen to be completely redrawn based on the current database# contents. This is accomplished by calling the "reload" module in# every top-level namespace other than ::undo.#proc reload_all set body foreach ns ; hd_resolve_one namespace children ::; hd_puts if ; hd_resolve_one info proc $ns::reload; hd_puts =="" continue append body $ns::reloadn proc ::undo::reload_all $body reload_all ############################################################################### The public interface to this module is above. Routines and variables that# follow (and whose names begin with "_") are private to this module.############################################################################### state information#set _undo(active)0set _undo(undostack) set _undo(redostack) set _undo(pending) set _undo(firstlog)1set _undo(startstate) # proc: ::undo::status_refresh# title: Enable and/or disable menu options a buttons#proc status_refresh variable _undo if ; hd_resolve_one llength $_undo(undostack); hd_puts ==0 .mb.edit entryconfig Undo -state disabled .bb.undo config -state disabled else .mb.edit entryconfig Undo -state normal .bb.undo config -state normal if !$_undo(active); hd_resolve_one llength $_undo(redostack); hd_puts ==0 .mb.edit entryconfig Redo -state disabled .bb.redo config -state disabled else .mb.edit entryconfig Redo -state normal .bb.redo config -state normal # xproc: ::undo::_create_triggers DB TABLE1 TABLE2 ...# title: Create change recording triggers for all tables listed## Create a temporary table in the database named "undolog". Create# triggers that fire on any insert, delete, or update of TABLE1, TABLE2, ....# When those triggers fire, insert records in undolog that contain# SQL text for statements that will undo the insert, delete, or update.#proc _create_triggers db args catch $db eval DROPTABLE undolog $db eval CREATETEMPTABLE undolog(seq integerprimarykey,sqltext) foreach tbl $args set collist ; hd_resolve_one $db eval "pragma table_info($tbl)"; hd_puts # xproc: ::undo::_drop_triggers DB# title: Drop all of the triggers that _create_triggers created#proc _drop_triggers db set tlist ; hd_resolve_one $db eval SELECT name FROM sqlite_temp_schema WHEREtype='trigger'; hd_puts foreach trigger $tlist if !; hd_resolve_one regexp _.*_(i $trigger; hd_puts continue $db eval "DROP TRIGGER $trigger;" catch $db eval DROPTABLE undolog # xproc: ::undo::_start_interval# title: Record the starting conditions of an undo interval#proc _start_interval variable _undo set _undo(firstlog) ; hd_resolve_one db one SELECTcoalesce(max(seq),0)+1FROM undolog; hd_puts # xproc: ::undo::_step V1 V2# title: Do a single step of undo or redo## For an undo V1=="undostack" and V2=="redostack". For a redo,# V1=="redostack" and V2=="undostack".#proc _step v1 v2 variable _undo set op ; hd_resolve_one lindex $_undo($v1)end; hd_puts set _undo($v1) ; hd_resolve_one lrange $_undo($v1)0end-1; hd_puts foreach beginend $op break db eval BEGINset q1 "SELECT sql FROM undolog WHERE seq>=$begin AND seq<=$end ORDER BY seq DESC"set sqllist ; hd_resolve_one db eval $q1; hd_puts db eval "DELETE FROM undolog WHERE seq>=$begin AND seq<=$end"set _undo(firstlog) ; hd_resolve_one db one SELECTcoalesce(max(seq),0)+1FROM undolog; hd_puts foreach sql $sqllist db eval $sql db eval COMMIT reload_all setend ; hd_resolve_one db one SELECTcoalesce(max(seq),0)FROM undolog; hd_puts setbegin $_undo(firstlog) lappend _undo($v2) ; hd_resolve_one list $begin $end; hd_puts _start_interval refresh # End of the ::undo namespace
Aquí tienes las reseñas y calificaciones
Si sostienes alguna sospecha y disposición de desarrollar nuestro división puedes añadir una disquisición y con mucho placer lo estudiaremos.