小巧、快速、可靠。
三选二。
SQLite 字节码引擎

1. 执行摘要

SQLite 通过将 SQL 语句转换为字节码,然后在虚拟机中运行这些字节码来工作。本文档介绍了字节码引擎的工作原理。

本文档描述了 SQLite 内部。此处提供的信息对于使用 SQLite 进行常规应用程序开发并不需要。本文档面向希望更深入地了解 SQLite 内部运行机制的人员。

字节码引擎不是 SQLite 的 API。字节码引擎的细节在 SQLite 的不同版本之间会发生变化。使用 SQLite 的应用程序不应该依赖本文档中提到的任何细节。

有关 SQLite 为什么要使用字节码来实现 SQL 的一些原因,请参见文档 "为什么 SQLite 使用字节码"。

2. 简介

SQLite 通过将每个 SQL 语句转换为字节码,然后运行这些字节码来工作。SQLite 中的预备语句主要就是实现相应 SQL 所需的字节码。 sqlite3_prepare_v2() 接口是一个将 SQL 转换为字节码的编译器。 sqlite3_step() 接口是运行预备语句中包含的字节码的虚拟机。

字节码虚拟机是 SQLite 的核心。想要了解 SQLite 内部运行机制的程序员必须熟悉字节码引擎。

在历史上,SQLite 中的字节码引擎被称为“虚拟数据库引擎”或“VDBE”。本网站将“字节码引擎”、“VDBE”、“虚拟机”和“字节码虚拟机”这些术语互换使用,因为它们都表示同一件事。

本文还将“字节码程序”和“预备语句”这两个术语互换使用,因为它们基本上是同一件事。

2.1. VDBE 源代码

字节码引擎的源代码位于 vdbe.c 源文件。本文档中的 操作码定义 来自该源文件中的注释。源代码注释是有关字节码引擎信息的主要来源。如有疑问,请参考源代码。

除了主要的 vdbe.c 源代码文件之外,源代码树中还有其他辅助代码文件,这些文件的名字都以“vdbe”开头——代表“虚拟数据库引擎”。

请记住,操作码的名称和含义在 SQLite 的不同版本之间经常会发生变化。因此,如果您正在研究来自 SQLite 的 EXPLAIN 输出,您应该参考与运行 EXPLAIN 的 SQLite 版本相对应的本文档版本(或 vdbe.c 源代码)。否则,操作码的描述可能不准确。本文档源自 SQLite 版本 3.46.1,检入 c9c2ab54ba1f5,日期为 2024-08-13。

2.2. 指令格式

SQLite 中的字节码程序包含一个或多个指令。每个指令都有一个操作码和五个操作数,分别命名为 P1、P2 P3、P4 和 P5。P1、P2 和 P3 操作数是 32 位有符号整数。这些操作数通常引用寄存器。对于对 b-树游标进行操作的指令,P1 操作数通常是游标编号。对于跳转指令,P2 通常是跳转目标。P4 可以是 32 位有符号整数、64 位有符号整数、64 位浮点数、字符串字面量、Blob 字面量、指向排序规则比较函数的指针、指向应用程序定义的 SQL 函数实现的指针,或者其他各种东西。P5 是一个 16 位无符号整数,通常用于保存标志。P5 标志的位有时会以细微的方式影响操作码。例如,如果在 Eq 操作码上设置了 P5 操作数的 SQLITE_NULLEQ (0x0080) 位,那么 NULL 值将彼此相等。否则 NULL 值将彼此不相等。

有些操作码使用所有五个操作数。有些操作码使用一到两个。有些操作码不使用任何操作数。

字节码引擎从指令编号 0 开始执行。执行将持续到遇到 Halt 指令,或者程序计数器大于最后一个指令的地址,或者出现错误。当字节码引擎停止时,它所分配的所有内存都会被释放,它可能打开的所有数据库游标都会被关闭。如果执行由于错误而停止,则任何挂起的交易都会被终止,对数据库所做的更改会被回滚。

ResultRow 操作码会导致字节码引擎暂停,并且相应的 sqlite3_step() 调用返回 SQLITE_ROW。在调用 ResultRow 之前,字节码程序会将单个查询结果的行加载到一系列寄存器中。C 语言 API(例如 sqlite3_column_int()sqlite3_column_text())从这些寄存器中提取查询结果。字节码引擎在下次调用 sqlite3_step() 时,会从 ResultRow 之后的下一条指令继续执行。

2.3. 寄存器

每个字节码程序都有固定数量(但可能很大)的寄存器。单个寄存器可以保存多种对象。

寄存器也可以是“未定义”,表示它根本不保存任何值。未定义与 NULL 不同。根据编译时选项,尝试读取未定义的寄存器通常会导致运行时错误。如果代码生成器 (sqlite3_prepare_v2()) 曾经生成读取未定义寄存器的预备语句,那么这就是代码生成器中的错误。

寄存器从 0 开始编号。大多数操作码都引用至少一个寄存器。

单个预备语句中的寄存器数量在编译时固定。当预备语句被重置结束时,所有寄存器的内容都会被清除。

内部 Mem 对象存储单个寄存器的值。API 中公开的抽象 sqlite3_value 对象实际上只是一个 Mem 对象或寄存器。

2.4. B-树游标

预备语句可以有零个或多个打开的游标。每个游标都由一个小整数标识,该整数通常是使用该游标的操作码的 P1 参数。同一个索引或表上可以打开多个游标。所有游标都独立运行,即使指向同一个索引或表的游标也是如此。虚拟机与数据库文件交互的唯一方式是通过游标。虚拟机中的指令可以创建新的游标(例如:OpenReadOpenWrite),从游标中读取数据 (Column),将游标前进到表中的下一条条目(例如:NextPrev),等等。当预备语句被重置结束时,所有游标都会自动关闭。

2.5. 子程序、协程和子程序

字节码引擎没有用于存储子程序返回地址的堆栈。返回地址必须存储在寄存器中。因此,字节码子程序不可重入。

Gosub 操作码将当前程序计数器存储到寄存器 P1 中,然后跳转到地址 P2。 Return 操作码跳转到地址 P1+1。因此,每个子程序都与两个整数相关联:子程序入口点的地址和用于保存返回地址的寄存器编号。

Yield 操作码交换程序计数器的值与寄存器 P1 中的整数值。此操作码用于实现协程。协程通常用于实现按需从中提取内容的子查询。

触发器 需要可重入。由于字节码子程序不可重入,因此必须使用其他机制来实现触发器。每个触发器都使用一个单独的字节码程序来实现,该程序有自己的操作码、程序计数器和寄存器集。 Program 操作码调用触发器子程序。 Program 指令为子程序的每次调用分配并初始化一个新的寄存器集,因此子程序可以是可重入的和递归的。 Param 操作码由子程序用于访问调用字节码程序的寄存器中的内容。

2.6. 自修改代码

有些操作码是自修改的。例如, Init 操作码(始终是每个字节码程序中的第一个操作码)会增加其 P1 操作数。随后的 Once 操作码将其 P1 操作数与 Init 操作码的 P1 值进行比较,以确定是否应该跳过后面的单次初始化代码。另一个例子是 String8 操作码,它将其 P4 操作数从 UTF-8 转换为正确的数据库字符串编码,然后将其自身转换为 String 操作码。

3. 查看字节码

SQLite 解释的每个 SQL 语句都会生成一个虚拟机程序。但如果 SQL 语句以关键字 EXPLAIN 开头,则虚拟机不会执行该程序。相反,程序的指令将被返回,每行一条指令,就像查询结果一样。此功能对于调试和了解虚拟机的工作原理非常有用。例如

sqlite3 ex1.db
sqlite> explain delete from tbl1 where two<20;
addr  opcode         p1    p2    p3    p4             p5  comment      
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     12    0                    00  Start at 12  
1     Null           0     1     0                    00  r[1]=NULL    
2     OpenWrite      0     2     0     3              00  root=2 iDb=0; tbl1
3     Rewind         0     10    0                    00               
4       Column         0     1     2                    00  r[2]=tbl1.two
5       Ge             3     9     2     (BINARY)       51  if r[2]>=r[3] goto 9
6       Rowid          0     4     0                    00  r[4]=rowid   
7       Once           0     8     0                    00               
8       Delete         0     1     0     tbl1           02               
9     Next           0     4     0                    01               
10    Noop           0     0     0                    00               
11    Halt           0     0     0                    00               
12    Transaction    0     1     1     0              01  usesStmtJournal=0
13    TableLock      0     2     1     tbl1           00  iDb=0 root=2 write=1
14    Integer        20    3     0                    00  r[3]=20      
15    Goto           0     1     0                    00

任何应用程序都可以运行 EXPLAIN 查询以获取类似于上面的输出。但是,SQLite 核心不会生成显示循环结构的缩进。 命令行 shell 包含用于缩进循环的额外逻辑。此外,只有在 SQLite 使用 -DSQLITE_ENABLE_EXPLAIN_COMMENTS 选项进行编译时,EXPLAIN 输出中的“注释”列才会被提供。

当 SQLite 使用 SQLITE_DEBUG 编译时选项进行编译时,将提供额外的 PRAGMA 命令,这些命令对于调试和探索 VDBE 的操作非常有用。例如,可以启用 vdbe_trace pragma,以便在执行操作码时将每个 VDBE 操作码的拆卸打印到标准输出。这些调试 pragma 包括

4. 操作码

虚拟机目前定义了 189 个操作码。下表描述了所有当前定义的操作码。此表是通过扫描来自文件 vdbe.c 的源代码自动生成的。

请记住:VDBE 操作码不是 SQLite 接口定义的一部分。操作码的数量及其名称和含义在 SQLite 的不同版本之间会有所不同。下表中显示的操作码适用于 SQLite 版本 3.46.1,签入时间为 c9c2ab54ba1f5,日期为 2024-08-13。

操作码名称描述
Abortable验证是否可以发生 Abort。如果此时 Abort 可能会导致数据库损坏,则断言。此操作码仅出现在调试版本中。

如果没有任何写入操作,或者存在活动的语句日志,则 Abort 是安全的。

Add将寄存器 P1 中的值添加到寄存器 P2 中的值,并将结果存储在寄存器 P3 中。如果任一输入为 NULL,则结果为 NULL。
AddImm将常量 P2 添加到寄存器 P1 中的值。结果始终是整数。

要强制任何寄存器为整数,只需添加 0 即可。

Affinity将亲和性应用于从 P1 开始的 P2 个寄存器范围。

P4 是一个长度为 P2 个字符的字符串。字符串的第 N 个字符表示应用于此范围内的第 N 个内存单元的列亲和性。

AggFinalP1 是作为聚合或窗口函数累加器的内存位置。执行聚合的最终化函数并将结果存储在 P1 中。

P2 是步进函数接受的参数数量,P4 是指向此函数的 FuncDef 的指针。P2 参数不会被此操作码使用。它仅用于区分可以接受不同数量参数的函数。P4 参数仅在步进函数以前没有被调用时才需要。

AggInverse执行聚合的 xInverse 函数。该函数具有 P5 个参数。P4 是指向指定函数的 FuncDef 结构的指针。寄存器 P3 是累加器。

P5 参数取自寄存器 P2 及其后续寄存器。

AggStep执行聚合的 xStep 函数。该函数具有 P5 个参数。P4 是指向指定函数的 FuncDef 结构的指针。寄存器 P3 是累加器。

P5 参数取自寄存器 P2 及其后续寄存器。

AggStep1执行聚合的 xStep(如果 P1==0)或 xInverse(如果 P1!=0)函数。该函数具有 P5 个参数。P4 是指向指定函数的 FuncDef 结构的指针。寄存器 P3 是累加器。

P5 参数取自寄存器 P2 及其后续寄存器。

此操作码最初被编码为 OP_AggStep0。在第一次计算时,存储在 P4 中的 FuncDef 被转换为 sqlite3_context,并且操作码被更改。通过这种方式,sqlite3_context 的初始化仅发生一次,而不是在每次调用步进函数时都发生。

AggValue调用 xValue() 函数并将结果存储在寄存器 P3 中。

P2 是步进函数接受的参数数量,P4 是指向此函数的 FuncDef 的指针。P2 参数不会被此操作码使用。它仅用于区分可以接受不同数量参数的函数。P4 参数仅在步进函数以前没有被调用时才需要。

And获取寄存器 P1 和 P2 中值的逻辑 AND,并将结果写入寄存器 P3 中。

如果 P1 或 P2 为 0(假),则结果为 0,即使另一个输入为 NULL。NULL 和 true 或者两个 NULL 会产生 NULL 输出。

AutoCommit将数据库自动提交标志设置为 P1(1 或 0)。如果 P2 为 true,则回滚任何当前活动的 btree 事务。如果有任何活动的 VM(除了这个 VM),则 ROLLBACK 会失败。如果存在活动的写入 VM 或使用共享缓存的活动 VM,则 COMMIT 会失败。

此指令会导致 VM 停止。

BeginSubrtn标记子例程的开始,该子例程可以内联进入,也可以使用 Gosub 调用。子例程应以 Return 指令终止,该指令的 P1 操作数与该操作码的 P2 操作数相同,并且 P3 设置为 1。如果子例程是内联进入的,则 Return 将直接执行。但是,如果子例程是使用 Gosub 进入的,则 Return 将跳转回 Gosub 后的第一条指令。

此例程通过在 P2 寄存器中加载 NULL 来工作。当返回地址寄存器包含 NULL 时,Return 指令是一个无操作,它只会直接执行到下一条指令(假设 Return 操作码的 P3 值为 1)。因此,如果子例程是内联进入的,则 Return 将导致内联执行继续。但是,如果子例程是通过 Gosub 进入的,则 Return 将导致返回到 Gosub 后的地址。

此操作码与 Null 相同。它只是为了使字节码更易于阅读和验证而有不同的名称。

BitAnd获取寄存器 P1 和 P2 中值的按位 AND,并将结果存储在寄存器 P3 中。如果任一输入为 NULL,则结果为 NULL。
BitNot将寄存器 P1 的内容解释为整数。将 P1 值的 ones-complement 存储到寄存器 P2 中。如果 P1 包含 NULL,则将 NULL 存储到 P2 中。
BitOr获取寄存器 P1 和 P2 中值的按位 OR,并将结果存储在寄存器 P3 中。如果任一输入为 NULL,则结果为 NULL。
BlobP4 指向一个长度为 P1 字节的数据 blob。将此 blob 存储在寄存器 P2 中。如果 P4 是一个 NULL 指针,则在 P2 中构造一个长度为 P1 字节的零填充 blob。
Cast强制寄存器 P1 中的值为 P2 定义的类型。

  • P2=='A' → BLOB
  • P2=='B' → TEXT
  • P2=='C' → NUMERIC
  • P2=='D' → INTEGER
  • P2=='E' → REAL

NULL 值不会被此例程更改。它仍然是 NULL。

Checkpoint检查点数据库 P1。如果 P1 当前不在 WAL 模式,则这是一个无操作。参数 P2 是以下之一:SQLITE_CHECKPOINT_PASSIVE、FULL、RESTART 或 TRUNCATE。如果检查点返回 SQLITE_BUSY,则将 1 或 0 写入 mem[P3],否则写入 0。将检查点完成后的 WAL 中的页面数写入 mem[P3+1],并将检查点完成后的 WAL 中已检查点的页面数写入 mem[P3+2]。但是,在发生错误时,mem[P3+1] 和 mem[P3+2] 将初始化为 -1。
Clear删除数据库文件中根页面由 P1 给出的数据库表或索引的所有内容。但是,与 Destroy 不同,不要从数据库文件中删除表或索引。

如果 P2==0,则要清除的表位于主数据库文件中。如果 P2==1,则要清除的表位于辅助数据库文件中,该文件用于存储使用 CREATE TEMPORARY TABLE 创建的表。

如果 P3 值不为零,则行更改计数将增加要清除的表中的行数。如果 P3 大于零,则存储在寄存器 P3 中的值也将增加要清除的表中的行数。

另请参阅:Destroy

Close关闭先前作为 P1 打开的游标。如果 P1 当前未打开,则此指令是一个无操作。
ClrSubtype清除寄存器 P1 中的子类型。
CollSeqP4 是指向 CollSeq 对象的指针。如果下一个对用户函数或聚合的调用调用 sqlite3GetFuncCollSeq(),则将返回此排序规则。这被内置的 min()、max() 和 nullif() 函数使用。

如果 P1 不为零,则它是一个寄存器,后续的 min() 或 max() 聚合将在此寄存器中设置为 1,如果当前行不是最小值或最大值。P1 寄存器由此指令初始化为 0。

上述函数实现使用的接口来检索由此操作码设置的排序规则在公共场合不可用。只有内置函数可以访问此功能。

Column将游标 P1 指向的数据解释为使用 MakeRecord 指令构建的结构。(有关数据格式的更多信息,请参阅 MakeRecord 操作码。)从该记录中提取第 P2 列。如果记录中少于 (P2+1) 个值,则提取 NULL。

提取的值存储在寄存器 P3 中。

如果记录中的字段少于 P2 个,则提取 NULL。或者,如果 P4 参数是 P4_MEM,则使用 P4 参数的值作为结果。

如果 P5 中设置了 OPFLAG_LENGTHARG 位,则保证结果仅被 length() 函数或等效函数使用。不会加载大型 blob 的内容,从而节省了 CPU 周期。如果设置了 OPFLAG_TYPEOFARG 位,则结果将仅被 typeof() 函数或 IS NULL 或 IS NOT NULL 运算符或等效运算符使用。在这种情况下,可以省略所有内容加载。

ColumnsUsed此操作码(仅在 SQLite 编译时使用 SQLITE_ENABLE_COLUMN_USED_MASK 存在)标识游标 P1 的表或索引的哪些列被使用。P4 是一个 64 位整数(P4_INT64),其中前 63 位代表表或索引的前 63 列中实际被游标使用的每列。如果使用 64 列之后的任何列,则高位被设置为 1。
Compare比较 reg(P1)..reg(P1+P3-1) 中的两个寄存器向量(称为向量“A”)和 reg(P2)..reg(P2+P3-1) 中的向量(“B”)。保存比较结果以供下一个 Jump 指令使用。

如果 P5 设置了 OPFLAG_PERMUTE 位,则比较顺序由最近的 Permutation 运算符决定。如果 OPFLAG_PERMUTE 位未设置,则寄存器按顺序比较。

P4 是一个 KeyInfo 结构,用于定义比较的排序规则和排序顺序。置换仅适用于寄存器。KeyInfo 元素按顺序使用。

比较是排序比较,因此 NULL 相等,NULL 小于数字,数字小于字符串,字符串小于 blob。

此操作码必须紧跟在 Jump 操作码之后。

Concat将寄存器 P1 中的文本添加到寄存器 P2 中的文本的末尾,并将结果存储在寄存器 P3 中。如果 P1 或 P2 文本为 NULL,则将 NULL 存储到 P3 中。

P3 = P2 || P1

P1 和 P3 不得是同一个寄存器。有时,如果 P3 与 P2 是同一个寄存器,则实现能够避免 memcpy()。

Copy将寄存器 P1..P1+P3 复制到寄存器 P2..P2+P3 中。

如果 P5 的第 0x0002 位被设置,则还会清除目标中的 MEM_Subtype 标志。P5 的第 0x0001 位表示此 Copy 操作码无法合并。0x0001 位由查询计划器使用,在查询执行期间不会发挥作用。

此指令对值进行深度复制。对任何字符串或 blob 常量进行复制。另请参见 SCopy

Count将光标 P1 打开的表或索引中的条目数(一个整数值)存储在寄存器 P2 中。

如果 P3==0,则获得精确计数,这涉及访问表的每个 btree 页面。但是,如果 P3 非零,则根据当前光标位置返回估计值。

CreateBtree如果 P1==0,则在主数据库文件中分配一个新的 b 树,如果 P1==1,则在 TEMP 数据库文件中分配一个新的 b 树,如果 P1>1,则在附加的数据库文件中分配一个新的 b 树。对于行 ID 表,P3 参数必须为 1(BTREE_INTKEY),对于索引或 WITHOUT ROWID 表,它必须为 2(BTREE_BLOBKEY)。新 b 树的根页号存储在寄存器 P2 中。
CursorHint向光标 P1 提供一个提示,表明它只需要返回满足 P4 中 Expr 的行。P4 表达式中的 TK_REGISTER 项引用当前保存在寄存器中的值。P4 表达式中的 TK_COLUMN 项引用光标 P1 指向的 b 树中的列。
CursorLock锁定光标 P1 指向的 b 树,以便其他光标无法写入 b 树。
CursorUnlock解锁光标 P1 指向的 b 树,以便其他光标可以写入它。
DecrJumpZero寄存器 P1 必须保存一个整数。减少 P1 中的值,如果新值为零,则跳转到 P2。
DeferredSeekP1 是一个打开的索引光标,P3 是对应表的指针。此操作码对 P3 表光标进行延迟查找,以找到与 P1 当前行相对应的行。

这是一个延迟查找。直到光标用于读取记录才会真正发生任何操作。这样,如果未发生读取操作,则不会发生不必要的 I/O 操作。

P4 可以是一个整数数组(类型 P4_INTARRAY),每个条目对应 P3 表中的一列。如果数组条目 a(i) 非零,则从光标 P3 读取列 a(i)-1 等效于执行延迟查找,然后从 P1 读取列 i。此信息存储在 P3 中,并用于将针对 P3 的读取重定向到 P1,从而可能避免需要查找和读取光标 P3。

Delete删除 P1 光标当前指向的记录。

如果 P5 参数的 OPFLAG_SAVEPOSITION 位被设置,则光标将指向表中的下一条记录或前一条记录。如果它指向下一条记录,则下一个 Next 指令将成为一个无操作。因此,在这种情况下,可以从 Next 循环中删除记录。如果 P5 的 OPFLAG_SAVEPOSITION 位未设置,则光标将处于未定义状态。

如果 P5 上设置了 OPFLAG_AUXDELETE 位,则表明此删除是与删除表行及其所有关联索引条目相关的几个删除之一。这些删除中只有一个是“主要”删除。其他所有删除都位于 OPFLAG_FORDELETE 光标上,或者用 AUXDELETE 标志标记。

如果 P2(注意:是 P2 而不是 P5)的 OPFLAG_NCHANGE(0x01)标志被设置,则行变更计数将递增(否则不会)。

如果 P2(不是 P5!)的 OPFLAG_ISNOOP(0x40)标志被设置,则将运行删除的预更新钩子,但 b 树保持不变。这种情况发生在 Delete 之后紧跟着一个 Insert,并且具有相同的键,导致 b 树条目被覆盖。

P1 不能是伪表。它必须是具有多行的真实表。

如果 P4 不为 NULL,则它指向一个 Table 对象。在这种情况下,更新或预更新钩子,或两者都可能被调用。P1 光标必须使用 NotFound 定位,然后才能在这种情况下调用此操作码。具体来说,如果配置了一个预更新钩子,则在 P4 不为 NULL 时调用它。如果配置了一个更新钩子,并且 P4 不为 NULL 且 P2 中设置了 OPFLAG_NCHANGE 标志,则调用它。

如果 P2 中设置了 OPFLAG_ISUPDATE 标志,则 P3 包含内存单元的地址,该内存单元包含更新将设置的行 ID 的值。

Destroy删除整个数据库表或索引,其数据库文件中的根页由 P1 给出。

要销毁的表位于主数据库文件中,如果 P3==0。如果 P3==1,则要销毁的表位于用于存储使用 CREATE TEMPORARY TABLE 创建的表的辅助数据库文件中。

如果启用了 AUTOVACUUM,则可能将另一个根页移动到新删除的根页中,以使所有根页在数据库开头保持连续。移动的根页的先前值(移动发生之前的其值)存储在寄存器 P2 中。如果没有必要移动页面(因为要删除的表已经是数据库中的最后一个表),则在寄存器 P2 中存储零。如果禁用了 AUTOVACUUM,则在寄存器 P2 中存储零。

如果在调用此操作码时有任何活动的读取器 VM,则此操作码将抛出错误。这样做是为了避免在 AUTOVACUUM 数据库中移动根页时更新现有光标的难度。即使数据库不是 AUTOVACUUM 数据库,也会抛出此错误,以避免在自动真空模式和非自动真空模式之间引入不兼容性。

另请参见:Clear

Divide将寄存器 P1 中的值除以寄存器 P2 中的值,并将结果存储在寄存器 P3 中(P3=P2/P1)。如果寄存器 P1 中的值为零,则结果为 NULL。如果任一输入为 NULL,则结果为 NULL。
DropIndex删除描述数据库 P1 中名为 P4 的索引的内部(内存中)数据结构。在从磁盘删除索引(使用 Destroy 操作码)之后调用此操作码,以使模式的内部表示与磁盘上的模式一致。
DropTable删除描述数据库 P1 中名为 P4 的表的内部(内存中)数据结构。在从磁盘删除表(使用 Destroy 操作码)之后调用此操作码,以使模式的内部表示与磁盘上的模式一致。
DropTrigger删除描述数据库 P1 中名为 P4 的触发的内部(内存中)数据结构。在从磁盘删除触发器(使用 Destroy 操作码)之后调用此操作码,以使模式的内部表示与磁盘上的模式一致。
ElseEq此操作码必须位于 LtGt 比较运算符之后。之间可以有零个或多个 OP_ReleaseReg 操作码,但不能允许其他操作码发生在该指令与先前的 LtGt 之间。

如果对与先前的 LtGt 相同的两个操作数进行 Eq 比较的结果为真,则跳转到 P2。如果对两个先前的操作数进行 Eq 比较的结果为假或 NULL,则继续执行。

EndCoroutine寄存器 P1 中的地址处的指令是 YieldJump 到该 Yield 的 P2 参数。跳转后,值寄存器 P1 保持一个值,使得后续的 OP_Yields 返回到同一个 EndCoroutine 指令。

另请参见:InitCoroutine

Eq比较寄存器 P1 和 P3 中的值。如果 reg(P3)==reg(P1),则跳转到地址 P2。

P5 的 SQLITE_AFF_MASK 部分必须是一个关联字符 - SQLITE_AFF_TEXT、SQLITE_AFF_INTEGER 等等。在进行比较之前,将尝试根据此关联对两个输入进行强制转换。如果 SQLITE_AFF_MASK 为 0x00,则使用数字关联。请注意,关联转换将存储回输入寄存器 P1 和 P3。因此,此操作码可能会导致对寄存器 P1 和 P3 的持久性更改。

一旦任何转换完成,并且两个值都不为 NULL,就会比较这些值。如果两个值都是 blob,则使用 memcmp() 来确定比较结果。如果两个值都是文本,则使用 P4 中指定的适当排序规则函数来进行比较。如果未指定 P4,则使用 memcmp() 来比较文本字符串。如果两个值都是数字,则使用数字比较。如果两个值类型不同,则数字被认为小于字符串,字符串被认为小于 blob。

如果在 P5 中设置了 SQLITE_NULLEQ,则比较结果始终为真或假,绝不会为 NULL。如果两个操作数都为 NULL,则比较结果为真。如果任一操作数为 NULL,则结果为假。如果两个操作数都不为 NULL,则结果与在 P5 中省略 SQLITE_NULLEQ 标志时相同。

此操作码保存比较结果,供新的 Jump 操作码使用。

Expire使预编译的语句过期。当使用 sqlite3_step() 执行过期的语句时,它将自动重新准备它本身(如果它最初是使用 sqlite3_prepare_v2() 创建的),或者它将失败并返回 SQLITE_SCHEMA。

如果 P1 为 0,则所有 SQL 语句都将过期。如果 P1 非零,则只有当前正在执行的语句会过期。

如果 P2 为 0,则 SQL 语句立即过期。如果 P2 为 1,则允许正在运行的 SQL 语句继续运行到完成。当发生 CREATE INDEX 或类似的模式更改时,P2==1 情况会发生,这可能有助于语句更快地运行,但不会影响操作的正确性。

Filter在从 r[P3] 开始的 P4 寄存器中包含的键上计算哈希值。检查该哈希值是否在由寄存器 P1 托管的布隆过滤器中找到。如果它不存在,则可以跳转到 P2。否则继续执行。

假阴性是无害的。即使该值位于布隆过滤器中,继续执行也是安全的。假阴性会导致使用更多 CPU 周期,但它仍然应该产生正确的结果。但是,假阳性可能会导致错误的结果 - 如果在应该继续执行时进行了跳转。

FilterAdd在从 r[P3] 开始的 P4 寄存器上计算哈希值,并将该哈希值添加到包含在 r[P1] 中的布隆过滤器中。
FinishSeek如果光标 P1 之前是通过 DeferredSeek 移动的,则立即完成该查找操作,无需进一步延迟。如果光标查找已经发生,则此指令是一个无操作。
FkCheck如果存在任何未解决的外键约束冲突,则停止并返回 SQLITE_CONSTRAINT 错误。如果不存在外键约束冲突,则此操作码为无操作。

在预备语句退出时,也会检查 FK 约束违规情况。此操作码用于在返回结果(例如行更改计数或 RETURNING 子句的结果)之前引发外键约束错误。

FkCounter将“约束计数器”递增 P2(P2 可以是负数或正数)。如果 P1 非零,则递增数据库约束计数器(延迟外键约束)。否则,如果 P1 为零,则递增语句计数器(立即外键约束)。
FkIfZero此操作码测试外键约束计数器当前是否为零。如果是,则跳转到指令 P2。否则,继续执行下一条指令。

如果 P1 非零,则如果数据库约束计数器为零(用于计数延迟约束违规的计数器),则执行跳转。如果 P1 为零,则如果语句约束计数器为零(立即外键约束违规),则执行跳转。

Found如果 P4==0,则寄存器 P3 持有一个由 MakeRecord 构造的 blob。如果 P4>0,则寄存器 P3 是形成未打包记录的 P4 个寄存器的第一个。

游标 P1 位于索引 B 树上。如果由 P3 和 P4 标识的记录是 P1 中任何条目的前缀,则跳转到 P2,并将 P1 留在指向匹配条目的位置。

此操作将游标置于可以向前方向前进的状态。Next 指令将起作用,但 Prev 指令不会起作用。

另请参见:NotFoundNoConflictNotExists。SeekGe

Function调用用户函数(P4 是指向 sqlite3_context 对象的指针,该对象包含指向要运行的函数的指针),并从寄存器 P2 及其后续寄存器中获取参数。参数数量在 P4 指向的 sqlite3_context 对象中。函数的结果存储在寄存器 P3 中。寄存器 P3 不得是函数输入之一。

P1 是一个 32 位位掩码,指示函数的每个参数在编译时是否被确定为常量。如果第一个参数是常量,则设置 P1 的位 0。这用于确定是否可以使用 sqlite3_set_auxdata() API 安全地保留与用户函数参数关联的元数据,直到下次调用此操作码为止。

另请参见:AggStepAggFinalPureFunc

Ge此操作的工作方式与 Lt 操作码完全相同,只是如果寄存器 P3 的内容大于或等于寄存器 P1 的内容,则执行跳转。有关更多信息,请参见 Lt 操作码。
GetSubtype从寄存器 P1 中提取子类型值,并将该子类型写入寄存器 P2。如果 P1 没有子类型,则 P1 将变为 NULL。
Gosub将当前地址写入寄存器 P1,然后跳转到地址 P2。
Goto无条件跳转到地址 P2。执行的下一条指令将是程序开头索引为 P2 的指令。

P1 参数实际上不被此操作码使用。但是,有时将其设置为 1 而不是 0,作为对命令行 shell 的提示,说明此 Goto 是循环的底部,并且从 P2 到当前行的行应为 EXPLAIN 输出缩进。

Gt此操作的工作方式与 Lt 操作码完全相同,只是如果寄存器 P3 的内容大于寄存器 P1 的内容,则执行跳转。有关更多信息,请参见 Lt 操作码。
Halt立即退出。所有打开的游标等都会自动关闭。

P1 是由 sqlite3_exec()、sqlite3_reset() 或 sqlite3_finalize() 返回的结果代码。对于正常停止,这应该是 SQLITE_OK(0)。对于错误,它可以是其他一些值。如果 P1!=0,则 P2 将确定是否回滚当前事务。如果 P2==OE_Fail,则不回滚。如果 P2==OE_Rollback,则执行回滚。如果 P2==OE_Abort,则撤消在此次 VDBE 执行期间发生的更改,但不回滚事务。

如果 P4 非空,则它是一个错误消息字符串。

P5 是一个介于 0 和 4 之间(含)的值,它修改 P4 字符串。

0:(无更改) 1:NOT NULL 约束失败:P4 2:UNIQUE 约束失败:P4 3:CHECK 约束失败:P4 4:FOREIGN KEY 约束失败:P4

如果 P5 非零且 P4 为 NULL,则省略冒号后的所有内容。

在每个程序的末尾隐式插入一个“Halt 0 0 0”指令。因此,跳过程序的最后一条指令与执行 Halt 相同。

HaltIfNull检查寄存器 P3 中的值。如果它是 NULL,则使用参数 P1、P2 和 P4 执行 Halt,就像它是 Halt 指令一样。如果寄存器 P3 中的值不是 NULL,则此例程是无操作的。P5 参数应为 1。
IdxDelete从寄存器 P2 开始的 P3 个寄存器的内容构成一个未打包的索引键。此操作码从游标 P1 打开的索引中删除该条目。

如果 P5 非零,则在未找到匹配的索引条目时引发 SQLITE_CORRUPT_INDEX 错误。当执行 UPDATE 或 DELETE 语句且未找到要更新或删除的索引条目时,就会发生这种情况。对于 IdxDelete 的某些用途(例如 EXCEPT 运算符),是否找到匹配的条目无关紧要。对于这些情况,P5 为零。此外,如果处于 writable_schema 模式,则不要引发此(自我纠正且非关键)错误。

IdxGE从 P3 开始的 P4 个寄存器值构成一个未打包的索引键,它省略了主键。将此键值与 P1 当前指向的索引进行 Compare,忽略末尾的主键或 ROWID 字段。

如果 P1 索引条目大于或等于键值,则跳转到 P2。否则,继续执行下一条指令。

IdxGT从 P3 开始的 P4 个寄存器值构成一个未打包的索引键,它省略了主键。将此键值与 P1 当前指向的索引进行 Compare,忽略末尾的主键或 ROWID 字段。

如果 P1 索引条目大于键值,则跳转到 P2。否则,继续执行下一条指令。

IdxInsert寄存器 P2 持有一个使用 MakeRecord 指令制作的 SQL 索引键。此操作码将该键写入索引 P1。条目的数据为 nil。

如果 P4 非零,则它是 reg(P2) 中未打包键的值数量。在这种情况下,P3 是未打包键的第一个寄存器的索引。未打包键的可用性有时可以是一种优化。

如果 P5 设置了 OPFLAG_APPEND 位,则它是对 B 树层的提示,说明此插入可能是追加操作。

如果 P5 设置了 OPFLAG_NCHANGE 位,则此指令将递增更改计数器。如果 OPFLAG_NCHANGE 位未设置,则更改计数器保持不变。

如果设置了 P5 的 OPFLAG_USESEEKRESULT 标志,则实现可能会通过避免对游标 P1 进行不必要的查找而运行得更快。但是,只有在游标上没有先前的查找,或者最近一次查找使用与 P2 等效的键时,才能设置 OPFLAG_USESEEKRESULT 标志。

此指令仅适用于索引。表等效的指令是 Insert

IdxLE从 P3 开始的 P4 个寄存器值构成一个未打包的索引键,它省略了主键或 ROWID。将此键值与 P1 当前指向的索引进行 Compare,忽略 P1 索引上的主键或 ROWID。

如果 P1 索引条目小于或等于键值,则跳转到 P2。否则,继续执行下一条指令。

IdxLT从 P3 开始的 P4 个寄存器值构成一个未打包的索引键,它省略了主键或 ROWID。将此键值与 P1 当前指向的索引进行 Compare,忽略 P1 索引上的主键或 ROWID。

如果 P1 索引条目小于键值,则跳转到 P2。否则,继续执行下一条指令。

IdxRowid将指向游标 P1 的索引键末尾的记录中的最后一个条目写入寄存器 P2。此整数应该是此索引条目指向的表条目的 rowid。

另请参见:RowidMakeRecord

If如果寄存器 P1 中的值为真,则跳转到 P2。如果该值为数字且非零,则该值被视为真。如果 P1 中的值为 NULL,则仅当 P3 非零时才执行跳转。
IfNoHope寄存器 P3 是形成未打包记录的 P4 个寄存器的第一个。游标 P1 是索引 B 树。P2 是跳转目标。换句话说,此操作码的操作数与 NotFoundIdxGT 的操作数相同。

此操作码仅是一种优化尝试。如果此操作码始终继续执行,则仍可获得正确的结果,但会执行更多工作。

游标 P1 的 seekHit 标志中的 N 值表示存在一个键 P3:N 将与索引中的某个记录匹配。我们要知道记录 P3:P4 是否可能与索引中的某个记录匹配。如果不可能,我们可以跳过一些工作。因此,如果 seekHit 小于 P4,则尝试通过运行 NotFound 来找出是否可能匹配。

此操作码用于处理多列键的 IN 子句。如果 IN 子句附加到除最左侧元素之外的键的任何元素,并且如果在整个键上的最近一次查找上没有匹配项,则可能是键左侧的某个元素阻止了匹配,因此无论检查多少个 IN 子句元素,都不存在“希望”匹配。在这种情况下,我们会使用此操作码尽早放弃 IN 子句搜索。操作码名称源于这样一个事实,即如果“没有希望”实现匹配,则会执行跳转。

另请参见:NotFoundSeekHit

IfNot如果寄存器 P1 中的值为假,则跳转到 P2。如果该值具有数字值零,则该值被视为假。如果 P1 中的值为 NULL,则仅当 P3 非零时才执行跳转。
IfNotOpen如果游标 P1 未打开,或者如果 P1 使用 NullRow 操作码设置为 NULL 行,则跳转到指令 P2。否则,继续执行。
IfNotZero寄存器 P1 必须包含一个整数。如果寄存器 P1 的内容最初大于零,则将寄存器 P1 中的值递减。如果它非零(负数或正数),则还会跳转到 P2。如果寄存器 P1 最初为零,则保持不变,并继续执行。
IfNullRow检查游标 P1,查看它当前是否指向 NULL 行。如果是,则将寄存器 P3 设置为 NULL,并立即跳转到 P2。如果 P1 不在 NULL 行上,则继续执行,不进行任何更改。

如果 P1 不是打开的游标,则此操作码是无操作的。

IfPos寄存器 P1 必须包含一个整数。如果寄存器 P1 的值为 1 或更大,则从 P1 中的值中减去 P3,并跳转到 P2。

如果寄存器 P1 的初始值小于 1,则该值保持不变,并控制权转交给下一条指令。

IfSizeBetween令 N 为游标 P1 所在表或索引中的行数,令 X 为 10*log2(N)(如果 N 为正数)或 -1(如果 N 为零)。

如果 X 介于 P3 和 P4 之间(含),则跳转到 P2。

IncrVacuum在 P1 数据库上执行增量 vacuum 过程的单个步骤。如果 vacuum 已完成,则跳转到指令 P2。否则,继续执行下一条指令。
Init程序包含此操作码的单个实例作为第一个操作码。

如果启用了跟踪(通过 sqlite3_trace() 接口),则 P4 中包含的 UTF-8 字符串将在跟踪回调中发出。或者如果 P4 为空,则使用 sqlite3_sql() 返回的字符串。

如果 P2 不为零,则跳转到指令 P2。

增加 P1 的值,以便 Once 操作码在它们在本次运行中首次评估时跳转。

如果 P3 不为零,则它是在遇到 SQLITE_CORRUPT 错误时跳转到的地址。

InitCoroutine设置寄存器 P1,使其 Yield 到地址 P3 处的协程。

如果 P2!=0,则协程实现紧随此操作码之后。因此,跳过协程实现到地址 P2。

另请参阅:EndCoroutine

Insert在游标 P1 的表中写入一个条目。如果该条目不存在,则创建一个新条目,或者覆盖现有条目的数据。数据是存储在寄存器编号 P2 中的 MEM_Blob 值。键存储在寄存器 P3 中。键必须是 MEM_Int。

如果 P5 的 OPFLAG_NCHANGE 标志被设置,则行更改计数将递增(否则不会递增)。如果 P5 的 OPFLAG_LASTROWID 标志被设置,则行 ID 将被存储,以便随后由 sqlite3_last_insert_rowid() 函数返回(否则它将保持不变)。

如果 P5 的 OPFLAG_USESEEKRESULT 标志被设置,则实现可以通过避免对游标 P1 进行不必要的查找来更快地运行。但是,只有在游标之前没有进行过任何查找,或者最近一次查找使用的键等于 P3 时,才能设置 OPFLAG_USESEEKRESULT 标志。

如果 OPFLAG_ISUPDATE 标志被设置,则此操作码是更新操作的一部分。否则(如果该标志未被设置),则此操作码是插入操作的一部分。只有更新挂钩才关心这种差异。

参数 P4 可能指向一个 Table 结构,也可能为空。如果它不为空,则在成功插入后将调用更新挂钩(sqlite3.xUpdateCallback)。

(警告/待办事项:如果 P1 是一个伪游标,并且 P2 是动态分配的,则 P2 的所有权将被转移到伪游标,并且寄存器 P2 将变成临时的。如果游标被更改,则寄存器 P2 的值也将更改。确保这不会造成任何问题。)

此指令仅适用于表。索引的等效指令是 IdxInsert

Int64P4 是指向 64 位整数值的指针。将该值写入寄存器 P2。
IntCopy将寄存器 P1 中持有的整数值转移到寄存器 P2 中。

这是 SCopy 的优化版本,仅适用于整数值。

Integer将 32 位整数值 P1 写入寄存器 P2。
IntegrityCk对当前打开的数据库进行分析。将描述任何问题的错误消息的文本存储在寄存器 (P1+1) 中。如果未发现问题,则在寄存器 (P1+1) 中存储 NULL。

寄存器 (P1) 包含允许的最大错误数量减 1。最多会报告 reg(P1) 个错误。换句话说,一旦看到 reg(P1) 个错误,分析就会停止。Reg(P1) 将使用剩余的错误数量进行更新。

数据库中所有表的根页面编号是存储在 P4_INTARRAY 参数中的整数。

如果 P5 不为零,则检查将在辅助数据库文件上进行,而不是在主数据库文件上进行。

此操作码用于实现 integrity_check pragma。

IsNull如果寄存器 P1 中的值为 NULL,则跳转到 P2。
IsTrue此操作码实现 IS TRUE、IS FALSE、IS NOT TRUE 和 IS NOT FALSE 运算符。

将寄存器 P1 中的值解释为布尔值。将该布尔值(0 或 1)存储在寄存器 P2 中。或者,如果寄存器 P1 中的值为 NULL,则将 P3 存储在寄存器 P2 中。如果 P4 为 1,则反转答案。

逻辑总结如下

  • 如果 P3==0 且 P4==0,则 r[P2] := r[P1] IS TRUE
  • 如果 P3==1 且 P4==1,则 r[P2] := r[P1] IS FALSE
  • 如果 P3==0 且 P4==1,则 r[P2] := r[P1] IS NOT TRUE
  • 如果 P3==1 且 P4==0,则 r[P2] := r[P1] IS NOT FALSE
IsType如果 B 树中列的类型是 P5 位掩码指定的类型之一,则跳转到 P2。

P1 通常是 B 树上的游标,其行解码缓存对于至少列 P3 都是有效的。换句话说,应该有一个先前的 Column 用于列 P3 或更大。如果游标无效,则此操作码可能会给出虚假的结果。如果 B 树行少于 P3 列,则使用 P4 作为数据类型。

如果 P1 为 -1,则 P3 是一个寄存器编号,并且数据类型取自该寄存器中的值。

P5 是数据类型的位掩码。SQLITE_INTEGER 是最低有效位 (0x01)。SQLITE_FLOAT 是 0x02 位。SQLITE_TEXT 是 0x04。SQLITE_BLOB 是 0x08。SQLITE_NULL 是 0x10。

警告:此操作码无法可靠地区分 P1>=0 时 NULL 和 REAL。如果数据库包含 NaN 值,则此操作码将认为数据类型为 REAL,而实际上应该为 NULL。当 P1<0 并且值已存储在寄存器 P3 中时,则此操作码可以可靠地区分 NULL 和 REAL。问题只出现在 P1>=0 时。

仅当由 P1 和 P3 确定的值的类型对应于 P5 位掩码中的一个位时,才会执行跳转到地址 P2 的操作。

JournalMode将数据库 P1 的日志模式更改为 P3。P3 必须是 PAGER_JOURNALMODE_XXX 值之一。如果在各种回滚模式(delete、truncate、persist、off 和 memory)之间切换,则这是一个简单的操作。不需要 I/O。

如果切换到 WAL 模式或从 WAL 模式切换出去,则过程会更加复杂。

将包含最终日志模式的字符串写入寄存器 P2。

Jump根据最近 Compare 指令中 P1 向量是否小于、等于或大于 P2 向量,分别跳转到地址 P1、P2 或 P3 处的指令。

此操作码必须紧随 Compare 操作码之后。

LastP1 的下一个 RowidColumnPrev 指令的使用将引用数据库表或索引中的最后一个条目。如果表或索引为空,并且 P2>0,则立即跳转到 P2。如果 P2 为 0,或者表或索引不为空,则继续执行下一条指令。

此操作码将游标配置为以相反的顺序移动,从末尾到开头。换句话说,游标配置为使用 Prev,而不是 Next

Le此操作码的工作原理与 Lt 操作码相同,只是如果寄存器 P3 的内容小于或等于寄存器 P1 的内容,则会执行跳转。有关其他信息,请参阅 Lt 操作码。
LoadAnalysis读取数据库 P1 的 sqlite_stat1 表并将该表的内容加载到内部索引哈希表中。这将导致在准备所有后续查询时使用分析结果。
Lt比较寄存器 P1 和 P3 中的值。如果 reg(P3)<reg(P1),则跳转到地址 P2。

如果 P5 的 SQLITE_JUMPIFNULL 位被设置,并且 reg(P1) 或 reg(P3) 为 NULL,则执行跳转。如果 SQLITE_JUMPIFNULL 位未被设置,则如果任一操作数为 NULL,则继续执行。

P5 的 SQLITE_AFF_MASK 部分必须是一个关联字符 - SQLITE_AFF_TEXT、SQLITE_AFF_INTEGER 等等。在进行比较之前,将尝试根据此关联对两个输入进行强制转换。如果 SQLITE_AFF_MASK 为 0x00,则使用数字关联。请注意,关联转换将存储回输入寄存器 P1 和 P3。因此,此操作码可能会导致对寄存器 P1 和 P3 的持久性更改。

一旦任何转换完成,并且两个值都不为 NULL,就会比较这些值。如果两个值都是 blob,则使用 memcmp() 来确定比较结果。如果两个值都是文本,则使用 P4 中指定的适当排序规则函数来进行比较。如果未指定 P4,则使用 memcmp() 来比较文本字符串。如果两个值都是数字,则使用数字比较。如果两个值类型不同,则数字被认为小于字符串,字符串被认为小于 blob。

此操作码保存比较结果,供新的 Jump 操作码使用。

MakeRecord将从 P1 开始的 P2 个寄存器转换为 记录格式,用作数据库表中的数据记录或索引中的键。 Column 操作码可以稍后解码记录。

P4 可能是一个长度为 P2 个字符的字符串。字符串的第 N 个字符表示应用于索引键的第 N 个字段的列关联性。

字符到关联性的映射由 sqliteInt.h 中定义的 SQLITE_AFF_ 宏给出。

如果 P4 为 NULL,则所有索引字段的关联性都是 BLOB。

P5 的含义取决于是否启用了 SQLITE_ENABLE_NULL_TRIM 编译时选项

* 如果启用了 SQLITE_ENABLE_NULL_TRIM,则 P5 是可以进行 NULL 修剪的最右侧表的索引。

* 如果省略了 SQLITE_ENABLE_NULL_TRIM,则如果 MakeRecord 操作码允许接受序列类型为 10 的无更改记录,则 P5 的值为 OPFLAG_NOCHNG_MAGIC。此值仅在 assert() 内部使用,不会影响最终结果。

MaxPgcnt尝试将数据库 P1 的最大页面数量设置为 P3 中的值。不要让最大页面数量低于当前页面数量,并且如果 P3==0,则不要更改最大页面数量的值。

更改后的最大页面数量存储在寄存器 P2 中。

MemMaxP1 是此 VM 的根帧中的一个寄存器(如果此指令在子程序中执行,则根帧与当前帧不同)。将寄存器 P1 的值设置为其当前值和寄存器 P2 中的值中的最大值。

如果内存单元最初不是整数,则此指令将抛出错误。

Move将寄存器 P1..P1+P3-1 中的 P3 个值移动到寄存器 P2..P2+P3-1 中。寄存器 P1..P1+P3-1 剩下一个 NULL。寄存器范围 P1..P1+P3-1 和 P2..P2+P3-1 出现重叠将导致错误。P3 小于 1 将导致错误。
Multiply将寄存器 P1 中的值乘以寄存器 P2 中的值,并将结果存储在寄存器 P3 中。如果任一输入为 NULL,则结果为 NULL。
MustBeInt强制寄存器 P1 中的值为整数。如果 P1 中的值不是整数,并且无法在不丢失数据的情况下转换为整数,则立即跳转到 P2,或者如果 P2==0,则引发 SQLITE_MISMATCH 异常。
Ne此操作码的工作原理与 Eq 操作码相同,只是如果寄存器 P1 和 P3 中的操作数不相等,则会执行跳转。有关其他信息,请参阅 Eq 操作码。
NewRowid获取一个新的整数值记录编号(也称为“rowid”),用作表的键。该记录编号之前从未用作游标 P1 指向的数据库表中的键。新的记录编号将被写入寄存器 P2 中。

如果 P3>0,则 P3 是此 VDBE 根帧中的一个寄存器,它保存了之前生成的最大的记录号。不允许生成小于此值的新的记录号。当此值达到其最大值时,将生成 SQLITE_FULL 错误。P3 寄存器使用生成的记录号进行更新。此 P3 机制用于帮助实现 AUTOINCREMENT 功能。

下一个前进游标 P1,使其指向其表或索引中的下一个键值对。如果没有更多键值对,则直接进入到下一条指令。但是,如果游标前进成功,则立即跳转到 P2。

The Next 操作码仅在使用 SeekGTSeekGERewind 操作码定位游标后才有效。不允许 Next 紧随 SeekLTSeekLELast

P1 游标必须是真实表,而不是伪表。P1 必须在此操作码之前打开,否则程序将出现段错误。

P3 值是 btree 实现的一个提示。如果 P3==1,则意味着 P1 是一个 SQL 索引,并且如果该索引是唯一的,则可以省略此指令。P3 通常为 0。P3 始终为 0 或 1。

如果 P5 为正数且执行了跳转,则准备好的语句中的事件计数器编号 P5-1 将递增。

另请参阅:Prev

无冲突如果 P4==0,则寄存器 P3 持有一个由 MakeRecord 构造的 blob。如果 P4>0,则寄存器 P3 是形成未打包记录的 P4 个寄存器的第一个。

游标 P1 在索引 btree 上。如果由 P3 和 P4 标识的记录包含任何 NULL 值,则立即跳转到 P2。如果记录的所有项均为非 NULL,则执行检查以确定 P1 索引 btree 中是否有任何行具有匹配的键前缀。如果没有匹配项,则立即跳转到 P2。如果有匹配项,则直接进入并使 P1 游标指向匹配行。

此操作码类似于 NotFound,区别在于,如果搜索键输入的任何部分为 NULL,则始终会执行分支。

此操作将游标置于无法向任何方向前进的状态。换句话说,NextPrev 操作码在此操作之后将无法使用。

另请参阅:NotFoundFoundNotExists

无操作不执行任何操作。此指令通常用作跳转目标。
将寄存器 P1 中的值解释为布尔值。在寄存器 P2 中存储布尔值的补码。如果寄存器 P1 中的值为 NULL,则在 P2 中存储 NULL。
不存在P1 是在 SQL 表 btree(具有整数键)上打开的游标的索引。P3 是一个整数行 ID。如果 P1 不包含行 ID 为 P3 的记录,则立即跳转到 P2。或者,如果 P2 为 0,则引发 SQLITE_CORRUPT 错误。如果 P1 包含行 ID 为 P3 的记录,则使游标指向该记录并直接进入下一条指令。

The SeekRowid 操作码执行相同的操作,但还允许 P3 寄存器包含非整数值,在这种情况下,始终会执行跳转。此操作码要求 P3 始终包含整数。

The NotFound 操作码在索引 btree(具有任意多值键)上执行相同的操作。

此操作将游标置于无法向任何方向前进的状态。换句话说,NextPrev 操作码在此操作之后将无法使用。

另请参阅:FoundNotFoundNoConflictSeekRowid

未找到如果 P4==0,则寄存器 P3 持有一个由 MakeRecord 构造的 blob。如果 P4>0,则寄存器 P3 是形成未打包记录的 P4 个寄存器的第一个。

游标 P1 在索引 btree 上。如果由 P3 和 P4 标识的记录不是 P1 中任何条目的前缀,则跳转到 P2。如果 P1 包含一个条目,其前缀与 P3/P4 记录匹配,则控制权直接进入下一条指令,P1 将指向匹配条目。

此操作将游标置于无法向任何方向前进的状态。换句话说,NextPrev 操作码在此操作之后将无法使用。

另请参阅:FoundNotExistsNoConflictIfNoHope

非空如果寄存器 P1 中的值不为 NULL,则跳转到 P2。
将 NULL 写入寄存器 P2。如果 P3 大于 P2,则还将 NULL 写入寄存器 P3 以及 P2 和 P3 之间的每个寄存器。如果 P3 小于 P2(通常 P3 为零),则仅将寄存器 P2 设置为 NULL。

如果 P1 值不为零,则还设置 MEM_Cleared 标志,以便即使在 NeEq 上设置了 SQLITE_NULLEQ,NULL 值也不会比较相等。

空行将游标 P1 移动到空行。当游标位于空行时,发生的任何 Column 操作将始终写入 NULL。

如果游标 P1 尚未打开,则现在将其打开到一个特殊伪游标,该游标始终为每一列返回 NULL。

偏移量在寄存器 r[P3] 中存储指向数据库文件的字节偏移量,该偏移量是游标 P1 当前指向的记录的有效负载的开头。

P2 是 sqlite_offset() 函数参数的列号。此操作码本身不使用 P2,但 P2 值由代码生成器使用。此操作码的 P1、P2 和 P3 操作数与 Column 相同。

此操作码仅在 SQLite 使用 -DSQLITE_ENABLE_OFFSET_SQL_FUNC 选项编译时才可用。

偏移量限制此操作码执行与 LIMIT 和 OFFSET 处理相关的常见计算。r[P1] 保存限制计数器。r[P3] 保存偏移量计数器。操作码计算 LIMIT 和 OFFSET 的组合值,并将该值存储在 r[P2] 中。计算出的 r[P2] 值是完成查询所需的总行数。

如果 r[P3] 为零或负数,则意味着没有 OFFSET,r[P2] 被设置为 LIMIT 的值,r[P1]。

如果 r[P1] 为零或负数,则意味着没有 LIMIT,r[P2] 被设置为 -1。

否则,r[P2] 被设置为 r[P1] 和 r[P3] 的总和。

一次第一次在字节码程序的每次调用中遇到此操作码时,直接进入下一条指令。在第二次以及每次后续调用中遇到此操作码时,Jump 到 P2。

顶层程序通过将 P1 操作数与程序开头的 Init 操作码上的 P1 操作数进行比较来确定第一次调用。如果 P1 值不同,则直接进入并使此操作码的 P1 等于 Init 的 P1。如果 P1 值相同,则执行跳转。

对于子程序,VdbeFrame 中有一个位掩码来确定是否应该执行跳转。位掩码是必要的,因为自修改代码技巧不适用于递归触发器。

打开自动索引此操作码与 OpenEphemeral 的工作方式相同。它具有不同的名称以区分其用途。使用此操作码创建的表将用于在联接中自动创建的临时索引。
打开副本打开一个新的游标 P1,它指向与游标 P2 相同的临时表。P2 游标必须由先前的 OpenEphemeral 操作码打开。仅临时游标可以复制。

复制的临时游标用于物化视图的自联接。

打开临时表打开一个新的游标 P1 到临时表。即使主数据库是只读的,游标也始终以读写方式打开。临时表在关闭游标时会自动删除。

如果游标 P1 已在临时表上打开,则该表将被清除(所有内容都被擦除)。

P2 是临时表中的列数。如果 P4==0,则游标指向一个 BTree 表;如果 P4 不为 0,则指向一个 BTree 索引。如果 P4 不为 NULL,则它指向一个 KeyInfo 结构,该结构定义了索引中键的格式。

P5 参数可以是 btree.h 中定义的 BTREE_* 标志的掩码。这些标志控制 btree 操作的各个方面。BTREE_OMIT_JOURNAL 和 BTREE_SINGLE 标志会自动添加。

如果 P3 为正数,则 reg[P3] 会略微修改,以便它可以用作 Insert 的零长度数据。这是一个优化,它避免了为初始化该寄存器而额外使用 Blob 操作码。

打开伪表打开一个新的游标,它指向一个包含单个数据行的假表。该单行的内容是内存寄存器 P2 的内容。换句话说,游标 P1 成为寄存器 P2 中包含的 MEM_Blob 内容的别名。

由此操作码创建的伪表用于保存排序器输出的单个行,以便可以使用 Column 操作码将该行分解为单个列。The Column 操作码是唯一可与伪表一起使用的游标操作码。

P3 是伪表将存储的记录中的字段数。如果 P2 为 0 或负数,则伪游标将为每一列返回 NULL。

打开只读为数据库表打开一个只读游标,该表的根页在数据库文件中为 P2。数据库文件由 P3 确定。P3==0 表示主数据库,P3==1 表示用于临时表的数据库,而 P3>1 表示使用相应的附加数据库。为新游标提供一个标识符 P1。P1 值不必连续,但所有 P1 值都应为小整数。P1 为负数是非法的。

允许的 P5 位

  • 0x02 OPFLAG_SEEKEQ: 此游标仅用于等式查找(实现为一对 SeekGE/IdxGTSeekLE/IdxLT 操作码)。

P4 值可以是整数 (P4_INT32) 或指向 KeyInfo 结构的指针 (P4_KEYINFO)。如果它是指向 KeyInfo 对象的指针,则正在打开的表必须是 索引 btree,其中 KeyInfo 对象定义了该索引 btree 的内容和排序顺序。否则,如果 P4 是一个整数值,则正在打开的表必须是 表 btree,其列数不少于 P4 的值。

另请参阅:OpenWriteReopenIdx

打开写入在根页为 P2 的表或索引上打开一个名为 P1 的读写游标(或者如果 P5 中设置了 OPFLAG_P2ISREG 位 - 参见下文 - 则其根页保存在寄存器 P2 中)。

P4 值可以是整数 (P4_INT32) 或指向 KeyInfo 结构的指针 (P4_KEYINFO)。如果它是指向 KeyInfo 对象的指针,则正在打开的表必须是 索引 btree,其中 KeyInfo 对象定义了该索引 btree 的内容和排序顺序。否则,如果 P4 是一个整数值,则正在打开的表必须是 表 btree,其列数不少于 P4 的值。

允许的 P5 位

  • 0x02 OPFLAG_SEEKEQ: 此游标仅用于等式查找(实现为一对 SeekGE/IdxGTSeekLE/IdxLT 操作码)。
  • 0x08 OPFLAG_FORDELETE: 此游标仅用于在索引 btree 中搜索并随后删除条目。这是对存储引擎的提示,存储引擎可以忽略该提示。官方 SQLite b*tree 存储引擎不使用此提示,但 COMDB2 使用此提示。
  • 0x10 OPFLAG_P2ISREG: 使用寄存器 P2 的内容作为根页,而不是 P2 本身的值。

此指令与 OpenRead 的工作方式相同,只是它以读写模式打开游标。

另请参阅:OpenReadReopenIdx

取寄存器 P1 和 P2 中值的逻辑或,并将答案存储在寄存器 P3 中。

如果 P1 或 P2 其中之一非零(真),则结果为 1(真),即使另一个输入为 NULL。NULL 和 false 或两个 NULL 会产生 NULL 输出。

Pagecount将当前数据库 P1 中的页数写入内存单元 P2。
Param此操作码仅存在于通过 Program 指令调用的子程序中。将当前存储在调用(父)帧的内存单元中的值 Copy 到当前帧地址空间中的单元 P2。这由触发器程序用于访问 new.* 和 old.* 值。

父帧中单元的地址是通过将 P1 参数的值加上调用 Program 指令的 P1 参数的值来确定的。

ParseSchema读取和解析数据库 P1 的模式表中与 WHERE 子句 P4 匹配的所有条目。如果 P4 是一个 NULL 指针,则重新解析 P1 的整个模式。

此操作码调用解析器创建一个新的虚拟机,然后运行新的虚拟机。因此,它是一个可重入操作码。

Permutation设置下一条指令中 Compare 运算符使用的排列。排列存储在 P4 操作数中。

排列仅对下一条操作码有效,该操作码必须是 Compare,并且其 P5 中设置了 OPFLAG_PERMUTE 位。

P4 整数数组中的第一个整数是数组的长度,不会成为排列的一部分。

Prev备份游标 P1,使其指向其表或索引中的上一组键/数据对。如果没有上一组键/值对,则会贯穿到下一条指令。但如果游标备份成功,则立即跳转到 P2。

只有在 SeekLTSeekLELast 操作码用于定位游标之后,Prev 操作码才有效。不允许 Prev 紧随 SeekGTSeekGERewind

P1 游标必须是真正的表,而不是伪表。如果 P1 未打开,则行为未定义。

P3 值是 btree 实现的一个提示。如果 P3==1,则意味着 P1 是一个 SQL 索引,并且如果该索引是唯一的,则可以省略此指令。P3 通常为 0。P3 始终为 0 或 1。

如果 P5 为正数且执行了跳转,则准备好的语句中的事件计数器编号 P5-1 将递增。

Program执行作为 P4 传递的触发器程序(类型为 P4_SUBPROGRAM)。

P1 包含内存单元的地址,该内存单元包含一个用作子程序参数的值数组的第一个内存单元。P2 包含如果子程序使用 RAISE() 函数抛出 IGNORE 异常时要跳转到的地址。如果不可能抛出 IGNORE 异常,则 P2 可能为零。寄存器 P3 包含此(父)VM 中内存单元的地址,该内存单元用于在运行时分配子 vdbe 所需的内存。

P4 是指向包含触发器程序的 VM 的指针。

如果 P5 非零,则启用递归程序调用。

PureFunc调用用户函数(P4 是指向 sqlite3_context 对象的指针,该对象包含指向要运行的函数的指针),并从寄存器 P2 及其后续寄存器中获取参数。参数数量在 P4 指向的 sqlite3_context 对象中。函数的结果存储在寄存器 P3 中。寄存器 P3 不得是函数输入之一。

P1 是一个 32 位位掩码,指示函数的每个参数在编译时是否被确定为常量。如果第一个参数是常量,则设置 P1 的位 0。这用于确定是否可以使用 sqlite3_set_auxdata() API 安全地保留与用户函数参数关联的元数据,直到下次调用此操作码为止。

此操作码的工作原理与 Function 完全相同。唯一的区别是名称。此操作码用于必须完全非确定性的函数。一些内置的日期/时间函数可以是确定性的或非确定性的,具体取决于其参数。当这些函数以非确定性的方式使用时,它们将检查是否使用 PureFunc 而不是 Function 调用它们,如果是,则抛出错误。

另请参阅:AggStepAggFinalFunction

ReadCookie从数据库 P1 读取 cookie 编号 P3 并将其写入寄存器 P2。P3==1 是模式版本。P3==2 是数据库格式。P3==3 是推荐的页面缓存大小,依此类推。P1==0 是主数据库文件,P1==1 是用于存储临时表的数据库文件。

在执行此指令之前,必须对数据库进行读锁定(必须启动事务或必须打开游标)。

RealP4 是指向 64 位浮点值的指针。将该值写入寄存器 P2。
RealAffinity如果寄存器 P1 包含整数,则将其转换为实数值。

此操作码用于从具有 REAL 亲和性的列中提取信息。出于空间效率的考虑,此类列值可能仍以整数形式存储,但提取后,我们希望它们仅具有实数值。

ReleaseReg从服务中释放寄存器。此操作码完成后,寄存器中任何内容都是不可靠的。

释放的寄存器将是从 P1 开始的 P2 寄存器,除非 P3 的第 ii 位设置,则不释放寄存器 P1+ii。换句话说,P3 是要保留的寄存器的掩码。

释放寄存器会清除 Mem.pScopyFrom 指针。这意味着如果释放的寄存器的内容是使用 SCopy 设置的,则对 SCopy 的源寄存器值的更改将不再在 sqlite3VdbeMemAboutToChange() 中生成断言错误。

如果 P5 设置,则所有释放的寄存器都会将其类型设置为 MEM_Undefined,以便任何随后对释放的寄存器进行的读取尝试(在重新初始化之前)都会生成断言错误。

P5 应该在每次调用此操作码时都设置。但是,代码生成器中的某些地方会在寄存器被使用之前释放它们,假设这些寄存器在被使用之前不会被重新分配用于其他目的,因此可以安全地释放它们。此假设是有效的。

此操作码仅在测试和调试版本中可用。它不会为发布版本生成。此操作码的目的是帮助验证生成的字节码。此操作码实际上并不有助于计算答案。

Remainder计算整数寄存器 P2 除以寄存器 P1 后的余数,并将结果存储在寄存器 P3 中。如果寄存器 P1 中的值为零,则结果为 NULL。如果任何一个操作数为 NULL,则结果为 NULL。
ReopenIdxReopenIdx 操作码与 OpenRead 类似,只是它首先检查 P1 上的游标是否已在同一 B 树上打开,如果是,则此操作码将变为无操作。换句话说,如果游标已经打开,则不要重新打开它。

ReopenIdx 操作码只能与 P5==0 或 P5==OPFLAG_SEEKEQ 以及 P4 为 P4_KEYINFO 对象一起使用。此外,P3 值必须与相同游标编号的每个其他 ReopenIdxOpenRead 相同。

允许的 P5 位

  • 0x02 OPFLAG_SEEKEQ: 此游标仅用于等式查找(实现为一对 SeekGE/IdxGTSeekLE/IdxLT 操作码)。

另请参阅:OpenReadOpenWrite

ResetCount将更改计数器的值复制到数据库句柄更改计数器(由随后的 sqlite3_changes() 调用返回)。然后,VM 的内部更改计数器重置为 0。这由触发器程序使用。
ResetSorter删除在游标 P1 上打开的临时表或排序器中的所有内容。

此操作码仅适用于用于排序并在 OpenEphemeralSorterOpen 中打开的游标。

ResultRow寄存器 P1 到 P1+P2-1 包含一行结果。此操作码导致 sqlite3_step() 调用以 SQLITE_ROW 返回代码终止,并设置 sqlite3_stmt 结构以提供对 r(P1)..r(P1+P2-1) 值的访问,作为结果行。
Return跳转到存储在寄存器 P1 中的地址。如果 P1 是返回地址寄存器,则这将完成从子例程的返回。

如果 P3 为 1,则只有当寄存器 P1 包含整数值时,才会执行跳转,否则执行将贯穿到下一条操作码,Return 将变为无操作。如果 P3 为 0,则寄存器 P1 必须包含整数,否则会引发 assert()。当此操作码与 BeginSubrtn 结合使用时,P3 应设置为 1,否则应设置为 0。

寄存器 P1 中的值不会被此操作码更改。

P2 不被字节码引擎使用。但是,如果 P2 为正且小于当前地址,则 CLI 中的“EXPLAIN”输出格式化程序将缩进从 P2 操作码到不包括当前 Return 的所有操作码。P2 应为此操作码正在返回的子例程中的第一个操作码。因此,P2 值是一个字节码缩进提示。请参阅 wherecode.c 和 shell.c 中的 tag-20220407a。

Rewind对 P1 的 RowidColumnNext 指令的下次使用将引用数据库表或索引中的第一个条目。如果表或索引为空,则立即跳转到 P2。如果表或索引不为空,则贯穿到下一条指令。

如果 P2 为零,则断言 P1 表永远不为空,因此永远不会执行跳转。

此操作码将游标配置为向前移动,从开头移动到结尾。换句话说,游标被配置为使用 Next,而不是 Prev

RowCellP1 和 P2 都是打开的游标。两者都必须在相同类型的表上打开 - intkey 或索引。此操作码用于将当前行从 P2 复制到 P1。如果游标是在 intkey 表上打开的,则寄存器 P3 包含要与 P1 中的新记录一起使用的行 ID。如果它们是在索引表上打开的,则不使用 P3。

此操作码必须后跟 Insert 或 InsertIdx 操作码,并设置 OPFLAG_PREFORMAT 标志以完成插入操作。

RowData将当前游标 P1 指向的行完整内容写入寄存器 P2。没有对数据进行解释。它只是按在数据库文件中找到的原样复制到 P2 寄存器中。

如果游标 P1 是索引,则内容是行的键。如果游标 P2 是表,则提取的内容是数据。

P1 游标必须指向真正的表的有效行(不是 NULL 行),而不是伪表。

如果 P3!=0,则此操作码允许在数据库页面中创建临时指针。这意味着输出寄存器的内容将在游标移动后立即失效 - 包括由其他游标移动造成的失效,这些游标“保存”当前游标的位置以便它们可以写入相同的表。如果 P3==0,则会将数据的副本复制到内存中。P3!=0 速度更快,但 P3==0 更安全。

如果 P3!=0,则 P2 寄存器的内容不适合在 OP_Result 中使用,任何 OP_Result 都会使 P2 寄存器内容失效。P2 寄存器内容会被类似于 Function 的操作码或任何指向同一表的其他游标的使用所使失效。

Rowid在寄存器 P2 中存储一个整数,该整数是 P1 当前指向的表条目的键。

P1 可以是普通表或虚拟表。以前有一个单独的 OP_VRowid 操作码用于虚拟表,但现在这个操作码适用于两种类型的表。

RowSetAdd将寄存器 P2 中保存的整数值插入到寄存器 P1 中保存的 RowSet 对象中。

如果 P2 不是整数,则断言失败。

RowSetRead从 P1 中的 RowSet 对象中提取最小值,并将该值放入寄存器 P3 中。或者,如果 RowSet 对象 P1 最初为空,则将 P3 保持不变,并跳转到指令 P2。
RowSetTest假设寄存器 P3 保存一个 64 位整数值。如果寄存器 P1 包含一个 RowSet 对象,并且该 RowSet 对象包含 P3 中保存的值,则跳转到寄存器 P2。否则,将 P3 中的整数插入 RowSet,并继续执行下一个操作码。

RowSet 对象针对将整数集插入到不同阶段的情况进行了优化,每个阶段的集合中不包含重复项。每个集合都由唯一的 P4 值标识。第一个集合必须有 P4==0,最后一个集合必须有 P4==-1,对于所有其他集合必须有 P4>0。

这允许优化:(a) 当 P4==0 时,无需测试 RowSet 对象是否包含 P3,因为可以保证它不包含 P3;(b) 当 P4==-1 时,无需插入该值,因为它永远不会被测试;(c) 当插入作为集合 X 的一部分的值时,无需搜索以查看该值是否以前作为集合 X 的一部分被插入(只有在它以前作为其他集合的一部分被插入时)。

Savepoint根据 P1 的值,打开、释放或回滚由参数 P4 命名的保存点。要打开新的保存点,请将 P1 设置为 0 (SAVEPOINT_BEGIN)。要释放(提交)现有保存点,请将 P1 设置为 1 (SAVEPOINT_RELEASE)。要回滚现有保存点,请将 P1 设置为 2 (SAVEPOINT_ROLLBACK)。
SCopy将寄存器 P1 的浅拷贝复制到寄存器 P2 中。

此指令对值进行浅拷贝。如果该值是字符串或 Blob,则该拷贝只是对原始值的指针,因此,如果原始值更改,则该拷贝也会更改。更糟的是,如果原始值被释放,则该拷贝将变为无效。因此,程序必须保证在拷贝的整个生命周期内原始值不会更改。使用 Copy 来制作完整的拷贝。

SeekEnd将游标 P1 定位到 B 树的末尾,以便将新条目追加到 B 树上。

假设游标仅用于追加,因此,如果游标有效,则游标必须已经指向 B 树的末尾,因此不会对游标进行任何更改。

SeekGE如果游标 P1 指向 SQL 表(使用整数值作为键的 B 树),则使用寄存器 P3 中的值作为键。如果游标 P1 指向 SQL 索引,则 P3 是用作非打包索引键的 P4 个寄存器数组中的第一个寄存器。

重新定位游标 P1,使其指向大于或等于键值的最小条目。如果没有大于或等于键值的记录,并且 P2 不为零,则跳转到 P2。

如果游标 P1 是使用 OPFLAG_SEEKEQ 标志打开的,则此操作码将定位到与键值完全匹配的记录,或者会导致跳转到 P2。当游标为 OPFLAG_SEEKEQ 时,此操作码必须后跟一个具有相同参数的 IdxLE 操作码。如果此操作码成功,则将跳过 IdxGT 操作码,但在后续循环迭代中将使用 IdxGT 操作码。OPFLAG_SEEKEQ 标志是向 B 树层提供的提示,表明这是一个相等性搜索。

此操作码将游标配置为向前移动,从开头移动到结尾。换句话说,游标被配置为使用 Next,而不是 Prev

另请参见:FoundNotFound、SeekLt、SeekGt、SeekLe

SeekGT如果游标 P1 指向 SQL 表(使用整数值作为键的 B 树),则使用寄存器 P3 中的值作为键。如果游标 P1 指向 SQL 索引,则 P3 是用作非打包索引键的 P4 个寄存器数组中的第一个寄存器。

重新定位游标 P1,使其指向大于键值的最小条目。如果没有大于键值的记录,并且 P2 不为零,则跳转到 P2。

此操作码将游标配置为向前移动,从开头移动到结尾。换句话说,游标被配置为使用 Next,而不是 Prev

另请参见:FoundNotFound、SeekLt、SeekGe、SeekLe

SeekHit如有必要,增加或减少游标 P1 的 seekHit 值,使其不小于 P2,不大于 P3。

seekHit 整数表示索引中已知至少存在一个匹配项的项数的最大值。如果 seekHit 值小于索引查找中相等项的总数,则 IfNoHope 操作码可能会运行,以查看是否可以提前放弃 IN 循环,从而节省工作量。这是 IN-early-out 优化的一个组成部分。

P1 必须是有效的 B 树游标。

SeekLE如果游标 P1 指向 SQL 表(使用整数值作为键的 B 树),则使用寄存器 P3 中的值作为键。如果游标 P1 指向 SQL 索引,则 P3 是用作非打包索引键的 P4 个寄存器数组中的第一个寄存器。

重新定位游标 P1,使其指向小于或等于键值的最大的条目。如果没有小于或等于键值的记录,并且 P2 不为零,则跳转到 P2。

此操作码将游标配置为以相反的顺序移动,从末尾到开头。换句话说,游标配置为使用 Prev,而不是 Next

如果游标 P1 是使用 OPFLAG_SEEKEQ 标志打开的,则此操作码将定位到与键值完全匹配的记录,或者会导致跳转到 P2。当游标为 OPFLAG_SEEKEQ 时,此操作码必须后跟一个具有相同参数的 IdxLE 操作码。如果此操作码成功,则将跳过 IdxGE 操作码,但在后续循环迭代中将使用 IdxGE 操作码。OPFLAG_SEEKEQ 标志是向 B 树层提供的提示,表明这是一个相等性搜索。

另请参见:FoundNotFound、SeekGt、SeekGe、SeekLt

SeekLT如果游标 P1 指向 SQL 表(使用整数值作为键的 B 树),则使用寄存器 P3 中的值作为键。如果游标 P1 指向 SQL 索引,则 P3 是用作非打包索引键的 P4 个寄存器数组中的第一个寄存器。

重新定位游标 P1,使其指向小于键值的最大的条目。如果没有小于键值的记录,并且 P2 不为零,则跳转到 P2。

此操作码将游标配置为以相反的顺序移动,从末尾到开头。换句话说,游标配置为使用 Prev,而不是 Next

另请参见:FoundNotFound、SeekGt、SeekGe、SeekLe

SeekRowidP1 是在 SQL 表 B 树(使用整数值作为键)上打开的游标的索引。如果寄存器 P3 不包含整数,或者如果 P1 不包含行 ID 为 P3 的记录,则立即跳转到 P2。或者,如果 P2 为 0,则引发 SQLITE_CORRUPT 错误。如果 P1 包含行 ID 为 P3 的记录,则将游标定位到该记录,并继续执行下一条指令。

NotExists 操作码执行相同的操作,但使用 NotExists 时,P3 寄存器必须保证包含整数值。对于此操作码,寄存器 P3 可能不包含整数值。

The NotFound 操作码在索引 btree(具有任意多值键)上执行相同的操作。

此操作将游标置于无法向任何方向前进的状态。换句话说,NextPrev 操作码在此操作之后将无法使用。

另请参阅:FoundNotFoundNoConflictSeekRowid

SeekScan此操作码是 SeekGE 的前缀操作码。换句话说,此操作码必须紧跟在 SeekGE 之后。此约束由 assert() 语句检查。

此操作码使用后续 SeekGE 的 P1 到 P4 操作数。在以下文本中,后续 SeekGE 操作码的操作数表示为 SeekOP.P1 到 SeekOP.P4。只有此操作码的 P1、P2 和 P5 操作数也使用,分别称为 This.P1、This.P2 和 This.P5。

此操作码有助于优化多列索引上的 IN 运算符,其中 IN 运算符位于索引的后面项上,方法是避免在 B 树上进行不必要的搜索,而是替换为进入 B 树下一行的步骤。如果省略此操作码或将其设为无操作,则会获得正确的结果。

SeekGE.P3 和 SeekGE.P4 操作数标识一个非打包键,该键是我们希望游标 SeekGE.P1 指向的条目。将此 SeekGE.P3/P4 行称为“目标”。

如果 SeekGE.P1 游标当前没有指向有效行,则此操作码为无操作,并将控制权传递到 SeekGE 中。

如果 SeekGE.P1 游标指向有效行,则该行可能是目标行,也可能在目标行附近,稍微在目标行之前,或者在目标行之后。如果游标当前在目标行之前,则此操作码尝试通过在游标上调用 sqlite3BtreeStep() 1 到 This.P1 次,将游标定位到目标行或目标行之后。

This.P5 参数是一个标志,指示如果游标最终指向超过目标行的有效行,该怎么做。如果 This.P5 为假 (0),则跳转到 SeekGE.P2。如果 This.P5 为真 (非零),则跳转到 This.P2。P5==0 的情况发生在 IN 约束右侧没有不等式约束时。跳转到 SeekGE.P2 会结束循环。P5!=0 的情况发生在 IN 运算符右侧存在不等式约束时。在这种情况下,This.P2 将直接指向或指向检查循环终止的 IdxGTIdxGE 操作码之前的设置代码。

此操作码的可能结果

  1. 如果游标最初没有指向任何有效行,则继续执行后续的 SeekGE 操作码。

  2. 如果游标仍指向目标行之前的行,即使在调用了 This.P1 次 sqlite3BtreeNext() 之后,也继续执行 SeekGE

  3. 如果游标仍指向目标行(因为它最初就在目标行上,或者因为一次或多次 sqlite3BtreeNext() 调用将游标移动到目标行上),则跳转到 This.P2..。

  4. 如果游标最初在目标行之前,并且调用 sqlite3BtreeNext() 将游标移出索引的末尾(表明目标行肯定不存在于 B 树中),则跳转到 SeekGE.P2,结束循环。

  5. 如果游标最终指向超过目标行的有效行(表明目标行不存在于 B 树中),则如果 This.P5==0,则跳转到 SeekOP.P2,或者如果 This.P5>0,则跳转到 This.P2。
Sequence查找游标 P1 的下一个可用序列号。将序列号写入寄存器 P2 中。在执行此指令后,游标上的序列号将递增。
SequenceTestP1 是一个排序游标。如果序列计数器当前为零,则跳转到 P2。无论是否执行跳转,都会递增序列值。
SetCookie将整数 P3 写入数据库 P1 的 Cookie P2 中。P2==1 是模式版本。P2==2 是数据库格式。P2==3 是建议的页面缓存大小,等等。P1==0 是主数据库文件,而 P1==1 是用于存储临时表的数据库文件。

必须在执行此操作码之前启动事务。

如果 P2 是 SCHEMA_VERSION Cookie(Cookie 编号 1),则内部模式版本将设置为 P3-P5。“PRAGMA schema_version=N”语句将 P5 设置为 1,因此内部模式版本将不同于数据库模式版本,从而导致模式重置。

SetSubtype将寄存器 P2 的子类型值设置为来自寄存器 P1 的整数。如果 P1 为 NULL,则从 p2 中清除子类型。
ShiftLeft将寄存器 P2 中的整数值向左移位,移动位数由寄存器 P1 中的整数指定。将结果存储在寄存器 P3 中。如果任一输入为 NULL,则结果为 NULL。
ShiftRight将寄存器 P2 中的整数值向右移动由寄存器 P1 中的整数值指定的位数。将结果存储在寄存器 P3 中。如果任一输入为 NULL,则结果为 NULL。
SoftNull将寄存器 P1 设置为具有 NULL 值,如 MakeRecord 指令所见,但不释放与该寄存器关联的任何字符串或 Blob 内存,因此,如果该值是之前使用 SCopy 复制的字符串或 Blob,则这些副本将继续有效。
Sort此操作码与 Rewind 执行完全相同的操作,只是它会递增用于测试的未公开全局变量。

排序是通过将记录写入排序索引,然后回绕该索引并从头到尾播放来实现的。我们使用 Sort 操作码而不是 Rewind 来执行回绕操作,以便递增全局变量,回归测试可以确定优化器是否正确优化了排序。

SorterCompareP1 是一个排序器游标。此指令将寄存器 P3 中的记录 Blob 的前缀与排序器游标当前指向的条目的前缀进行比较。仅比较 r[P3] 和排序器记录的前 P4 个字段。

如果 P3 或排序器在其重要字段中包含 NULL(不包括末尾忽略的 P4 字段),则假设比较相等。

如果两个记录彼此比较相等,则继续执行下一条指令。如果它们不同,则 Jump 到 P2。

SorterData将排序器游标 P1 的当前排序器数据写入寄存器 P2。然后清除游标 P3 上的列标题缓存。

此操作码通常用于将记录从排序器移动到寄存器中,该寄存器是使用 OpenPseudo 创建的伪表游标的来源。该伪表游标是参数 P3 所标识的游标。清除此操作码的一部分的 P3 列缓存可以避免我们必须发出单独的 NullRow 指令来清除该缓存。

SorterInsert寄存器 P2 持有一个使用 MakeRecord 指令创建的 SQL 索引键。此操作码将该键写入排序器 P1 中。该条目的数据为 nil。
SorterNext此操作码与 Next 的工作原理相同,只是 P1 必须是一个排序器对象,并且已经为其调用了 SorterSort 操作码。此操作码将游标推进到下一个排序后的记录,或者如果没有更多排序后的记录,则跳到 P2。
SorterOpen此操作码与 OpenEphemeral 的工作原理相同,只是它会打开一个瞬时索引,该索引专门用于使用外部合并排序算法对大型表进行排序。

如果参数 P3 非零,则表示排序器可以假设对每个键的前 P3 个字段进行稳定排序足以产生所需的结果。

SorterSort在将所有记录插入到由 P1 标识的排序器对象中之后,调用此操作码以实际执行排序。如果没有任何要排序的记录,则 Jump 到 P2。

此操作码是用于排序器对象的 SortRewind 的别名。

SqlExec运行 P4 字符串中指定的 SQL 语句或语句集。

P1 参数是选项的位掩码

0x0001 在 P4 中的语句正在运行时禁用 Auth 和 Trace 回调。

0x0002 在 P4 中的语句正在运行时将 db->nAnalysisLimit 设置为 P2。

String长度为 P1(字节)的字符串值 P4 存储在寄存器 P2 中。

如果 P3 非零且寄存器 P3 的内容等于 P5,则寄存器 P2 的数据类型将转换为 BLOB。内容是相同的字节序列,只是被解释为 BLOB 而不是字符串,就好像它被 CAST 一样。换句话说

if( P3!=0 and reg[P3]==P5 ) reg[P2] := CAST(reg[P2] as BLOB)

String8P4 指向一个以 nul 结尾的 UTF-8 字符串。此操作码在第一次执行之前会转换为 String 操作码。在此转换过程中,会计算字符串 P4 的长度并将其存储为 P1 参数。
Subtract从寄存器 P2 中的值中减去寄存器 P1 中的值,并将结果存储在寄存器 P3 中。如果任一输入为 NULL,则结果为 NULL。
TableLock获得对特定表的锁定。此指令仅在启用共享缓存功能时使用。

P1 是 sqlite3.aDb[] 中要获得锁定的数据库的索引。如果 P3==0,则获得读锁定,如果 P3==1,则获得写锁定。

P2 包含要锁定的表的根页面。

P4 包含指向要锁定的表名称的指针。这仅用于在无法获得锁定时生成错误消息。

Trace如果启用语句跟踪,则在语句跟踪输出上写入 P4。

操作数 P1 必须为 0x7fffffff,P2 必须为正数。

Transaction如果事务尚未处于活动状态,则在数据库 P1 上开始事务。如果 P2 非零,则开始写事务,或者如果读事务已处于活动状态,则将其升级为写事务。如果 P2 为零,则开始读事务。如果 P2 为 2 或更大,则开始排他事务。

P1 是要开始事务的数据库文件的索引。索引 0 是主数据库文件,索引 1 是用于临时表的该文件。索引 2 或更大用于附加数据库。

如果开始写事务并且 Vdbe.usesStmtJournal 标志为真(如果 Vdbe 可能修改多行并且可能抛出 ABORT 异常,则设置此标志),则也可能打开语句事务。更具体地说,如果数据库连接当前未处于自动提交模式,或者如果有其他活动语句,则会打开语句事务。语句事务允许在错误发生后回滚此 VDBE 所做的更改,而无需回滚整个事务。如果未遇到错误,则语句事务将在 VDBE 停止时自动提交。

如果 P5!=0,则此操作码还会将架构 cookie 与 P3 以及架构生成计数器与 P4 进行比较。只要数据库架构发生变化,cookie 就会更改其值。此操作用于检测 cookie 何时发生变化以及当前进程是否需要重新读取架构。如果 P3 中的架构 cookie 与数据库标题中的架构 cookie 不同,或者 P4 中的架构生成计数器与当前生成计数器不同,则会引发 SQLITE_SCHEMA 错误,执行会停止。sqlite3_step() 包装函数可能会重新准备语句并从头开始重新运行它。

TypeCheck将亲和性应用于从 P1 开始的 P2 个寄存器范围。从 P4 中的 Table 对象中获取亲和性。如果任何值无法强制转换为正确的类型,则会引发错误。

此操作码类似于 Affinity,只是此操作码强制寄存器类型为 Table 列类型。这用于实现“严格亲和性”。

仅当 P3 为零时才会检查 GENERATED ALWAYS AS ... STATIC 列。当 P3 非零时,不会对静态生成列进行类型检查。虚拟列在查询时计算,因此它们从不进行检查。

前提条件

  • P2 应为 P4 表中非虚拟列的个数。
  • 表 P4 应为 STRICT 表。

如果任何前提条件为假,则会发生断言错误。

Vacuum清理整个数据库 P1。P1 为 0 表示“主数据库”,2 或更大表示附加数据库。不能清理“临时”数据库。

如果 P2 非零,则它是保存字符串的寄存器,该字符串是要将清理结果写入的文件。当 P2 为零时,清理会覆盖原始数据库。

Variable将绑定参数 P1 的值传输到寄存器 P2
VBeginP4 可以是指向 sqlite3_vtab 结构的指针。如果是这样,则调用该表的 xBegin 方法。

此外,无论 P4 是否设置,都要检查是否不是从对虚拟表 xSync() 方法的回调中调用它。如果是,则错误代码将设置为 SQLITE_LOCKED。

VCheckP4 是指向 Table 对象的指针,该对象是支持 xIntegrity() 方法的架构 P1 中的虚拟表。此操作码运行该虚拟表的 xIntegrity() 方法,使用 P3 作为整数参数。如果报告了错误,则将表名称附加到错误消息,并将该消息存储在 P2 中。如果没有看到任何错误,则将寄存器 P2 设置为 NULL。
VColumn在寄存器 P3 中存储游标 P1 的当前行的 P2-th 列的值。

如果 VColumn 操作码用于在 UPDATE 操作期间获取不变列的值,则 P5 值为 OPFLAG_NOCHNG。这将导致 sqlite3_vtab_nochange() 函数在虚拟表实现的 xColumn 方法中返回真。P5 列也可能包含其他位(OPFLAG_LENGTHARG 或 OPFLAG_TYPEOFARG),但这些位未被 VColumn 使用。

VCreateP2 是一个寄存器,它保存数据库 P1 中虚拟表的名称。调用该表的 xCreate 方法。
VDestroyP4 是数据库 P1 中虚拟表的名称。调用该表的 xDestroy 方法。
VFilterP1 是使用 VOpen 打开的游标。P2 是一个地址,如果过滤后的结果集为空,则跳到该地址。

P4 为 NULL 或由模块的 xBestIndex 方法生成的字符串。P4 字符串的解释留给模块实现。

此操作码调用由 P1 指定的虚拟表的 xFilter 方法。传递给 xFilter 的整数查询计划参数存储在寄存器 P3 中。寄存器 P3+1 存储要传递给 xFilter 方法的 argc 参数。寄存器 P3+2..P3+1+argc 是 argc 个附加参数,这些参数将作为 argv 传递给 xFilter。当传递给 xFilter 时,寄存器 P3+2 成为 argv[0]。

如果过滤后的结果集为空,则会跳到 P2。

VInitIn将寄存器 P2 设置为指向游标 P1 的 ValueList 对象的指针,该对象具有缓存寄存器 P3 和输出寄存器 P3+1。此 ValueList 对象可以用作 sqlite3_vtab_in_first() 和 sqlite3_vtab_in_next() 的第一个参数,以提取存储在 P1 游标中的所有值。寄存器 P3 用于保存 sqlite3_vtab_in_first() 和 sqlite3_vtab_in_next() 返回的值。
VNext将虚拟表 P1 推进到其结果集中的下一行,并跳到指令 P2。或者,如果虚拟表已到达其结果集的末尾,则继续执行下一条指令。
VOpenP4 是指向虚拟表对象的指针,即 sqlite3_vtab 结构。P1 是游标编号。此操作码会打开到该虚拟表的游标,并将该游标存储在 P1 中。
VRenameP4 是指向虚拟表对象的指针,即 sqlite3_vtab 结构。此操作码调用相应的 xRename 方法。寄存器 P1 中的值将作为 zName 参数传递给 xRename 方法。
VUpdateP4 指向一个虚拟表对象,也就是 sqlite3_vtab 结构体。该操作码调用相应的 xUpdate 方法。P2 值是指向从 P3 开始的一系列连续内存单元,传递给 xUpdate 调用。寄存器 (P3+P2-1) 中的值对应于传递给 xUpdate 的 argv 数组中的第 p2 个元素。

xUpdate 方法将执行 DELETE 或 INSERT 操作,或同时执行两者。argv[0] 元素(对应于内存单元 P3)是要删除的行 ID。如果 argv[0] 为 NULL,则不执行删除操作。argv[1] 元素是新行的行 ID。可以为 NULL,让虚拟表自己选择新的行 ID。数组中的后续元素是新行中各个列的值。

如果 P2==1,则不执行插入操作。argv[0] 是要删除的行 ID。

P1 是一个布尔型标志。如果该标志设置为 true,并且 xUpdate 调用成功,则 sqlite3_last_insert_rowid() 返回的值将设置为刚插入行的行 ID 值。

P5 是在插入或更新操作发生约束冲突时要应用的错误操作(OE_Replace、OE_Fail、OE_Ignore 等)。

Yield将程序计数器与寄存器 P1 中的值交换。这将导致挂起协程。

如果由该指令启动的协程以 YieldReturn 结束,则继续执行下一条指令。但如果由该指令启动的协程以 EndCoroutine 结束,则跳至 P2,而不是继续执行下一条指令。

另请参见:InitCoroutine

ZeroOrNull如果寄存器 P1 和 P3 都不是 NULL,则在寄存器 P2 中存储一个零。如果寄存器 P1 或 P3 其中一个是 NULL,则在寄存器 P2 中放置一个 NULL。

本页面最后修改时间:2024-05-03 13:40:22 UTC