非常教程

Sqlite参考手册

其他 | Miscellaneous

Automatic Undo/Redo With SQLite

本页演示如何使用触发器为使用SQLite作为其应用程序文件格式的应用程序实现撤销/重做逻辑。

面向对象的设计

本设计笔记认为数据库是对象的集合。每个SQL表是一个类。每一行都是该类的一个实例。当然,还有其他解释SQL数据库模式的方法,这里描述的技术在其他解释下也同样适用,但对于大多数当代程序员来说,面向对象的视图似乎更自然。

使用触发器捕获更改

核心思想是创建一个特殊的表格(在本例中命名为“UNDOLOG”),该表格保存撤消/重做数据库更改所需的信息。对于要参与撤销/重做的数据库中的每个类(表),都会创建触发器,以便在参与类的每个DELETE,INSERT和UPDATE的UNDOLOG表中创建条目。UNDOLOG条目由普通的SQL语句组成,可以播放这些语句以反转更改。

例如,假设你想在一个类(表)上取消/重做,如下所示:

CREATE TABLE ex1(a,b,c);

用于记录表EX1更改的触发器可能如下所示:

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之后,ex1_it触发器将构造将撤销INSERT的DELETE语句的文本。ex1_ut触发器构造一个UPDATE语句,该语句将撤销UPDATE的影响。并且ex1_dt触发器构造一个将撤销DELETE效果的语句。

请注意在这些触发器中使用quote() SQL函数。quote() 函数将其参数转换为适合包含在SQL语句中的表单。数值通过不变。单引号添加在字符串之前和之后,并且任何内部单引号都会被转义。BLOB值使用SQL标准的十六进制BLOB表示法呈现。quote() 函数的使用可以确保用于撤消和重做的SQL语句对SQL注入始终是安全的。

自动创建触发器

上述触发器可以手动输入,但这很单调。下面演示的技术的一个重要特点是触发器是自动生成的。

示例代码的实现语言是TCL,但您可以在其他编程语言中轻松完成同样的任务。请记住,这里的代码是该技术的演示,而不是一个可自动为您做所有事情的插件模块。下面显示的演示代码来源于生产使用中的实际代码。但是您需要进行更改才能将其定制到您的应用程序。

要激活撤销/重做逻辑,请将所有参与撤销/重做的类(表)作为参数调用undo :: activate命令。使用undo :: deactivate,undo :: freeze和undo :: unfreeze来控制撤销/重做机制的状态。

undo :: activate命令在数据库中创建临时触发器,用于记录对参数中指定的表所做的所有更改。

应用接口

在定义单个撤销/重做步骤的一系列更改之后,调用undo :: barrier命令来定义该步骤的限制。在交互式程序中,您可以在任何更改后调用undo :: event并将自动调用undo :: barrier作为空闲回调。

当用户按下Undo按钮时,调用undo :: undo。当用户按下重做按钮时调用undo :: redo。

在每次调用undo :: undo或undo :: redo时,undo / redo模块会自动在所有顶层命名空间中调用方法status_refresh和reload_all。应该定义这些方法来重建显示或基于数据库的撤消/重做更改来更新程序的状态。

下面的演示代码包含一个status_refresh方法,根据是否有任何要撤消或重做的内容,灰显或激活撤销和重做按钮和菜单条目。您将需要重新定义此方法以控制应用程序中的撤消和重做按钮。

演示代码假定SQLite数据库被打开,用作名为“db”的数据库对象。

示例代码

# 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) 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
#
# 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 {} return
  if {$_undo(freeze)>=0} {error "recursive call to ::undo::freeze"}
  set _undo(freeze) }; hd_resolve_one {db one {SELECT coalesce(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 {} 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
#
# 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
  }
  set end }; hd_resolve_one {db one {SELECT coalesce(max(seq),0) FROM undolog}}; hd_puts {
  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) }; 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_refresh\n
  }
  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}::reload\n
  }
  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) 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) || }; 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 {DROP TABLE undolog}}
  $db eval {CREATE TEMP TABLE undolog(seq integer primary key, sql text)}
  foreach tbl $args {
    set collist }; hd_resolve_one {$db eval "pragma table_info($tbl)"}; hd_puts {
    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 }; hd_resolve_one {$db eval {SELECT name FROM sqlite_temp_master
                       WHERE type='trigger'}}; hd_puts {
  foreach trigger $tlist {
    if {!}; hd_resolve_one {regexp {_.*_(i|u|d)t$} $trigger}; hd_puts {} 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) }; hd_resolve_one {db one {SELECT coalesce(max(seq),0)+1 FROM 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) 0 end-1}; hd_puts {
  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 }; 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 {SELECT coalesce(max(seq),0)+1 FROM undolog}}; hd_puts {
  foreach sql $sqllist {
    db eval $sql
  }
  db eval COMMIT
  reload_all

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


# End of the ::undo namespace
}
 SQLite在公共领域。

其他 | Miscellaneous相关

1.35% Faster Than The Filesystem
2.8+3 Filenames
3.An Asynchronous I/O Module For SQLite
4.Appropriate Uses For SQLite
5.Architecture of SQLite
6.Atomic Commit In SQLite
7.Benefits of SQLite As A File Format
8.Change in Default Page Size in SQLite Version 3.12.0
9.Clustered Indexes and the WITHOUT ROWID Optimization
10.Compile-time Options
11.Constraint Conflict Resolution in SQLite
12.Custom Builds Of SQLite
13.Deterministic SQL Functions
14.Distinctive Features Of SQLite
15.EXPLAIN QUERY PLAN
16.Features Of SQLite
17.File Format Changes in SQLite
18.Full-Featured SQL
19.High Reliability
20.Hints for Debugging SQLite
21.How SQLite Is Tested
22.How To Compile SQLite
23.How To Download Canonical SQLite Source Code
24.Imposter Tables
25.In-Memory Databases
26.Indexes On Expressions
27.Internal Versus External BLOBs
28.Isolation In SQLite
29.Long Term Support
30.Maintaining Private Branches Of SQLite
31.Many Small Queries Are Efficient In SQLite
32.Measuring and Reducing CPU Usage in SQLite
33.Memory-Mapped I/O
34.NULL Handling in SQLite
35.Partial Indexes
36.Pointer Passing Interfaces
37.Powersafe Overwrite
38.Release History Of SQLite
39.Result and Error Codes
40.Row Values
41.Rowid Tables
42.Run-Time Loadable Extensions
43.SQL Features That SQLite Does Not Implement
44.sqldiff.exe: Database Difference Utility
45.SQLite As An Application File Format
46.SQLite Autoincrement
47.SQLite Backup API
48.SQLite Changes From Version 3.4.2 To 3.5.0
49.SQLite Changes From Version 3.5.9 To 3.6.0
50.SQLite Database Speed Comparison
51.SQLite File IO Specification
52.SQLite Frequently Asked Questions
53.SQLite In 5 Minutes Or Less
54.SQLite is a Self Contained System
55.SQLite Is Serverless
56.SQLite Is Transactional
57.SQLite Library Footprint
58.SQLite Shared-Cache Mode
59.SQLite Unlock-Notify API
60.SQLite Version 3 Overview
61.SQLite: Single File Database
62.Temporary Files Used By SQLite
63.TH3
64.The COMPLETION() Table-Valued Function
65.The CSV Virtual Table
66.The dbhash.exe Utility Program
67.The DBSTAT Virtual Table
68.The Error And Warning Log
69.The generate_series Table-Valued Function
70.The OS Backend (VFS) To SQLite
71.The Spellfix1 Virtual Table
72.The SQLite Amalgamation
73.The SQLite Bytecode Engine
74.The sqlite3_analyzer.exe Utility Program
75.The SQLITE_STMT Virtual Table
76.The UNION Virtual Table
77.The Virtual Database Engine of SQLite
78.Uniform Resource Identifiers
79.Using SQLite In Multi-Threaded Applications
80.Version Numbers in SQLite
81.What If OpenDocument Used SQLite?
82.Why Is SQLite Coded In C
83.Zero-Configuration
Sqlite

SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来

主页 https://sqlite.org/
源码 https://www.sqlite.org/src/
发布版本 3.21.0

Sqlite目录

1.C界面 | C Interface
2.C Interface: Session Module
3.CLI
4.数据库文件表 | Database File Format
5.数据类 | Datatypes
6.动态内存分配 | Dynamic Memory Allocation
7.外键约束 | Foreign Key Constraints
8.全文索引 | Full-Text Search
9.损坏方式 | How To Corrupt
10.JSON
11.语言 | Language
12.局限性 | Limits
13.锁定和并发 | Locking and Concurrency
14.其他 | Miscellaneous
15.PRAGMA Statements
16.查询计划程序 | Query Planner
17.R*Tree Module
18.RBU Extension
19.语法图 | Syntax Diagrams
20.Tcl Interface
21.虚拟表机制 | Virtual Table Mechanism
22.预写日志 | Write-Ahead Logging
23.SQL 教程
24.SQL 简介
25.SQL 语法
26.SQL DELETE 语句
27.SQL UPDATE 语句
28.SQL NOT NULL 约束
29.SQL 约束
30.SQL CREATE TABLE 语句
31.SQL CREATE DATABASE 语句
32.SQL INSERT INTO SELECT 语句
33.SQL SELECT INTO 语句
34.SQL CREATE VIEW、REPLACE VIEW、 DROP VIEW 语句
35.SQL AUTO INCREMENT 字段
36.SQL ALTER TABLE 语句
37.SQL 撤销索引、表以及数据库
38.SQL CREATE INDEX 语句
39.SQL DEFAULT 约束
40.SQL CHECK 约束
41.SQL FOREIGN KEY 约束
42.SQL PRIMARY KEY 约束
43.SQL UNIQUE 约束
44.SQL 通用数据类型
45.SQL ISNULL()、NVL()、IFNULL() 和 COALESCE() 函数
46.SQL NULL 值 – IS NULL 和 IS NOT NULL
47.SQL Server 和 MySQL 中的 Date 函数
48.SQL MS Access、MySQL 和 SQL Server 数据类型
49.SQL 函数
50.SQL 总结
51.SQL 主机
52.SQL 快速参考
53.SQL ROUND() 函数
54.SQL Server GETDATE() 函数
55.MySQL DATE_FORMAT() 函数
56.MySQL DATEDIFF() 函数
57.MySQL DATE_SUB() 函数
58.MySQL DATE_ADD() 函数
59.MySQL EXTRACT() 函数
60.MySQL DATE() 函数
61.MySQL CURTIME() 函数
62.MySQL CURDATE() 函数
63.MySQL NOW() 函数
64.SQL Server CONVERT() 函数
65.SQL Server DATEDIFF() 函数
66.SQL Server DATEADD() 函数
67.SQL Server DATEPART() 函数
68.SQLite 命令
69.SQLite 安装
70.SQLite 简介
71.SQLite 运算符
72.SQLite Select 语句
73.SQLite 删除表
74.SQLite 创建表
75.SQLite Insert 语句
76.SQLite 分离数据库
77.SQLite 附加数据库
78.SQLite 创建数据库
79.SQLite 数据类型
80.SQLite 语法
81.SQLite Order By
82.SQLite Limit 子句
83.SQLite Glob 子句
84.SQLite Like 子句
85.SQLite Delete 语句
86.SQLite Update 语句
87.SQLite AND/OR 运算符
88.SQLite Where 子句
89.SQLite 表达式
90.SQLite Distinct 关键字
91.SQLite Having 子句
92.SQLite Group By
93.SQLite Join
94.SQLite 约束
95.SQLite PRAGMA
96.SQLite 事务
97.SQLite 视图
98.SQLite Truncate Table
99.SQLite Alter 命令
100.SQLite Indexed By