`

SQLite中如何用触发器执行取消和重做逻辑--转

阅读更多

 

SQLite中如何用触发器执行取消和重做逻辑

<script></script>标签:

知识/探索

<!-- 正文开始 -->

这页主要描述一个使用SQLite作为主要数据结构的应用程序如何使用触发器去执行取消和重做逻辑。

我的想法是创建一个特殊的表格(例如名为撤销记录),表格保存数据库撤销和重做变化所需的信息。因为数据库中的每个表格都需要参与撤销和重做,每个DELETE,INSERT,和UPDATE都生成了触发器,DELETE,INSERT,和UPDATE可以在撤销日志表格中生成登记项,这个登记项将撤销操作。撤销表格中的登记项由一般的SQL语句组成,为了完成撤销,SQL语句可以被重新运行。

例如,如果你想在类似下面表格中执行撤销或重:

 CREATE TABLE ex1(a,b,c);
 

Then triggers would be created as follows:

 CREATE TEMP TRIGGER _ex1_it AFTER INSERT ON ex1 BEGIN
INSERT INTO undolog VALUES(NULL,'DELETE FROM ex1 WHERE rowid='||new.rowid);
END;
CREATE TEMP TRIGGER _ex1_ut AFTER UPDATE ON ex1 BEGIN
INSERT INTO undolog VALUES(NULL,'UPDATE ex1
SET a='||quote(old.a)||',b='||quote(old.b)||',c='||quote(old.c)||'
WHERE rowid='||old.rowid);
END;
CREATE TEMP TRIGGER _ex1_dt BEFORE DELETE ON ex1 BEGIN
INSERT INTO undolog VALUES(NULL,'INSERT INTO ex1(rowid,a,b,c)
VALUES('||old.rowid||','||quote(old.a)||','||quote(old.b)||
','||quote(old.c)||')');
END;
 

在ex1表格中执行每个INSERT后, the _ex1_it 触发器生成DELETE语句的文本,它将撤销INSERT操作。 The _ex1_ut触发器生成UPDATE语句,这语句将取消一个UPDATE所产生的作用。_ex1_dt触发器生成一个语句,这语句将取消一个 DELETE所具有的作用。

要注意quote()函数在这些触发器中的使用。quote()函数在SQLite中是标准的。它把它的参数转换成一种适合被包含在 SQL语句中的形式。数字值不改变。单个的quotes被加在字符串之前或之后,任何内在的单个quotes都被逃逸。quote()函数被加入 SQLite是为了执行撤销和重做操作。

当然,你也可以像上面一样用手生成触发器。但这个技术最突出的特点就是这些触发器可以自动生成。

例子中编码的语言是TCL。使用其它语言也是可以的,但是有可能要做更多工作。记住,这里的编码是demonstration技术,不是一个方便的模式,不能自动的为你做每件事。下面所示的demonstration编码是源于程序执行过程中所使用的真实的代码。但你在使用它的时候,为了满足你的需要,你需要做些改变。

为了激活撤销和重做的逻辑,激活要参加撤销和重做的所有种类的(表格)undo::activate指令作为参数。用undo::deactivate, undo::freeze, and undo::unfreeze来控制undo/redo机制的状态。

The undo::activate指令在数据库中生成临时的触发器,它记录表格中所有被命名为参数的变化。

在一系列的改变定义了一个单独的undo/redo步骤后,激活undo::barrier指令来定义那步的局限性。在一个交互式的程序中,在做任何改动后,你可以调用undo::event,undo::barrier将被自动调用作为一个等待的回调。

当使用者按下Undo按钮,激活undo::undo。当使用者按下Redo按钮,激活undo::redo。

在调用undo::undo or undo::redo,undo/redo模块将自动在所有顶级有名字的空间激活程序status_refresh和reload_all。这些程序应该被定义用来重建画面,或者更新基于undone/redon在数据库中变化的程序的状态。 

下面的 demonstration代码包含一个status_refresh程序,它激活Undo and Redo按钮,根据没做的和要重做的事来选菜单。你需要重新定义这个程序,用来涉及特定的Undo和Redo按钮,为你的应用线则进入菜单。这里所提供的执行只是一个例子。

demonstration代码假定SQLite数据库是用一个名为"db"的句柄打开的。对于一个内置内存的数据库来说,合适的指令应如下:

       sqlite3 db :memory:

这里是demonstration代码:

# 每件事都在一个私有的有名称的空间里进行
namespace eval ::undo {

# proc:  ::undo::activate TABLE ...
# title: Start up the undo/redo system
#
# 参数应是在一个或多个数据库表格(在数据库中和句柄"db"相关联)
# 它们的变化被记录下来是为了撤销和重做的目的。
proc activate {args} {
  variable _undo
  if {$_undo(active)} return
  eval _create_triggers db $args
  set _undo(undostack) {}
  set _undo(redostack) {}
  set _undo(active) 1
  set _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) 0
  set _undo(freeze) -1
}

# proc:  ::undo::freeze
# title: Stop accepting database changes into the undo stack
#
# 当这个例行程序被启用直到下一步被解冻前,新的数据库的变化将不被记录在撤销存储栈。
#
proc freeze {} {
  variable _undo
  if {![info exists _undo(freeze)]} return
  if {$_undo(freeze)>=0} {error "recursive call to ::undo::freeze"}
  set _undo(freeze) [db one {SELECT coalesce(max(seq),0) FROM undolog}]
}

# proc:  ::undo::unfreeze
# title: Begin accepting undo actions again.
#
proc unfreeze {} {
  variable _undo
  if {![info exists _undo(freeze)]} return
  if {$_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
#
# 当一个不可撤销的操作发生的时候调用这个程序。在下一个空闲时刻之前激活::undo::barrier。
#
proc event {} {
  variable _undo
  if {$_undo(pending)==""} {
    set _undo(pending) [after idle ::undo::barrier]
  }
}

# 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
  }
  set end [db one {SELECT coalesce(max(seq),0) FROM undolog}]
  if {$_undo(freeze)>=0 && $end>$_undo(freeze)} {set end $_undo(freeze)}
  set begin $_undo(firstlog)
  _start_interval
  if {$begin==$_undo(firstlog)} {
    refresh
    return
  }
  lappend _undo(undostack) [list $begin $end]
  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
#
# 基于现行数据库的状态,为了合理的进行控制,撤销模块在任何撤销和重做后调用这个例行程序。
这个模块通过激活所有顶级有名称的空间中的status_refresh模块来工作。
#
proc refresh {} {
  set body {}
  foreach ns [namespace children ::] {
    if {[info proc ${ns}::status_refresh]==""} continue
    append body ${ns}::status_refresh\n
  }
  proc ::undo::refresh {} $body
  refresh
}

# proc:   ::undo::reload_all
# title:  Redraw everything based on the current database
#
# 为了使屏幕在数据库内容的基础上被完全重新绘图,撤销模块在任何的撤销和重做后都调用这个
# 例行程序。通过在每个顶级的有名称的空间调用"reload" 模块而不是::undo来完成这个程序。
proc reload_all {} {
  set body {}
  foreach ns [namespace children ::] {
    if {[info proc ${ns}::reload]==""} continue
    append body ${ns}::reload\n
  }
  proc ::undo::reload_all {} $body
  reload_all
}
##############################################################################
# 这个模块的公共接口程序在上面。例行程序和变量静态追踪(名字以"_"开头的)是这个模块私有的。
##############################################################################

# state information
#
set _undo(active) 0
set _undo(undostack) {}
set _undo(redostack) {}
set _undo(pending) {}
set _undo(firstlog) 1
set _undo(startstate) {}


# proc:  ::undo::status_refresh
# title: Enable and/or disable menu options a buttons
#
proc status_refresh {} {
  variable _undo
  if {!$_undo(active) || [llength $_undo(undostack)]==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) || [llength $_undo(redostack)]==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
#
# 在数据库中创建一个名为"undolog"的临时表格。创建可以激发任何 insert, delete, or update of TABLE1, TABLE2, ....的触发器。
# 当这些触发器激发的时候,insert records in 在未做日志中插入记录,这些未做日志中包含SQL语句的文本,这些语句将撤销insert, delete,或update。
#
proc _create_triggers {db args} {
  catch {$db eval {DROP TABLE undolog}}
  $db eval {CREATE TEMP TABLE undolog(seq integer primary key, sql text)}
  foreach tbl $args {
    set collist [$db eval "pragma table_info($tbl)"]
    set sql "CREATE TEMP TRIGGER _${tbl}_it AFTER INSERT ON $tbl BEGIN\n"
    append sql "  INSERT INTO undolog VALUES(NULL,"
    append sql "'DELETE FROM $tbl WHERE rowid='||new.rowid);\nEND;\n"

    append sql "CREATE TEMP TRIGGER _${tbl}_ut AFTER UPDATE ON $tbl BEGIN\n"
    append sql "  INSERT INTO undolog VALUES(NULL,"
    append sql "'UPDATE $tbl "
    set sep "SET "
    foreach {x1 name x2 x3 x4 x5} $collist {
      append sql "$sep$name='||quote(old.$name)||'"
      set sep ","
    }
    append sql " WHERE rowid='||old.rowid);\nEND;\n"

    append sql "CREATE TEMP TRIGGER _${tbl}_dt BEFORE DELETE ON $tbl BEGIN\n"
    append sql "  INSERT INTO undolog VALUES(NULL,"
    append sql "'INSERT INTO ${tbl}(rowid"
    foreach {x1 name x2 x3 x4 x5} $collist {append sql ,$name}
    append sql ") VALUES('||old.rowid||'"
    foreach {x1 name x2 x3 x4 x5} $collist {append sql ,'||quote(old.$name)||'}
    append sql ")');\nEND;\n"

    $db eval $sql
  }
}

# xproc:  ::undo::_drop_triggers DB
# title:  Drop all of the triggers that _create_triggers created
#
proc _drop_triggers {db} {
  set tlist [$db eval {SELECT name FROM sqlite_temp_master
                       WHERE type='trigger'}]
  foreach trigger $tlist {
    if {![regexp {^_.*_(i|u|d)t$} $trigger]} continue
    $db eval "DROP TRIGGER $trigger;"
  }
  catch {$db eval {DROP TABLE undolog}}
}

# xproc: ::undo::_start_interval
# title: Record the starting conditions of an undo interval
#
proc _start_interval {} {
  variable _undo
  set _undo(firstlog) [db one {SELECT coalesce(max(seq),0)+1 FROM undolog}]
}

# 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 [lindex $_undo($v1) end]
  set _undo($v1) [lrange $_undo($v1) 0 end-1]
  foreach {begin end} $op break
  db eval BEGIN
  set q1 "SELECT sql FROM undolog WHERE seq>=$begin AND seq<=$end
          ORDER BY seq DESC"
  set sqllist [db eval $q1]
  db eval "DELETE FROM undolog WHERE seq>=$begin AND seq<=$end"
  set _undo(firstlog) [db one {SELECT coalesce(max(seq),0)+1 FROM undolog}]
  foreach sql $sqllist {
    db eval $sql
  }
  db eval COMMIT
  reload_all

  set end [db one {SELECT coalesce(max(seq),0) FROM undolog}]
  set begin $_undo(firstlog)
  lappend _undo($v2) [list $begin $end]
  _start_interval
  refresh
}


# End of the ::undo namespace
}
分享到:
评论

相关推荐

    sqlite 触发器

    `WHEN`子句中的表达式只有为真时,才会执行触发器动作。 #### 五、新旧记录引用 在触发器动作中,可以使用以下两种方式引用新旧记录的列: - `NEW.column-name`:用于引用新记录的列。 - `OLD.column-name`:用于...

    SQLite:SQLite触发器与事件教程.docx

    SQLite:SQLite触发器与事件教程

    SQLite触发器的SQL语法.pdf

    SQLite 触发器是数据库 schema 中的一种自动执行的数据库操作,发生在特定的数据库事件,如 DELETE、INSERT、UPDATE 时。触发器可以由在特殊表上执行的语句触发,或 UPDATE 表中特定的字段时触发。 CREATE TRIGGER ...

    sqlite-jdbc-3.34.0-API文档-中文版.zip

    赠送jar包:sqlite-jdbc-3.34.0.jar; 赠送原API文档:sqlite-jdbc-3.34.0-javadoc.jar; 赠送源代码:sqlite-jdbc-3.34.0-sources.jar;...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    用触发器实现SQLite的外键约束

    ### 使用触发器实现SQLite的外键约束 #### 背景与问题介绍 在进行数码相框嵌入式开发的过程中,开发团队选择了SQLite作为数据库管理系统。然而,在编码过程中遇到了一个较为棘手的问题:SQLite默认情况下不支持...

    sqlite-tools-win-x64-3440200.zip

    通过这个工具,用户可以直接与SQLite数据库进行交互,执行SQL语句,创建、修改和查询数据库。它支持所有的SQL标准,包括数据定义语言(DDL)、数据操纵语言(DML)以及数据控制语言(DCL)。例如,你可以用它来创建...

    SQLite触发器详细指南

    在触发器的定义中,可以使用WHEN子句来决定在什么条件下执行触发器内部的语句。NEW和OLD关键字可用于引用即将被插入、更新或删除的行的元素。OLD用于引用更新或删除操作中即将被替换的行的数据,而NEW用于引用插入...

    SQLiteStudio-3.4.4-windows-x64-installer.zip

    3. **SQL编辑器**:提供了一个完善的SQL编辑器,支持语法高亮、自动补全和错误检查,帮助用户编写和执行复杂的SQL查询。 4. **数据库设计**:可以设计和修改表结构,包括添加、删除或修改字段,设置主键、外键等...

    sqlite-shell-win32-x86: sqlite3.exe

    2. 为了方便命令行执行,将sqlite3.exe放到svn 项目的主目录下,和.svn目录同级下。 3. cmd进入该目录下,执行 sqlite3 .svn/wc.db "select * from work_queue".看到有4条记录。就是刚才我执行的一些操作。 226539|...

    全面解析_SQLite

    #### 七、触发器执行取消与重做逻辑 - **触发器机制**:SQLite 支持通过触发器实现事务的回滚与恢复,从而保障数据一致性。 以上内容仅是《全面解析 SQLite.pdf》文件的部分概括,该文档提供了更详尽的技术细节和...

    sqlite-tools-win32-x86-3310100.zip

    通过这个工具,你可以创建数据库、创建表、插入数据、更新数据、查询数据以及执行更复杂的数据库操作,如视图、触发器和存储过程。 2. `sqlite3.exe`:这是SQLite的命令行实用程序,可以打开一个SQLite数据库,执行...

    SQLite 全面解析

    SQLite支持使用触发器来执行取消和重做逻辑。这对于需要事务处理的应用程序非常重要。 SQLite提供了C/C++开发接口(API函数),方便开发者进行SQLite数据库编程。在Visual Studio 2003下编译SQLite也是一个重要的...

    sqlite-tools-win32-x86-3290000

    sqlite-tools-win32-x86-3290000 通常包含了用于管理SQLite数据库的命令行工具,如sqlite3.exe,这个工具可以用来创建、打开、查询和管理SQLite数据库文件(通常以.db为扩展名)。 用户可以在该目录下运行sqlite3....

    sqlite-jdbc-3.15.1-API文档-中文版.zip

    赠送jar包:sqlite-jdbc-3.15.1.jar; 赠送原API文档:sqlite-jdbc-3.15.1-javadoc.jar; 赠送源代码:sqlite-jdbc-3.15.1-sources.jar;...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    sqlite-dll-win32-x86-3080700.zip

    SQLite是一个开源的嵌入式关系型数据库,它无需单独的服务器进程,可以直接在应用程序中使用。这个"sqlite-dll-win32-x86-3080700.zip"压缩包包含的是SQLite针对Windows 32位平台的动态链接库(DLL)文件。 首先,...

    sqlite-shell-win32-x86-3080200.zip

    SQLite Shell是与SQLite数据库交互的一个重要工具,通过这个客户端,用户可以直接在命令行环境下执行SQL语句,进行数据查询、插入、更新和删除等操作。它支持所有的SQL标准,包括创建数据库、表,定义字段,以及复杂...

    Shape与Sqlite数据转换

    - **过程**:此命令会将名为“input.shp”的Shapefile导入到一个名为“output.sqlite”的SQLite数据库中,其中包含所有几何、属性和元数据。 - **数据结构**:在SQLite中,Shapefile的几何数据通常被存储为WKB...

    sqlite-shell-win32-x86-3080406.zip

    2. 为了方便命令行执行,将sqlite3.exe放到svn 项目的主目录下,和.svn目录同级下。 3. 执行 sqlite3 .svn/wc.db "select * from work_queue".看到有4条记录。就是刚才我执行的一些操作。 226539|(sync-file-flags ...

Global site tag (gtag.js) - Google Analytics