|
|
@@ -1,5 +1,4 @@
|
|
1
|
|
-"""content_db.py — SQLite 内容数据库操作类"""
|
|
2
|
|
-
|
|
|
1
|
+"""SQLite 内容数据库操作类"""
|
|
3
|
2
|
import json
|
|
4
|
3
|
import sqlite3
|
|
5
|
4
|
from pathlib import Path
|
|
|
@@ -7,10 +6,7 @@ from typing import Optional
|
|
7
|
6
|
|
|
8
|
7
|
|
|
9
|
8
|
class ContentDB:
|
|
10
|
|
- """SQLite 内容数据库操作类
|
|
11
|
|
-
|
|
12
|
|
- 每个文档对应一个独立的 SQLite 数据库文件,存储文档的所有 Blocks。
|
|
13
|
|
- """
|
|
|
9
|
+ """SQLite 内容数据库操作类,每个文档对应一个独立的 SQLite 数据库文件"""
|
|
14
|
10
|
|
|
15
|
11
|
def __init__(self, db_path: str):
|
|
16
|
12
|
self.db_path = Path(db_path)
|
|
|
@@ -51,17 +47,9 @@ class ContentDB:
|
|
51
|
47
|
metadata TEXT DEFAULT '{}'
|
|
52
|
48
|
)
|
|
53
|
49
|
""")
|
|
54
|
|
-
|
|
55
|
|
- # 创建索引
|
|
56
|
|
- self.conn.execute(
|
|
57
|
|
- 'CREATE INDEX IF NOT EXISTS idx_block_order ON document_blocks(block_order)'
|
|
58
|
|
- )
|
|
59
|
|
- self.conn.execute(
|
|
60
|
|
- 'CREATE INDEX IF NOT EXISTS idx_type ON document_blocks(type)'
|
|
61
|
|
- )
|
|
62
|
|
- self.conn.execute(
|
|
63
|
|
- 'CREATE INDEX IF NOT EXISTS idx_level ON document_blocks(level)'
|
|
64
|
|
- )
|
|
|
50
|
+ self.conn.execute('CREATE INDEX IF NOT EXISTS idx_block_order ON document_blocks(block_order)')
|
|
|
51
|
+ self.conn.execute('CREATE INDEX IF NOT EXISTS idx_type ON document_blocks(type)')
|
|
|
52
|
+ self.conn.execute('CREATE INDEX IF NOT EXISTS idx_level ON document_blocks(level)')
|
|
65
|
53
|
self.conn.commit()
|
|
66
|
54
|
|
|
67
|
55
|
def insert_blocks(self, blocks: list[dict]):
|
|
|
@@ -186,198 +174,98 @@ class ContentDB:
|
|
186
|
174
|
'by_type': stats
|
|
187
|
175
|
}
|
|
188
|
176
|
|
|
189
|
|
- def calculate_index_for_insert(
|
|
190
|
|
- self,
|
|
191
|
|
- after_block_id: str,
|
|
192
|
|
- new_type: str,
|
|
193
|
|
- new_level: int = 0
|
|
|
177
|
+ def _build_type_filter(self, block_type: str, level: Optional[int] = None) -> tuple[str, list]:
|
|
|
178
|
+ """构建类型过滤条件(用于 index 查询)"""
|
|
|
179
|
+ if block_type == 'heading' and level is not None:
|
|
|
180
|
+ return "type = ? AND level = ?", [block_type, level]
|
|
|
181
|
+ return "type = ?", [block_type]
|
|
|
182
|
+
|
|
|
183
|
+ def _query_next_value(self, field: str, after_order: int, type_filter: str, params: list) -> Optional[int]:
|
|
|
184
|
+ """查询下一个值(index 或 block_order)"""
|
|
|
185
|
+ sql = f'SELECT "{field}" FROM document_blocks WHERE {type_filter} AND block_order > ? ORDER BY block_order LIMIT 1'
|
|
|
186
|
+ cursor = self.conn.execute(sql, params + [after_order])
|
|
|
187
|
+ row = cursor.fetchone()
|
|
|
188
|
+ return row[field] if row else None
|
|
|
189
|
+
|
|
|
190
|
+ def _query_prev_value(self, field: str, after_order: int, type_filter: str, params: list) -> Optional[int]:
|
|
|
191
|
+ """查询前一个值(index 或 block_order)"""
|
|
|
192
|
+ sql = f'SELECT "{field}" FROM document_blocks WHERE {type_filter} AND block_order <= ? ORDER BY block_order DESC LIMIT 1'
|
|
|
193
|
+ cursor = self.conn.execute(sql, params + [after_order])
|
|
|
194
|
+ row = cursor.fetchone()
|
|
|
195
|
+ return row[field] if row else None
|
|
|
196
|
+
|
|
|
197
|
+ def _query_max_value(self, field: str, type_filter: str, params: list) -> Optional[int]:
|
|
|
198
|
+ """查询最大值(index 或 block_order)"""
|
|
|
199
|
+ sql = f'SELECT MAX("{field}") as max_val FROM document_blocks'
|
|
|
200
|
+ if type_filter:
|
|
|
201
|
+ sql += f' WHERE {type_filter}'
|
|
|
202
|
+ cursor = self.conn.execute(sql, params)
|
|
|
203
|
+ row = cursor.fetchone()
|
|
|
204
|
+ return row['max_val']
|
|
|
205
|
+
|
|
|
206
|
+ def _calculate_sparse_value(
|
|
|
207
|
+ self,
|
|
|
208
|
+ field: str,
|
|
|
209
|
+ after_block_id: Optional[str],
|
|
|
210
|
+ type_filter: str,
|
|
|
211
|
+ params: list,
|
|
|
212
|
+ default_min: int,
|
|
|
213
|
+ rebalance_func: callable
|
|
194
|
214
|
) -> int:
|
|
195
|
|
- """计算插入 block 的 index(稀疏排序)
|
|
196
|
|
-
|
|
197
|
|
- Args:
|
|
198
|
|
- after_block_id: 在哪个 block 后插入(如果为 None 则插入到末尾)
|
|
199
|
|
- new_type: 新 block 的类型
|
|
200
|
|
- new_level: 新 block 的级别(标题有效)
|
|
201
|
|
-
|
|
202
|
|
- Returns:
|
|
203
|
|
- 计算得到的 index
|
|
204
|
|
- """
|
|
|
215
|
+ """通用稀疏值计算逻辑"""
|
|
205
|
216
|
if after_block_id:
|
|
206
|
|
- # 1. 获取 after_block 的 block_order
|
|
207
|
217
|
after_block = self.get_block_by_id(after_block_id)
|
|
208
|
218
|
if not after_block:
|
|
209
|
219
|
raise ValueError(f"Block not found: {after_block_id}")
|
|
210
|
220
|
after_order = after_block['block_order']
|
|
211
|
221
|
|
|
212
|
|
- # 2. 查找下一个同类型同级别的 block
|
|
213
|
|
- if new_type == 'heading':
|
|
214
|
|
- # 标题:按 level 查询
|
|
215
|
|
- cursor = self.conn.execute("""
|
|
216
|
|
- SELECT "index" FROM document_blocks
|
|
217
|
|
- WHERE type = ? AND level = ? AND block_order > ?
|
|
218
|
|
- ORDER BY block_order LIMIT 1
|
|
219
|
|
- """, (new_type, new_level, after_order))
|
|
220
|
|
- else:
|
|
221
|
|
- # 其他类型:只按 type 查询
|
|
222
|
|
- cursor = self.conn.execute("""
|
|
223
|
|
- SELECT "index" FROM document_blocks
|
|
224
|
|
- WHERE type = ? AND block_order > ?
|
|
225
|
|
- ORDER BY block_order LIMIT 1
|
|
226
|
|
- """, (new_type, after_order))
|
|
|
222
|
+ next_val = self._query_next_value(field, after_order, type_filter, params)
|
|
227
|
223
|
|
|
228
|
|
- next_row = cursor.fetchone()
|
|
229
|
|
-
|
|
230
|
|
- if next_row:
|
|
231
|
|
- next_index = next_row['index']
|
|
232
|
|
-
|
|
233
|
|
- # 3. 查找前一个同类型同级别的 block
|
|
234
|
|
- if new_type == 'heading':
|
|
235
|
|
- cursor = self.conn.execute("""
|
|
236
|
|
- SELECT "index" FROM document_blocks
|
|
237
|
|
- WHERE type = ? AND level = ? AND block_order <= ?
|
|
238
|
|
- ORDER BY block_order DESC LIMIT 1
|
|
239
|
|
- """, (new_type, new_level, after_order))
|
|
240
|
|
- else:
|
|
241
|
|
- cursor = self.conn.execute("""
|
|
242
|
|
- SELECT "index" FROM document_blocks
|
|
243
|
|
- WHERE type = ? AND block_order <= ?
|
|
244
|
|
- ORDER BY block_order DESC LIMIT 1
|
|
245
|
|
- """, (new_type, after_order))
|
|
246
|
|
-
|
|
247
|
|
- prev_row = cursor.fetchone()
|
|
248
|
|
- prev_index = prev_row['index'] if prev_row else -100
|
|
|
224
|
+ if next_val is not None:
|
|
|
225
|
+ prev_val = self._query_prev_value(field, after_order, type_filter, params)
|
|
|
226
|
+ if prev_val is None:
|
|
|
227
|
+ prev_val = default_min
|
|
249
|
228
|
|
|
250
|
|
- # 4. 计算中间值
|
|
251
|
|
- gap = next_index - prev_index
|
|
|
229
|
+ gap = next_val - prev_val
|
|
252
|
230
|
if gap <= 1:
|
|
253
|
|
- # 间隙不足,触发局部重排
|
|
254
|
|
- self._rebalance_indexes_between(
|
|
255
|
|
- new_type, new_level, prev_index, next_index
|
|
256
|
|
- )
|
|
257
|
|
- # 重新查询
|
|
258
|
|
- if new_type == 'heading':
|
|
259
|
|
- cursor = self.conn.execute("""
|
|
260
|
|
- SELECT "index" FROM document_blocks
|
|
261
|
|
- WHERE type = ? AND level = ? AND block_order > ?
|
|
262
|
|
- ORDER BY block_order LIMIT 1
|
|
263
|
|
- """, (new_type, new_level, after_order))
|
|
264
|
|
- else:
|
|
265
|
|
- cursor = self.conn.execute("""
|
|
266
|
|
- SELECT "index" FROM document_blocks
|
|
267
|
|
- WHERE type = ? AND block_order > ?
|
|
268
|
|
- ORDER BY block_order LIMIT 1
|
|
269
|
|
- """, (new_type, after_order))
|
|
270
|
|
- next_row = cursor.fetchone()
|
|
271
|
|
- next_index = next_row['index'] if next_row else prev_index + 200
|
|
|
231
|
+ rebalance_func(prev_val, next_val)
|
|
|
232
|
+ next_val = self._query_next_value(field, after_order, type_filter, params)
|
|
|
233
|
+ if next_val is None:
|
|
|
234
|
+ next_val = prev_val + 200
|
|
272
|
235
|
|
|
273
|
|
- new_index = (prev_index + next_index) // 2
|
|
|
236
|
+ return (prev_val + next_val) // 2
|
|
274
|
237
|
else:
|
|
275
|
|
- # 没有下一个同类 block,追加到最后
|
|
276
|
|
- if new_type == 'heading':
|
|
277
|
|
- cursor = self.conn.execute("""
|
|
278
|
|
- SELECT MAX("index") as max_index FROM document_blocks
|
|
279
|
|
- WHERE type = ? AND level = ?
|
|
280
|
|
- """, (new_type, new_level))
|
|
281
|
|
- else:
|
|
282
|
|
- cursor = self.conn.execute("""
|
|
283
|
|
- SELECT MAX("index") as max_index FROM document_blocks
|
|
284
|
|
- WHERE type = ?
|
|
285
|
|
- """, (new_type,))
|
|
286
|
|
-
|
|
287
|
|
- row = cursor.fetchone()
|
|
288
|
|
- max_index = row['max_index'] if row['max_index'] is not None else -100
|
|
289
|
|
- new_index = max_index + 100
|
|
|
238
|
+ max_val = self._query_max_value(field, type_filter, params)
|
|
|
239
|
+ return (max_val if max_val is not None else default_min) + 100
|
|
290
|
240
|
else:
|
|
291
|
|
- # 插入到文档末尾
|
|
292
|
|
- if new_type == 'heading':
|
|
293
|
|
- cursor = self.conn.execute("""
|
|
294
|
|
- SELECT MAX("index") as max_index FROM document_blocks
|
|
295
|
|
- WHERE type = ? AND level = ?
|
|
296
|
|
- """, (new_type, new_level))
|
|
297
|
|
- else:
|
|
298
|
|
- cursor = self.conn.execute("""
|
|
299
|
|
- SELECT MAX("index") as max_index FROM document_blocks
|
|
300
|
|
- WHERE type = ?
|
|
301
|
|
- """, (new_type,))
|
|
302
|
|
-
|
|
303
|
|
- row = cursor.fetchone()
|
|
304
|
|
- max_index = row['max_index'] if row['max_index'] is not None else -100
|
|
305
|
|
- new_index = max_index + 100
|
|
306
|
|
-
|
|
307
|
|
- return new_index
|
|
|
241
|
+ max_val = self._query_max_value(field, type_filter, params)
|
|
|
242
|
+ return (max_val if max_val is not None else default_min) + 100
|
|
|
243
|
+
|
|
|
244
|
+ def calculate_index_for_insert(self, after_block_id: str, new_type: str, new_level: int = 0) -> int:
|
|
|
245
|
+ """计算插入 block 的 index(稀疏排序)"""
|
|
|
246
|
+ type_filter, params = self._build_type_filter(new_type, new_level)
|
|
|
247
|
+ return self._calculate_sparse_value(
|
|
|
248
|
+ field='index',
|
|
|
249
|
+ after_block_id=after_block_id,
|
|
|
250
|
+ type_filter=type_filter,
|
|
|
251
|
+ params=params,
|
|
|
252
|
+ default_min=-100,
|
|
|
253
|
+ rebalance_func=lambda prev, next: self._rebalance_indexes_between(new_type, new_level, prev, next)
|
|
|
254
|
+ )
|
|
308
|
255
|
|
|
309
|
256
|
def calculate_block_order_for_insert(self, after_block_id: str = None) -> int:
|
|
310
|
|
- """计算插入 block 的 block_order(稀疏排序)
|
|
311
|
|
-
|
|
312
|
|
- Args:
|
|
313
|
|
- after_block_id: 在哪个 block 后插入(如果为 None 则插入到末尾)
|
|
314
|
|
-
|
|
315
|
|
- Returns:
|
|
316
|
|
- 计算得到的 block_order
|
|
317
|
|
- """
|
|
318
|
|
- if after_block_id:
|
|
319
|
|
- # 1. 获取 after_block 的 block_order
|
|
320
|
|
- after_block = self.get_block_by_id(after_block_id)
|
|
321
|
|
- if not after_block:
|
|
322
|
|
- raise ValueError(f"Block not found: {after_block_id}")
|
|
323
|
|
- after_order = after_block['block_order']
|
|
324
|
|
-
|
|
325
|
|
- # 2. 查询下一个 block 的 block_order
|
|
326
|
|
- cursor = self.conn.execute("""
|
|
327
|
|
- SELECT block_order FROM document_blocks
|
|
328
|
|
- WHERE block_order > ?
|
|
329
|
|
- ORDER BY block_order LIMIT 1
|
|
330
|
|
- """, (after_order,))
|
|
331
|
|
-
|
|
332
|
|
- next_row = cursor.fetchone()
|
|
333
|
|
-
|
|
334
|
|
- if next_row:
|
|
335
|
|
- next_order = next_row['block_order']
|
|
336
|
|
- gap = next_order - after_order
|
|
337
|
|
-
|
|
338
|
|
- # 3. 检查间隙是否足够
|
|
339
|
|
- if gap <= 1:
|
|
340
|
|
- # 触发局部重排
|
|
341
|
|
- self._rebalance_block_orders_between(after_order, next_order)
|
|
342
|
|
- # 重新查询
|
|
343
|
|
- cursor = self.conn.execute("""
|
|
344
|
|
- SELECT block_order FROM document_blocks
|
|
345
|
|
- WHERE block_order > ?
|
|
346
|
|
- ORDER BY block_order LIMIT 1
|
|
347
|
|
- """, (after_order,))
|
|
348
|
|
- next_row = cursor.fetchone()
|
|
349
|
|
- next_order = next_row['block_order'] if next_row else after_order + 200
|
|
350
|
|
-
|
|
351
|
|
- # 4. 计算中间值
|
|
352
|
|
- return (after_order + next_order) // 2
|
|
353
|
|
- else:
|
|
354
|
|
- # 没有下一个 block,插入到最后
|
|
355
|
|
- return after_order + 100
|
|
356
|
|
- else:
|
|
357
|
|
- # 插入到文档末尾
|
|
358
|
|
- cursor = self.conn.execute("""
|
|
359
|
|
- SELECT MAX(block_order) as max_order FROM document_blocks
|
|
360
|
|
- """)
|
|
361
|
|
- row = cursor.fetchone()
|
|
362
|
|
- max_order = row['max_order'] if row['max_order'] is not None else 0
|
|
363
|
|
- return max_order + 100
|
|
|
257
|
+ """计算插入 block 的 block_order(稀疏排序)"""
|
|
|
258
|
+ return self._calculate_sparse_value(
|
|
|
259
|
+ field='block_order',
|
|
|
260
|
+ after_block_id=after_block_id,
|
|
|
261
|
+ type_filter='1=1',
|
|
|
262
|
+ params=[],
|
|
|
263
|
+ default_min=0,
|
|
|
264
|
+ rebalance_func=self._rebalance_block_orders_between
|
|
|
265
|
+ )
|
|
364
|
266
|
|
|
365
|
|
- def _rebalance_indexes_between(
|
|
366
|
|
- self,
|
|
367
|
|
- block_type: str,
|
|
368
|
|
- level: int,
|
|
369
|
|
- start_index: int,
|
|
370
|
|
- end_index: int
|
|
371
|
|
- ):
|
|
372
|
|
- """局部重排:重新分配区间内同类型同级别 blocks 的 index
|
|
373
|
|
-
|
|
374
|
|
- Args:
|
|
375
|
|
- block_type: Block 类型
|
|
376
|
|
- level: 级别(标题有效)
|
|
377
|
|
- start_index: 起始 index
|
|
378
|
|
- end_index: 结束 index
|
|
379
|
|
- """
|
|
380
|
|
- # 查询区间内的所有同类型同级别 blocks
|
|
|
267
|
+ def _rebalance_indexes_between(self, block_type: str, level: int, start_index: int, end_index: int):
|
|
|
268
|
+ """局部重排:重新分配区间内同类型同级别 blocks 的 index"""
|
|
381
|
269
|
if block_type == 'heading':
|
|
382
|
270
|
cursor = self.conn.execute("""
|
|
383
|
271
|
SELECT id, "index"
|
|
|
@@ -398,12 +286,9 @@ class ContentDB:
|
|
398
|
286
|
if not blocks:
|
|
399
|
287
|
return
|
|
400
|
288
|
|
|
401
|
|
- # 计算新的间隔
|
|
402
|
289
|
count = len(blocks)
|
|
403
|
290
|
gap = end_index - start_index
|
|
404
|
291
|
step = gap // (count + 1)
|
|
405
|
|
-
|
|
406
|
|
- # 重新分配 index
|
|
407
|
292
|
new_index = start_index
|
|
408
|
293
|
for block in blocks:
|
|
409
|
294
|
new_index += step
|
|
|
@@ -415,13 +300,7 @@ class ContentDB:
|
|
415
|
300
|
self.conn.commit()
|
|
416
|
301
|
|
|
417
|
302
|
def _rebalance_block_orders_between(self, start_order: int, end_order: int):
|
|
418
|
|
- """局部重排:重新分配区间内所有 blocks 的 block_order
|
|
419
|
|
-
|
|
420
|
|
- Args:
|
|
421
|
|
- start_order: 起始 block_order
|
|
422
|
|
- end_order: 结束 block_order
|
|
423
|
|
- """
|
|
424
|
|
- # 查询区间内的所有 blocks
|
|
|
303
|
+ """局部重排:重新分配区间内所有 blocks 的 block_order"""
|
|
425
|
304
|
cursor = self.conn.execute("""
|
|
426
|
305
|
SELECT id, block_order
|
|
427
|
306
|
FROM document_blocks
|
|
|
@@ -434,12 +313,9 @@ class ContentDB:
|
|
434
|
313
|
if not blocks:
|
|
435
|
314
|
return
|
|
436
|
315
|
|
|
437
|
|
- # 计算新的间隔
|
|
438
|
316
|
count = len(blocks)
|
|
439
|
317
|
gap = end_order - start_order
|
|
440
|
318
|
step = gap // (count + 1)
|
|
441
|
|
-
|
|
442
|
|
- # 重新分配 block_order
|
|
443
|
319
|
new_order = start_order
|
|
444
|
320
|
for block in blocks:
|
|
445
|
321
|
new_order += step
|
|
|
@@ -453,8 +329,6 @@ class ContentDB:
|
|
453
|
329
|
def _row_to_dict(self, row) -> dict:
|
|
454
|
330
|
"""将 sqlite3.Row 转换为字典"""
|
|
455
|
331
|
d = dict(row)
|
|
456
|
|
-
|
|
457
|
|
- # 解析 JSON 字段
|
|
458
|
332
|
if d.get('style'):
|
|
459
|
333
|
try:
|
|
460
|
334
|
d['style'] = json.loads(d['style'])
|
|
|
@@ -470,28 +344,17 @@ class ContentDB:
|
|
470
|
344
|
d['metadata'] = {}
|
|
471
|
345
|
else:
|
|
472
|
346
|
d['metadata'] = {}
|
|
473
|
|
-
|
|
474
|
|
- # content 可能是 JSON(富文本或表格)
|
|
475
|
347
|
if d.get('content'):
|
|
476
|
348
|
try:
|
|
477
|
349
|
d['content'] = json.loads(d['content'])
|
|
478
|
350
|
except (json.JSONDecodeError, TypeError):
|
|
479
|
|
- pass # 保持原字符串
|
|
|
351
|
+ pass
|
|
480
|
352
|
|
|
481
|
353
|
return d
|
|
482
|
354
|
|
|
483
|
355
|
@staticmethod
|
|
484
|
356
|
def generate_block_id(block_type: str, level: int, index: int) -> str:
|
|
485
|
|
- """生成 Block ID
|
|
486
|
|
-
|
|
487
|
|
- Args:
|
|
488
|
|
- block_type: Block 类型
|
|
489
|
|
- level: 级别(标题有效)
|
|
490
|
|
- index: 序号
|
|
491
|
|
-
|
|
492
|
|
- Returns:
|
|
493
|
|
- 生成的 Block ID
|
|
494
|
|
- """
|
|
|
357
|
+ """生成 Block ID"""
|
|
495
|
358
|
if block_type == 'heading':
|
|
496
|
359
|
return f'block-h{level}-{index}'
|
|
497
|
360
|
elif block_type == 'paragraph':
|
|
|
@@ -503,4 +366,4 @@ class ContentDB:
|
|
503
|
366
|
elif block_type == 'toc':
|
|
504
|
367
|
return f'block-toc-{index}'
|
|
505
|
368
|
else:
|
|
506
|
|
- return f'block-{block_type}-{index}'
|
|
|
369
|
+ return f'block-{block_type}-{index}'
|