.gen 文件定义
.gen 是 GenePad 的完整项目文件,文件本质是标准 SQLite 3 数据库
(文件头 16 字节为 SQLite format 3\0,MIME 登记为
application/vnd.genepad.database)。它比 .gjson 更完整,
除序列与注释外还保存引物结合位点、测序图谱(chromatogram)、比对条目、撤销/重做栈、
编辑审计、附件,以及 编辑历史树(完整克隆谱系 + 每步的序列快照)。
任何语言的标准 SQLite 驱动都能直接读写——不需要安装任何专用解析库。
Overview
读取原则
- 无需自定义二进制 parser——用任意 SQLite 驱动打开
.gen,再按下方表结构查询即可。 sequence_blocks必须按block_index升序拼接,不能依赖数据库默认返回顺序。segments、qualifiers、components、alignment、references、history_op、annotations_json等字段是 JSON 字符串;history_tree.tree是整棵历史树的 JSON;history_snapshots.nested_base64是原始字节的 base64;chromatogram 的 trace 是「UTF-8 JSON 装进 BLOB」——这些都需二次解析。.gen不持久化restrictionSites(酶切位点),由读取程序根据主序列重新检测:GenePad 在序列长度 ≤ 50,000 bp 时同步检测,超长序列走后台流式检测。- 保存出的
.gen永远是自包含单文件——GenePad 落盘前执行PRAGMA wal_checkpoint(TRUNCATE),用户目录里不会出现-wal/-shm伴生文件。 - 布尔值全部以字符串
'true'/'false'保存;键可能缺失,读取端要给默认值(见 project_meta 键参考)。 - 编辑历史两张表(
history_tree+history_snapshots)记录编辑历史(克隆谱系):根节点 = 当前文件状态,子节点 = 产生它的输入,树向过去生长。语义与字段详见编辑历史树一节。
Getting Started
快速上手:读一个 .gen(Python)
Python 内置的 sqlite3 模块就够,零第三方依赖。下面 20 行代码读出元信息、完整序列和全部 feature:
import json
import sqlite3
con = sqlite3.connect("plasmid.gen")
con.row_factory = sqlite3.Row
# —— 元信息:key-value 表,布尔以字符串保存
meta = {row["key"]: row["value"] for row in con.execute(
"SELECT key, value FROM project_meta")}
is_circular = meta.get("isCircular") == "true"
# —— 主序列:必须按 block_index 升序拼接
sequence = "".join(row["bases"] for row in con.execute(
"SELECT bases FROM sequence_blocks ORDER BY block_index"))
print(meta.get("name", "Untitled"), len(sequence), "bp,",
"环状" if is_circular else "线状")
# —— features:segments / qualifiers 列是 JSON 字符串
for f in con.execute("SELECT * FROM features ORDER BY start_pos"):
length = f["end_pos"] - f["start_pos"] + 1
if f["start_pos"] > f["end_pos"]: # 跨原点的环形 feature
length = len(sequence) - f["start_pos"] + f["end_pos"] + 1
qualifiers = json.loads(f["qualifiers"]) if f["qualifiers"] else {}
print(f["name"], f["type"], f["strand"], length, "bp", qualifiers)
提示:坐标是 1-based inclusive,start=1, end=10 表示第 1~10 号碱基共 10 个;环形序列跨原点时 start > end(详见坐标与环形序列)。
可以下载真实示例文件试跑:41-pPUR-P2A-BFPnls-sgRNA1-RNF2_4AtoG_MS2-MA.gen(112 KB)——7,952 bp 的环形慢病毒 sgRNA 载体,含 25 个 feature、2 条引物及结合位点。它也是一个很好的教学样本:碱基全为小写(验证「不强制大写」)、isCircular/isDamMethylated 等布尔以字符串保存、feature Misc_Feature_1 带 4 段 segments。对它运行上面代码的输出:
41-pPUR-P2A-BFPnls-sgRNA1-RNF2_4AtoG_MS2-MA 7952 bp, 环状
hPGK promoter misc_feature forward 507 bp {}
Puromycin misc_feature forward 594 bp {}
P2A misc_feature forward 57 bp {}
TagBFP2 misc_feature forward 711 bp {}
...(共 25 个 feature)
Walkthrough
实例解剖:ITR-CMV-AAV5-AfeI.gen(逐表过一遍)
下面用一份真实文件把上面所有概念串起来:ITR-CMV-AAV5-AfeI.gen——一个 8,206 bp 的环形 AAV 载体(ITR 骨架 + CMV enhancer / chicken β-actin 启动子 + AAV5 cap 基因 + NeoR/KanR + AmpR,带 AfeI 定点突变与 NNK 简并引物文库位点)。它由 GenePad 打开同名 .dna 文件后「另存为 .gen」得到,因此携带完整的编辑历史(225 个历史快照),是观察历史树表结构的最佳样本。文件总大小 37,810,176 字节(约 36 MB),其中约九成是历史快照的 base64——这也直观展示了「历史随文件走」的代价。
所有命令与输出都是对这份文件的实际运行结果,可逐条复现(换成本页提供的 小样本或任何一份 .gen 同样能跑,只是行数不同):
# ① 魔头验证:SQLite 3
$ xxd -l 16 ITR-CMV-AAV5-AfeI.gen
00000000: 5351 4c69 7465 2066 6f72 6d61 7420 3300 SQLite format 3.
# ② 打开并列出全部表(sqlite_sequence 是 AUTOINCREMENT 的簿记表,不算业务表)
$ sqlite3 ITR-CMV-AAV5-AfeI.gen
sqlite> .tables
alignment_entries features project_meta
attachments history_snapshots schema_version
base_color_ranges history_tree sequence_blocks
chromatogram_data primer_binding_sites undo_entries
edit_history primers
# ③ 每张表各有多少行
sqlite> SELECT 'sequence_blocks', COUNT(*) FROM sequence_blocks
...> UNION ALL SELECT 'features', COUNT(*) FROM features
...> UNION ALL SELECT 'base_color_ranges', COUNT(*) FROM base_color_ranges
...> UNION ALL SELECT 'primers', COUNT(*) FROM primers
...> UNION ALL SELECT 'primer_binding_sites', COUNT(*) FROM primer_binding_sites
...> UNION ALL SELECT 'alignment_entries', COUNT(*) FROM alignment_entries
...> UNION ALL SELECT 'chromatogram_data', COUNT(*) FROM chromatogram_data
...> UNION ALL SELECT 'attachments', COUNT(*) FROM attachments
...> UNION ALL SELECT 'undo_entries', COUNT(*) FROM undo_entries
...> UNION ALL SELECT 'edit_history', COUNT(*) FROM edit_history
...> UNION ALL SELECT 'history_tree', COUNT(*) FROM history_tree
...> UNION ALL SELECT 'history_snapshots', COUNT(*) FROM history_snapshots
...> UNION ALL SELECT 'schema_version', COUNT(*) FROM schema_version;
sequence_blocks|1
features|18
base_color_ranges|4
primers|30
primer_binding_sites|30
alignment_entries|0
chromatogram_data|0
attachments|0
undo_entries|0
edit_history|0
history_tree|1
history_snapshots|225
schema_version|0
这份文件刚从 .dna 转存、尚未在 GenePad 里做新编辑,所以 undo / 审计 / 比对 / 图谱 / 附件表全空,内容都集中在序列、注释与历史三块。逐块看:
④ project_meta——19 个键全在(保存流程幂等 upsert 所致,GenBank 元数据没有就落空串):
sqlite> SELECT key, value FROM project_meta;
name|ITR-CMV-AAV5-AfeI
description|
isCircular|true
isDoubleStranded|true
isDamMethylated|false
isDcmMethylated|false
accession|
organism|
date|
moleculeType|
division|
version|
keywords|
source|
comments|
references|
typeOfDisplay|file_name
customName|ITR-CMV-AAV5-AfeI
fileName|ITR-CMV-AAV5-AfeI.dna
isDirty|0
看点:fileName 记录了它的出身(同名 .dna 转存);isCircular=true 环形质粒;Dam/Dcm 甲基化在源文件里显式为否(键值 'false';若源文件未设置,GenePad 写空串 '',读取时空串同样按 false 处理,键缺失才是「未知」)。
⑤ sequence_blocks——8,206 bp 一块装下(块上限 10,000,故只有 block 0):
sqlite> SELECT block_index, length(bases), substr(bases, 1, 50) FROM sequence_blocks;
0|8206|TAAGAAACCATTATTATCATGACATTAACCTATAAAAATAGGCGTATCACGAGG
⑥ features——18 个注释(注意多来源 CDS 的重叠与 none 方向):
sqlite> SELECT name, type, start_pos, end_pos, strand, color FROM features ORDER BY start_pos;
telL|misc_feature|625|652|none|#a6acb3
telR|misc_feature|653|680|none|#a6acb3
CMV enhancer|enhancer|911|1290|none|#ffffff
chicken β-actin promoter|promoter|1293|1568|forward|#ffffff
Cap9|CDS|2685|3209|forward|#ffcc99
VP2|CDS|3096|3209|forward|#993366
AAV5 CAP|CDS|3210|4877|forward|#ffcc99
VP2|CDS|3210|4877|forward|#ff0000
AAP5|CDS|3211|3816|forward|#993366
Variable Region|CDS|4425|4445|forward|#993366
AGC-ACC(Ser-Thr)|misc_feature|4833|4835|none|#ff0000
telR|misc_feature|5128|5155|none|#a6acb3
telL|misc_feature|5156|5183|none|#a6acb3
f1 ori|rep_origin|5184|5638|reverse|#ffff00
ITR|repeat_region|6172|6274|none|#ffe4c4
ori|rep_origin|6517|7105|reverse|#ffff00
NeoR/KanR|CDS|7276|8070|reverse|#ccffcc
AmpR promoter|promoter|8071|8175|reverse|#ffffff
看点:VP1/VP2/VP3 共用 cap 读码框(Cap9 与 VP2 区段重叠、坐标交叠是正常的);无方向注释(ITR、telL/telR、enhancer)strand='none';中文名与全角括号原样存取。qualifiers 列也带着完整 GenBank 限定词,例如 NeoR/KanR 一行:
sqlite> SELECT qualifiers FROM features WHERE name = 'NeoR/KanR';
{"transl_table":["1"],"label":["NeoR/KanR"],"gene":["aph(3')-II (or nptII)"],
"codon_start":["1"],"product":["aminoglycoside phosphotransferase from Tn5"],
"translation":["MIEQDGLHAGSPAAWVERLFGYDWAQQTIG…"]}
⑦ base_color_ranges——手工碱基着色(同一区间正反链各一条,对应序列视图上下链颜色):
sqlite> SELECT id, start_pos, end_pos, strand, color FROM base_color_ranges;
base-color-0|4414|4430|forward|#ef4444
base-color-2|4414|4430|reverse|#ef4444
base-color-1|4437|4453|forward|#3b82f6
base-color-3|4437|4453|reverse|#3b82f6
4414–4453 正是 AAV5 cap 的 variable region(上面 features 表里的 Variable Region 4425–4445)——作者用红/蓝两段底色把突变文库区域标了出来。
⑧ primers / primer_binding_sites——30 条引物各一个结合位点:
sqlite> SELECT id, name, sequence FROM primers LIMIT 4;
primer-0|AAV5-AfeI-NNK-R|TACGTGCCGGTCGCGGGaGC
primer-1|AAV5-AfeI-NNK|GGCCACCAACAACCAGAGCTCCACCnnknnknnknnknnknnknnkACTGCtCCCGCGACCGGCAC
primer-2|AAV5-L8-AfeI-F|CCAACAACCAGAGCgcttaaAgcGCtCCCGCGACCGGCA
primer-3|NQJ p5enhancer F|CCATGATGCTCATCAAGAACA
sqlite> SELECT id, start_pos, end_pos, bound_strand,
...> round(melting_temperature, 1) AS tm, length(annealed_bases) AS annealed
...> FROM primer_binding_sites LIMIT 4;
primer-0-site-4437|4437|4456|reverse|69.9|20
primer-1-site-4409|4409|4453|forward|66.0|36
primer-2-site-4414|4414|4452|forward|73.6|39
primer-3-site-4603|4603|4623|forward|44.4|21
看点:primer-1 是 NNK 简并引物(连续小写 nnk 21 个简并位,用于 cap 文库饱和突变),大小写原样保留;位点 id 遵循 {primer_id}-site-{start} 固定格式;components / alignment 两列是 camelCase JSON,例如 {"hybridizedRange":"4436-4455","bases":"GCtCCCGCGACCGGCACGTA"}。
⑨ history_tree——一行存整棵克隆谱系。tree 列是 1,377,563 字符的 JSON(仅节点元数据,不含序列)。根节点原样片段(键序即写出顺序):
sqlite> SELECT substr(tree, 1, 720) FROM history_tree WHERE id = 1;
{"id":225,"name":"ITR-CMV-AAV5-AfeI.dna","seqLen":8206,"circular":true,
"operation":"inFusionCloning","resurrectable":true,"strandedness":"double",
"type":"DNA","upstreamModification":"Unmodified","downstreamModification":"Unmodified",
"inputSummaries":[
{"manipulation":"replace","val1":905,"val2":4893,
"enzymes":[["BglII",2],["BglII",2]]},
{"manipulation":"overlapAndInsert","val1":0,"val2":1796},
{"manipulation":"overlapAndInsert","val1":0,"val2":1767},
{"manipulation":"overlapAndInsert","val1":0,"val2":483}],
"children":[ … 四个输入:骨架 + 三个片段 … ]}
这段 JSON 说的是:最终载体由一次 In-Fusion 无缝克隆组装而成——用 BglII 双酶切替换单元(905–4893,0-based 闭区间),再让三个带重叠末端的片段(1,796 / 1,767 / 483 bp)重组插入;根的四个 children 正是骨架(#20)与三个片段供体(#88 / #156 / #224,均为 PCR 扩增产物)。每个 children 元素是产生它的一个输入,递归向过去生长。用十来行 Python 就能把整棵树摘要出来:
import json, sqlite3
con = sqlite3.connect("ITR-CMV-AAV5-AfeI.gen")
tree = json.loads(con.execute(
"SELECT tree FROM history_tree WHERE id = 1").fetchone()[0])
def walk(node, depth=0):
inputs = "; ".join(
f"{s['manipulation']}({s['val1']},{s['val2']})"
for s in node.get("inputSummaries", []))
print(" " * depth +
f"#{node['id']} {node.get('name','')} op={node.get('operation','')} "
f"len={node.get('seqLen')} circ={node.get('circular')} [{inputs}]")
for child in node.get("children", []):
walk(child, depth + 1)
walk(tree) # 共 226 个节点(含根)
实际输出(截选——根的骨架分支、片段分支与最深处的「原始文件」节点):
#225 ITR-CMV-AAV5-AfeI.dna op=inFusionCloning len=8206 circ=True [replace(905,4893); overlapAndInsert(0,1796); overlapAndInsert(0,1767); overlapAndInsert(0,483)]
#20 ITR-CB-Rh74.dna op=insert len=8221 circ=True [insertAt(4418,0)]
#19 ITR-CB-Rh74.dna op=remove len=8200 circ=True [remove(4418,4438)]
#18 ITR-CB-Rh74.dna op=remove len=8221 circ=True [remove(1402,1437)]
…(骨干上一长串 insert / remove / insertRestrictionSite 微编辑)…
#1 Untitled 7.dna op=replace len=8301 circ=True [replace(2684,4849)]
#0 Untitled 7.dna op=invalid len=8250 circ=True [] ← 原始导入文件
#88 CB.dna op=amplifyFragment len=1797 circ=False [amplify(887,2683)]
#87 ITR-CMV-AAV5-AfeI.dna op=insert len=8106 circ=True [insertAt(4433,0)]
…(cap 供体分支,一路追溯到)…
#180 pAAV5-XR5-right-NQJ.dna op=invalid len=7438 circ=True [] ← 另一原始文件
#156 Cap-1.dna op=amplifyFragment len=1768 circ=False [amplify(2666,4433)] ┐
#224 Cap-2.dna op=amplifyFragment len=484 circ=False [amplify(4413,4896)] ┘ 另两个片段供体
读法:op=invalid 是「从外部文件导入」的起点节点;骨干分支(Untitled 7 → AarI → ITR-CB-Rh74)记录了载体骨架的每一步酶切/插入/定点突变,cap 供体分支(pAAV5-XR5… → CB.dna 的 PCR 扩增 amplifyFragment)最终作为 In-Fusion 片段汇入根节点——一份完整可追溯的克隆实验记录。GenePad 的「历史」面板渲染的就是这两张表。
⑩ history_snapshots——225 个历史时点的序列快照:
sqlite> SELECT seq_type, COUNT(*), SUM(length(nested_base64)) FROM history_snapshots;
1|225|34367936 -- 全部为压缩 DNA 快照;nested 共 34.4 MB base64
sqlite> SELECT node_id, length(sequence) AS seq_len, seq_type,
...> length(nested_base64) AS nested FROM history_snapshots
...> ORDER BY node_id LIMIT 4;
node_id|seq_len|seq_type|nested
0|8250|1|8032 -- 原始导入文件(树里 #0)的序列与嵌套标注
1|8301|1|14092
2|8304|1|14308
3|8307|1|14308
sqlite> SELECT substr(sequence, 1, 60) FROM history_snapshots WHERE node_id = 0;
TAAGAAACCATTATTATCATGACATTAACCTATAAAAATAGGCGTATCACGAGGCCCTTT
sqlite> SELECT COUNT(*) FROM history_snapshots WHERE node_id = 225;
0 -- 根节点永远没有快照(= 当前文件状态)
sequence 列已经是 GenePad 解好的可读碱基串(打开 .dna 时把分段压缩快照解码后存入),nested_base64 是快照随附的嵌套 TLV 原始字节(该历史时点的 features/primers/notes 等标注包)的 base64,供「打开快照」时惰性解码。34.4 MB base64 ≈ 25.8 MB 原始字节,就是这份文件 36 MB 体积的来源——不需要历史时可用写回一节的瘦身配方清掉。
如何获得同类文件:拿任意一份带历史的 .dna 文件(商业合成载体常自带克隆谱系)在 GenePad 里打开,「另存为」选 .gen 即可——历史树会随转换完整落入这两张表;此后在 GenePad 里的每次编辑也会增量追加快照。
Schema
核心表结构(全部 14 张)
project_meta项目元信息 key-value 表
| 字段 | 类型 | 说明 |
|---|---|---|
key | TEXT PRIMARY KEY | 元信息键 |
value | TEXT | 元信息值,布尔值也以字符串保存 |
保存时按 key upsert(INSERT ... ON CONFLICT(key) DO UPDATE),从不整表清空——因此旧文件里多余的自定义 key 会原样保留。下表是 GenePad 会写入的全部键:
| key | 写入值 | 读取行为 / 默认 | 说明 |
|---|---|---|---|
name | string | 缺失 → 'Untitled' | 项目名(GenBank LOCUS 名) |
description | string | 缺失 → '' | 描述(GenBank DEFINITION) |
isCircular | 'true'/'false' | 缺失 → false | 拓扑:环状 / 线状,无独立 topology 列 |
isDoubleStranded | 'true'/'false' | 缺失或任何非 'false' 值 → true | 双链 / 单链 |
isDamMethylated | 'true'/'false'/'' | 键缺失 → 未知(undefined);'' → false | Dam 甲基化状态 |
isDcmMethylated | 'true'/'false'/'' | 同上 | Dcm 甲基化状态 |
accession | string | 缺失 → undefined | GenBank ACCESSION |
organism | string | 缺失 → undefined | GenBank ORGANISM |
date | string | 缺失 → undefined | GenBank LOCUS 行日期 |
moleculeType | string | 缺失 → undefined | LOCUS 分子类型(DNA 等) |
division | string | 缺失 → undefined | GenBank division 代码 |
version | string | 缺失 → undefined | GenBank VERSION 行 |
keywords | string | 缺失 → undefined | GenBank KEYWORDS |
source | string | 缺失 → undefined | GenBank SOURCE |
comments | string | 缺失 → undefined | GenBank COMMENT |
references | JSON 字符串 GenBankRef[] | 非法 JSON → undefined | 文献条目,形状见下方 |
typeOfDisplay | 'file_name'/'custom' | 缺失 → 'file_name' | 标签页标题来源 |
customName | string | 缺失 → '' | 自定义显示名(typeOfDisplay 为 custom 时生效) |
fileName | string | 缺失 → undefined | 原始导入文件名;桌面端打开时会被实际文件名覆盖 |
isDirty | '0' | 加载时不读取 | 保存流程的簿记字段,每次落盘后写 '0' |
GenBankRef 的 JSON 形状(references 键的值):
// JSON: GenBankRef[]
{
"number": 1, // 必有
"authors": "Smith J., Lee K.",
"title": "Cloning of pBR322",
"journal": "Nucleic Acids Res 2026",
"pubmed": "12345678",
"remark": "..." // 其余字段均可省略
}
sequence_blocks主序列分块
| 字段 | 类型 | 说明 |
|---|---|---|
block_index | INTEGER PRIMARY KEY | 0-based block index |
bases | TEXT NOT NULL | 该块碱基字符串,除最后一块外恰为 10,000 bases |
SELECT bases FROM sequence_blocks ORDER BY block_index;
分块规则:每块 BLOCK_SIZE = 10000 bp,block_index = floor(offset / 10000)(offset 为 0-based 偏移)——块 0 存偏移 0–9999(即 1-based 位置 1–10000),块 1 存 10000–19999,最后一块可以不足 10,000。没有 start_pos 列:块起点 = block_index × 10000。序列总长用 SELECT COALESCE(SUM(LENGTH(bases)), 0) FROM sequence_blocks 计算。写入时全删全写(非增量更新),block_index 必须从 0 起连续无空洞——GenePad 按范围取块拼接,中间缺块会静默错位。碱基大小写保留原样(不强制大写),字符集为 IUPAC。
features序列注释
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | feature id,GenePad 生成格式 {prefix}-{timestamp}-{counter} |
name | TEXT NOT NULL | feature 名称 |
type | TEXT NOT NULL | 如 gene, CDS, promoter, rep_origin, misc_feature |
start_pos | INTEGER NOT NULL | 1-based inclusive start |
end_pos | INTEGER NOT NULL | 1-based inclusive end |
strand | TEXT NOT NULL | forward / reverse / none |
color | TEXT | 显示颜色,CSS 颜色字符串(如 #e0502a),NULL 走默认色 |
label | TEXT | 显示标签(缺省用 name) |
note | TEXT | 备注 |
frame | INTEGER | 阅读框(0/1/2),可 NULL |
visible | INTEGER DEFAULT 1 | 1 可见,0 隐藏;缺省按 1 处理 |
segments | TEXT | JSON:FeatureSegment[],仅多片段(>1 段)feature 写入,否则 NULL |
qualifiers | TEXT | JSON:Record<string, string[]>,GenBank 限定词(/translation、/codon_start、/gene…) |
索引:idx_features_range ON features(start_pos, end_pos);GenePad 读取时按 ORDER BY start_pos 返回,范围查询用闭区间重叠判定 start_pos <= rangeEnd AND end_pos >= rangeStart。
两个 JSON 列的形状:
// segments 列 → JSON: FeatureSegment[](段数 > 1 时才写入)
[
{ "start": 1, "end": 120, "strand": "forward" },
{ "start": 500, "end": 874, "strand": "forward",
"name": " linker ", "color": "#3aaea0" } // name / color / strand 可选
]
// qualifiers 列 → JSON: Record<string, string[]>
{
"gene": ["AmpR"],
"codon_start": ["1"],
"translation": ["MSIQ..."]
}
primers / primer_binding_sites引物与结合位点(按 primer_id 关联)
| 字段 | 类型 | 说明 |
|---|---|---|
primers.id | TEXT PRIMARY KEY | 引物 id |
primers.name | TEXT NOT NULL | 引物名称 |
primers.sequence | TEXT NOT NULL | 引物序列(5'→3') |
primers.description | TEXT | 描述 |
primers.visible | INTEGER DEFAULT 1 | 1 显示,0 隐藏 |
primer_binding_sites.id | TEXT PRIMARY KEY | 固定格式 {primer_id}-site-{start} |
primer_binding_sites.primer_id | TEXT NOT NULL | 所属引物 |
primer_binding_sites.start_pos / end_pos | INTEGER NOT NULL | 1-based inclusive 结合范围 |
primer_binding_sites.bound_strand | TEXT NOT NULL | forward / reverse |
primer_binding_sites.annealed_bases | TEXT | 退火碱基,读取时 NULL → '' |
primer_binding_sites.melting_temperature | REAL | 解链温度,读取时 NULL → 0 |
primer_binding_sites.components | TEXT | JSON:PrimerComponent[],恒写入(可为 []) |
primer_binding_sites.alignment | TEXT | JSON:AlignmentSegment[],可 NULL |
索引:idx_binding_range ON primer_binding_sites(start_pos, end_pos)、idx_binding_primer ON primer_binding_sites(primer_id)。两个 JSON 列的形状:
// components 列 → JSON: PrimerComponent[]
[ { "hybridizedRange": "25-48", "bases": "ATGGC..." } ] // hybridizedRange 可选
// alignment 列 → JSON: AlignmentSegment[]
[
{ "type": "annealed", "primerBases": "ATG",
"templateBases": "TAC", "templateStart": 101, "templateEnd": 103 },
{ "type": "unannealed", "primerBases": "AAA",
"templateBases": "CGT", "templateStart": 104, "templateEnd": 106 },
{ "type": "del", "primerBases": "TT",
"templateBases": "", "templateStart": 107, "templateEnd": 107 }
]
base_color_ranges碱基颜色范围(手工着色)
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | 颜色段 id |
start_pos | INTEGER NOT NULL | 1-based inclusive start |
end_pos | INTEGER NOT NULL | 1-based inclusive end |
strand | TEXT NOT NULL | 仅 forward / reverse(读取时其他值一律归为 forward) |
color | TEXT NOT NULL | 颜色值 |
索引:idx_base_color_ranges ON base_color_ranges(start_pos, end_pos, strand)。整表替换式写入(DELETE 后逐行 INSERT)。
alignment_entries比对条目(Sanger 测序比对到主序列)
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | 比对条目 id |
name | TEXT NOT NULL | 条目名(通常为样品/引物名) |
sequence | TEXT NOT NULL | 被比对的读段序列 |
visible | INTEGER DEFAULT 1 | 1 显示,0 隐藏 |
features_json | TEXT | 预留列,当前版本不写入 |
chromatogram_json | TEXT | JSON:ChromatogramData(见下),可 NULL |
chromatogram_json 的形状(与 chromatogram_data 表同构,但这里存 JSON 字符串、非 BLOB):
// chromatogram_json → JSON: ChromatogramData
{
"traceA": [12, 45, 178, ...], // 四通道荧光强度,number[]
"traceC": [8, 31, 96, ...],
"traceG": [0, 12, 240, ...],
"traceT": [3, 60, 15, ...],
"peakLocations": [14, 25, 36, ...] // 峰位索引,number[]
}
chromatogram_data主序列测序图谱(id 固定 'primary')
| 字段 | 类型 | 说明 |
|---|---|---|
id | TEXT PRIMARY KEY | 主序列图谱固定为 'primary',整表仅此一行 |
trace_a / trace_c / trace_g / trace_t | BLOB | A/C/G/T 四通道荧光曲线 |
peak_locations | BLOB | 碱基峰位索引数组 |
BLOB 不是二进制浮点——内容是 JSON.stringify(number[]) 的 UTF-8 字节(TextEncoder 编码)。解码:JSON.parse(bytes.decode('utf-8')) / new TextDecoder().decode(blob)。写入流程为 DELETE WHERE id='primary' 再 INSERT,无该行表示主序列无图谱。
undo_entries撤销/重做栈(持久化,跨会话可用)
| 字段 | 类型 | 说明 |
|---|---|---|
sequence_number | INTEGER PRIMARY KEY AUTOINCREMENT | 单调递增序号,栈顶 = 最大值 |
stack_type | TEXT NOT NULL DEFAULT 'undo' | 'undo' 或 'redo'(同一张表存两条栈) |
operation_type | TEXT NOT NULL | 操作类型,取值见下 |
target_id | TEXT | 目标实体 id,序列级操作为 NULL |
before_diff / after_diff | TEXT | JSON:操作前/后差异负载,形状见下 |
history_op | TEXT | JSON:{ nodeId, operation, manipulation, val1, val2 }——序列编辑对应的编辑历史树节点元数据;undo 按 nodeId 弹根、redo 按相同 id 重挂。2026-09 起写入;旧库由迁移补列,旧行 NULL 时 undo 退化为仅同步根节点长度 |
栈机制:undo 栈上限 200 条,超出删最旧(按 sequence_number);每次压入新操作先清空整个 redo 栈;撤销 = 把最新 undo 行的 stack_type 翻转为 'redo';重做 = 读取后直接删除该行。operation_type 取值:addFeature, removeFeature, updateFeature, applyBaseColor, clearBaseColor, addPrimer, removePrimer, updatePrimer, insertBases, deleteBases, deleteBasesWrapped, replaceBases, replaceBasesWrapped, replaceSequence, addAlignmentEntry, removeAlignmentEntry(*Wrapped = 环形序列跨起点的删除/替换)。diff payload 形状:
// 实体操作:before/after 为完整对象或 null
add: { before: null, after: Feature | Primer }
update: { before: { id, changes }, after: { id, changes } } // changes = Partial<Feature|Primer>
applyBaseColor / clearBaseColor:
{ baseColorRanges: BaseColorRange[] }
// 序列编辑:附带编辑时刻的注释快照 + 编辑参数
InsertBasesDiff = Snapshot & { start: number, bases: string }
DeleteBasesDiff = Snapshot & { start: number, bases?: string, length?: number }
ReplaceBasesDiff = Snapshot & { start: number, oldBases?: string, newBases?: string }
// Snapshot = { features, baseColorRanges, primers, restrictionSites }
edit_history编辑审计日志(滚动保留最近 500 条)
| 字段 | 类型 | 说明 |
|---|---|---|
id | INTEGER PRIMARY KEY AUTOINCREMENT | 行号 |
timestamp | TEXT NOT NULL | new Date().toISOString() 格式(如 2026-08-22T03:14:07.000Z) |
operation_type | TEXT NOT NULL | 与 undo_entries 同一套操作类型 |
target_id | TEXT | 目标实体 id,可 NULL |
description | TEXT | 人类可读描述,如 Added feature 'AmpR' at 12..874 |
before_snapshot / after_snapshot | TEXT | JSON:受影响实体(增删为完整对象)或小编辑对象(如 { position, bases }、{ start, oldBases }) |
只追加、保存时不清空,但不是无限增长:每累计 50 次写入清理一次超限旧行,只保留最近 500 条(防止 .gen 随编辑无限膨胀;撤销用的是 undo_entries,不受此限)。可以用它做外部审计或改动统计;需要更长审计请自行定期导出。
history_tree / history_snapshots编辑历史(2026-09 起;克隆谱系 + 序列快照)
| 表 | 字段 | 说明 |
|---|---|---|
history_tree | id INTEGER PK(CHECK id=1) | 全表恒一行 |
tree TEXT NOT NULL | 整棵历史树的 JSON——只有节点元数据,不含序列;每次树结构变化(编辑/undo/裁切/清空)整行重写 | |
history_snapshots | node_id INTEGER PK | 对应树节点 id;根节点永远没有快照(根 = 当前文件状态) |
sequence TEXT NOT NULL | 该历史时点的完整序列(GenePad 已解好的可读碱基串) | |
seq_type INTEGER | 1=压缩 DNA(默认)、0/21/32=明文(蛋白/RNA)、29=仅修饰符(序列由父状态反推) | |
nested_base64 TEXT | 快照随附的嵌套 TLV 原始字节(该时点 features/primers/notes 标注包)的 base64,可 NULL | |
annotations_json TEXT | GenePad 自产快照的编辑前时点标注(结构化 JSON,见下),可 NULL;由 .dna 文件转存而来的 .gen 此列为 NULL(时点标注随 nested_base64 携带) |
写入时序:编辑时快照按 node_id 增量 upsert(ON CONFLICT DO UPDATE),分支裁切按 id 批量 DELETE;保存(另存为)时两张表全删全写。树 JSON 的节点形状与操作语义见编辑历史树一节;真实文件示例见ITR 解剖。
// annotations_json → SnapshotAnnotations(.dna 嵌套 TLV 的结构化对应物)
{
"features": [ /* Feature[],形状同 features 表行 → JSON */ ],
"primers": [ /* Primer[],形状同 primers + 结合位点 */ ],
"notes": { "name": "...", "description": "..." }, // 可选
"baseColorRanges": [ /* BaseColorRange[],可选 */ ],
"isDoubleStranded": true // 可选
}
attachments / schema_version预留表
| 表 | 结构 | 状态 |
|---|---|---|
attachments | id TEXT PK, name TEXT NOT NULL, mime_type TEXT NOT NULL, data BLOB NOT NULL | 预留:schema 每次都会建表,但当前版本无写入入口(id 约定为 att-{timestamp},data 存原始字节) |
schema_version | key TEXT PK, value TEXT | 预留:当前版本从不写入行。常量 SCHEMA_VERSION = 6 仅存在于代码中 |
兼容机制因此不是版本号门控:GenePad 打开文件时执行幂等 DDL(全部 CREATE TABLE IF NOT EXISTS,外加四条吞掉「列已存在」错误的 ALTER TABLE:features ADD COLUMN segments / qualifiers、undo_entries ADD COLUMN history_op、history_snapshots ADD COLUMN annotations_json),旧文件缺列/缺表会在打开时自动补齐。外部读取程序应对缺列容错(老文件 features 没有 segments/qualifiers 列,更老的库没有 history_* 两表)。
连接参数:GenePad 打开数据库即执行 PRAGMA journal_mode=WAL 与 PRAGMA synchronous=NORMAL;保存前执行 PRAGMA wal_checkpoint(TRUNCATE) 再整体复制字节,所以成品 .gen 永远自包含。外部工具用默认 journal(delete 模式)写入同样合法——GenePad 重新打开时会自动切回 WAL。
Conventions
坐标与环形序列
feature、primer binding site、base_color_range 的 start_pos / end_pos 均为 1-based inclusive:第一个碱基的位置是 1,start=1, end=10 表示第 1 到第 10 个碱基,长度 = end - start + 1。
环形序列(isCircular='true')的 feature 跨越原点时直接存 start > end,长度公式变为 seqLength - start + end + 1——从 start 走到序列末尾,再从 1 绕回到 end。例:1000 bp 环形质粒上 start=995, end=5 覆盖 995–1000 加 1–5 共 11 个碱基。除 isCircular 标志外没有任何额外的环形专用字段。
多段 feature(如移码拼接、内含子跳过的 CDS)不用多行表示,而是把各段写进 segments JSON 数组,每段独立 { start, end, strand?, name?, color? },同样是 1-based inclusive、支持 start > end 跨原点。
注意两套体系:sequence_blocks.block_index 与序列偏移是 0-based,而所有注释坐标是 1-based。换算:1-based 位置 p 位于块 floor((p-1) / 10000)。
History Tree
编辑历史树:字段与语义
GenePad 的编辑历史是一棵向过去生长的树:根节点 = 当前文件状态,子节点 = 产生它的输入。每次编辑时,旧根降级为新根的子节点;undo「弹根」、redo「重挂」。在 .gen 里树元数据进 history_tree.tree,每个非根节点的序列快照进 history_snapshots。节点 JSON 形状:
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 节点 id,整树唯一;快照表按它关联 |
name | string | 来源文件名(如 ITR-CB-Rh74.dna)或 GenePad 生成的操作描述名 |
seqLen | number | 该时点序列长度 |
circular | boolean | 该时点拓扑 |
operation | string | 产生该节点的操作:invalid(原始导入文件)、insert / remove / replace / flip(反向互补)/ changeTopology、insertRestrictionSite、setOrigin、digest / amplifyFragment(酶切/PCR)、ligateFragments / insertFragments / inFusionCloning(组装)等 |
resurrectable | boolean? | undo 弹根后的节点标记(可「复活」重挂) |
strandedness / type | string? | 链数(double/single)与分子类型(DNA 等) |
upstreamModification / downstreamModification | string? | 末端修饰状态(如 Unmodified) |
inputSummaries | InputSummary[] | 父编辑对该输入的处理,见下 |
oligos | { name, sequence, phosphorylated? }[]? | PCR 扩增等操作使用的引物 |
parameters | [string, string][]? | 操作参数对(如聚合酶、blunt 末端),保持写出顺序 |
children | Node[] | 递归子节点(输入),树深可达数百层 |
extraAttrs / rawChildren | object? / string[]? | 未建模 XML 属性 / 子元素的原样透传(.dna 往返保真用) |
InputSummary({ manipulation, val1, val2, enzymes?, extras? })描述父编辑对该输入做了什么。⚠️ val1/val2 是 0-based 闭区间——与 .gen 注释列的 1-based 不同,换算时务必 ±1。常见 manipulation:日常编辑 insert/insertAt(val1=0-based 位点)、remove、replace、flip、changeTopology;克隆组装 overlapAndInsert、ligateFragments、insertFragments;片段处理 amplify、digest(enzymes 为 [酶名, 位点数] 对的数组)。环形跨起点的编辑存为 val1 > val2 的环绕区间。
快照侧:seq_type=1 的压缩 DNA 在 GenePad 打开 .dna 时已解码为可读序列落库;seq_type=29 的「仅修饰符」快照没有序列载荷,序列由父状态经修饰符反推后同样落为可读序列。nested_base64 只做原样透传(打开快照时才惰性解码其中的时点标注),annotations_json 则是 GenePad 自己记录编辑时捕获的结构化时点标注——两者并存互不覆盖,解析时结构化 annotations 优先。
格式间流转:.gen ↔ .dna 互转(另存为)时历史随文件自动携带(.gen 两表 ↔ .dna Block 7/11);.gjson、GenBank、FASTA 没有历史字段,另存为这些格式会丢弃历史。
Behavior
保存与兼容行为
| 行为 | 规则 |
|---|---|
| 保存策略 | 内容表(sequence_blocks、features、base_color_ranges、primers、primer_binding_sites、chromatogram_data、history_tree、history_snapshots,及已加载比对的 alignment_entries)保存时全删全写;project_meta 按 key upsert;undo_entries、attachments 从不清空,edit_history 滚动保留 500 条。没有「精简导出」选项——历史随文件走 |
| 编辑期增量写 | 编辑过程中序列整表重建(writeSequence 从位置 0 重写全部分块,不做块级增量);每次序列编辑同时 upsert 一条历史快照(25 行/批多值 upsert)并整行重写 history_tree.tree;实体操作(feature/引物增删改)走单行 INSERT/UPDATE/DELETE |
| 批量写入 | sequence_blocks 每 100 行一条多值 INSERT,features 每 40 行一条(13 列 × 40 = 520 参数),history_snapshots 每 25 行一条;无显式事务,逐条语句自动提交 |
| 工作副本 | 打开 .gen 时先整体复制到临时目录(genepad-<random>.gen)再编辑,保存时 commitWorkingCopy(全量重写 + isDirty='0' + checkpoint)后把临时库字节整体写回目标路径 |
| 损坏容错 | 所有 JSON 列解析失败一律降级为 undefined/空,不报错(历史树 JSON 损坏则视为无历史);Android 导入路径会先校验 16 字节魔头 SQLite format 3\0 |
| 大小写 | 碱基大小写按原样存取(不强制大写);字符集为 IUPAC 扩展字母 |
| 加密 | 无——标准未加密 SQLite 文件,任何工具可读 |
Recipe
导出为 .gjson
把整个项目导出成 .gjson——GenePad 的文本 JSON 交换格式。下面这个脚本复刻了 GenePad 写出器的全部行为(键名、null 语义、空数组省略规则都一致),可直接交给 GenePad 或任何工具读回:
import json, sqlite3, time
def gen_to_gjson(path):
con = sqlite3.connect(path)
con.row_factory = sqlite3.Row
j = lambda s: json.loads(s) if s else None
meta = {r["key"]: r["value"] for r in con.execute("SELECT * FROM project_meta")}
n = lambda k: meta.get(k) or None # '' -> None:空串等同未设置
sequence = "".join(r["bases"] for r in con.execute(
"SELECT bases FROM sequence_blocks ORDER BY block_index"))
def segs_of(raw): # 多段(>1)feature 才带 segments 键
if not raw: return {}
segs = json.loads(raw)
if len(segs) <= 1: return {}
def seg(s): # 段级 strand/name/color 逐段可选
out = {"start": s["start"], "end": s["end"]}
for k in ("strand", "name", "color"):
if s.get(k): out[k] = s[k]
return out
return {"segments": [seg(s) for s in segs]}
# .gen 里这两个 JSON 列是 camelCase,.gjson 里是 snake_case
comp = lambda c: [{"hybridized_range": x.get("hybridizedRange"),
"bases": x["bases"]} for x in c]
align = lambda a: [{"type": x["type"], "primer_bases": x["primerBases"],
"template_bases": x["templateBases"],
"template_start": x["templateStart"],
"template_end": x["templateEnd"]} for x in a]
features = [{
"id": f["id"], "name": f["name"], "type": f["type"],
"start": f["start_pos"], "end": f["end_pos"], "strand": f["strand"],
"color": f["color"], "label": f["label"], "note": f["note"],
"frame": f["frame"], "visible": bool(f["visible"]),
**segs_of(f["segments"]), "qualifiers": j(f["qualifiers"]),
} for f in con.execute("SELECT * FROM features ORDER BY start_pos")]
primers = [{
"id": p["id"], "name": p["name"], "sequence": p["sequence"],
"description": p["description"],
"bindingSites": [{
"start": b["start_pos"], "end": b["end_pos"], "boundStrand": b["bound_strand"],
"annealedBases": b["annealed_bases"] or "",
"meltingTemperature": b["melting_temperature"] or 0.0,
"components": comp(json.loads(b["components"]) if b["components"] else []),
**({"alignment": align(json.loads(b["alignment"]))} if b["alignment"] else {}),
} for b in con.execute("SELECT * FROM primer_binding_sites WHERE primer_id = ?",
(p["id"],))],
"visible": None, # 写出器固定写 null
} for p in con.execute("SELECT * FROM primers")]
cnt = lambda t: con.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0]
doc = {
"version": "1.9",
"id": f"gjson-{int(time.time() * 1000)}", # 毫秒时间戳
"type_of_display": "file_name",
"custom_name": meta.get("name", "Untitled"),
"description": meta.get("description", ""),
"sequence": sequence,
"length": len(sequence),
"isCircular": meta.get("isCircular") == "true",
"isDoubleStranded": meta.get("isDoubleStranded") != "false",
"accession": n("accession"), "organism": n("organism"), "date": n("date"),
"moleculeType": n("moleculeType"), "division": n("division"),
"gbVersion": n("version"), "keywords": n("keywords"),
"source": n("source"), "comments": n("comments"),
"references": j(meta.get("references")),
"features": features,
**({"baseColorRanges": [{"id": r["id"], "color": r["color"],
"start": r["start_pos"], "end": r["end_pos"],
"strand": r["strand"]}
for r in con.execute("SELECT * FROM base_color_ranges")]}
if cnt("base_color_ranges") else {}),
**({"primers": primers} if primers else {}),
**({"alignmentEntries": [{"id": a["id"], "name": a["name"],
"sequence": a["sequence"], "visible": bool(a["visible"])}
for a in con.execute("SELECT * FROM alignment_entries")]}
if cnt("alignment_entries") else {}),
}
con.close()
return doc
# GenePad 写出 LF 换行;Windows 上 Python 需显式 newline="\n" 才能完全一致
with open("plasmid.gjson", "w", encoding="utf-8", newline="\n") as f:
json.dump(gen_to_gjson("plasmid.gen"), f, ensure_ascii=False, indent=2)
此脚本已与 GenePad 自身导出的同项目 .gjson 逐一核对:除两处原理性差异外完全一致——id(写出时刻的毫秒时间戳)与 feature 顺序(GenePad 按导出时的内存顺序,重开 .gen 后只能得到 start_pos 顺序)。可以用本页提供的两个样本文件自行复核:.gen 源文件、GenePad 导出的 .gjson。
转换要点:organism 等 GenBank 元数据在 .gen 里是空串 ''、在 .gjson 里写 null(空串等同未设置);结合位点 JSON 的键名从 camelCase 转为 snake_case;primers[].visible 恒为 null;baseColorRanges/primers/alignmentEntries 为空时整个键省略;segments 的段级 name/color 与 .gen JSON 列同形、直接透传即可;测序图谱(chromatogram)与编辑历史(history_* 两表)不进 .gjson。完整规则见 .gjson 文件定义。
Recipe
写回:外部修改 .gen
GenePad 对外部改动完全宽容——只要仍是合法 SQLite 且符合上述约定,重新打开即生效:
import sqlite3
con = sqlite3.connect("plasmid.gen")
# 改 feature 颜色 / 项目名 / 拓扑
con.execute("UPDATE features SET color = ? WHERE name = ?", ("#7c5cff", "AmpR"))
con.execute("UPDATE project_meta SET value = 'true' WHERE key = 'isCircular'")
con.execute("UPDATE project_meta SET value = ? WHERE key = 'name'", ("pBR322-mod",))
# 增删碱基必须整表重建 sequence_blocks:block_index 从 0 连续,块长 10000
new_seq = "ATG" + "GCT" * 100 # ...你的新序列
con.execute("DELETE FROM sequence_blocks")
con.executemany("INSERT INTO sequence_blocks VALUES (?, ?)",
[(i // 10000, new_seq[i:i + 10000])
for i in range(0, len(new_seq), 10000)])
con.commit()
注意:改动碱基长度后要同步修正受影响 feature 的坐标(GenePad 不会自动重算);isDirty 只是保存簿记,加载时不读取,外部改动无需维护它。用 Python 默认 journal 写出的文件即为自包含单文件。
常用配方——给带历史的 .gen 瘦身:历史快照可能占掉文件九成体积(见ITR 解剖的 36 MB 例子)。不需要克隆谱系时,外部清空两张历史表再 VACUUM 回收空间即可,GenePad 重新打开一切正常(无历史 = 普通文件):
import sqlite3
con = sqlite3.connect("ITR-CMV-AAV5-AfeI.gen")
con.execute("DELETE FROM history_tree") # 等价于应用内「清空历史」
con.execute("DELETE FROM history_snapshots")
con.commit()
con.execute("VACUUM") # 关键:真正把字节还给文件系统
con.close()
# 实测:37,810,176 B → 151,552 B(约 148 KB),序列与注释完好
Recipe
从零创建 .gen
最小可用文件只需要三张表——GenePad 打开时会自动补齐其余表和索引。控制解读方式的 project_meta 键如下:
| key | 推荐写入 | 缺省时读取结果 |
|---|---|---|
isCircular | 'true'(环形)或 'false'(线形) | 线形 |
isDoubleStranded | 'true'(双链)或 'false'(单链) | 双链 |
isDamMethylated | 'true' / 'false',不写则不设置 | 未知(undefined) |
isDcmMethylated | 'true' / 'false',不写则不设置 | 未知(undefined) |
import json, sqlite3
name, seq = "pDemo", "ATGGCTAGC" * 111 # 999 bp 示例序列
con = sqlite3.connect("demo.gen")
con.executescript("""
CREATE TABLE project_meta (key TEXT PRIMARY KEY, value TEXT);
CREATE TABLE sequence_blocks(block_index INTEGER PRIMARY KEY, bases TEXT NOT NULL);
CREATE TABLE features (
id TEXT PRIMARY KEY, name TEXT NOT NULL, type TEXT NOT NULL,
start_pos INTEGER NOT NULL, end_pos INTEGER NOT NULL, strand TEXT NOT NULL,
color TEXT, label TEXT, note TEXT, frame INTEGER, visible INTEGER DEFAULT 1,
segments TEXT, qualifiers TEXT);
""")
con.executemany("INSERT INTO project_meta VALUES (?, ?)", {
"name": name,
"isCircular": "true", # 见上表;isDoubleStranded 缺省即双链
"isDirty": "0",
}.items())
BLOCK = 10000 # 与 GenePad 的 BLOCK_SIZE 一致
con.executemany("INSERT INTO sequence_blocks VALUES (?, ?)",
[(i // BLOCK, seq[i:i + BLOCK])
for i in range(0, len(seq), BLOCK)])
con.execute("INSERT INTO features VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", (
"feat-1", "GFP", "CDS", 101, 804, "forward",
None, None, None, None, 1, None,
json.dumps({"gene": ["gfp"]}), # qualifiers:Record<string, string[]>
))
con.commit()
要点:feature 的 13 列一个都不能少(后几列写 NULL 即可);块长 10000 且 block_index 连续;布尔键用小写字符串。之后 GenePad(或任何读取程序)就能直接打开这个文件。
Quick Look
用 sqlite3 命令行看一眼
# 打开 .gen(本质就是 SQLite;可换成本页提供的示例文件)
sqlite3 example.gen
# 列出所有表 / 建表语句
.tables
.schema features
# 读取项目名 / 拓扑
SELECT key, value FROM project_meta WHERE key IN ('name','isCircular');
# 拼接完整序列(注意按 block_index 排序)
SELECT group_concat(bases, '') FROM (SELECT bases FROM sequence_blocks ORDER BY block_index);
# 查看前几个 feature(坐标 1-based inclusive)
SELECT name, type, start_pos, end_pos, strand FROM features ORDER BY start_pos LIMIT 5;
# 看绑定位点 / 编辑历史
SELECT primer_id, start_pos, end_pos, bound_strand FROM primer_binding_sites LIMIT 5;
SELECT timestamp, description FROM edit_history ORDER BY id DESC LIMIT 5;
# 编辑历史树:根节点元数据 + 各快照长度(详见「编辑历史树」与「实例解剖」两节)
SELECT json_extract(tree, '$.id'), json_extract(tree, '$.operation'),
json_extract(tree, '$.seqLen'), json_extract(tree, '$.circular')
FROM history_tree WHERE id = 1;
SELECT node_id, length(sequence), seq_type FROM history_snapshots ORDER BY node_id LIMIT 5;
# 解码主序列测序图谱的 A 通道(BLOB 里是 UTF-8 JSON 数组)
SELECT CAST(trace_a AS TEXT) FROM chromatogram_data WHERE id = 'primary';
# 顺手验证魔头:应为 "SQLite format 3" + \0
xxd -l 16 example.gen
只读场景请用 sqlite3 "file:example.gen?immutable=1" 打开——避免在文件旁产生 -wal/-shm 副产品(GenePad 自带的质粒库扫描器就是这么做的,Rust 示例见Rust 读取)。
See Also
延伸
- .gjson 文件定义——单文件 JSON 交换格式,适合脚本生成与轻量共享。
- .dna 转换——SnapGene
.dna的读取与转换说明。 - Rust 读取示例——rusqlite 依赖配置、只读(immutable)打开与 .gjson 读取。