You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
abc/src/net/micode/notes/data/NotesDatabaseHelper.java

419 lines
19 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data; //1
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
/**
* 笔记数据库帮助类,负责数据库的创建、升级以及表结构管理
* 采用单例模式确保全局唯一数据库实例
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库元数据配置
private static final String DB_NAME = "note.db";// 数据库文件名
private static final int DB_VERSION = 4;// 当前数据库版本
// 表名常量接口
public interface TABLE {
public static final String NOTE = "note";// 主表:存储笔记元信息(标题、创建时间、父目录等)
public static final String DATA = "data";// 数据表:存储笔记具体内容及扩展数据
}
private static final String TAG = "NotesDatabaseHelper";// 日志标签
private static NotesDatabaseHelper mInstance; // 单例实例
// Note表建表语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +// 笔记唯一标识
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +// 所属文件夹ID树形结构
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +// 提醒时间(毫秒时间戳)
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +// 背景颜色标识
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +// 是否有附件0/1布尔值
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +// 最后修改时间
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 子项数量(用于文件夹)
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +// 内容摘要
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 笔记类型(普通笔记、文件夹等)
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +// 关联的小部件ID
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +// 小部件类型
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +// 同步标识
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +// 本地修改标记
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +// 原始父目录(用于同步)
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +// Google Task关联ID
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 数据版本号(乐观锁)
")";
// Data表建表语句
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +// 数据项唯一标识
DataColumns.MIME_TYPE + " TEXT NOT NULL," +// MIME类型文本、清单、媒体等
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +// 同步创建时间
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +// 同步修改时间
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +// 主要内容存储
DataColumns.DATA1 + " INTEGER," +// 扩展字段1类型相关数据
DataColumns.DATA2 + " INTEGER," +// 扩展字段2如清单项状态
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +// 扩展字段3如地理位置
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +// 扩展字段4如附件路径
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +// 扩展字段5保留字段
")";
// 数据表索引加速按笔记ID查询数据项
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
// 触发器:当更新笔记的父目录时,增加新文件夹的计数
// 场景示例把笔记A从文件夹X移动到文件夹Y时Y的计数+1
/**
* Increase folder's note count when move note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
// 触发器:当更新笔记的父目录时,减少原文件夹的计数
// 场景示例同上移动操作时X的计数-1需>0时生效
/**
* Decrease folder's note count when move note from folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
// 触发器:插入新笔记时增加所在文件夹的计数
/**
* Increase folder's note count when insert new note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
// 触发器:删除笔记时减少原文件夹的计数(防止负数)
/**
* Decrease folder's note count when delete note from the folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* 插入NOTE类型数据时自动更新笔记摘要
* 场景当在data表插入类型为普通笔记的数据时自动更新note表的snippet字段
*/
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +// 仅处理普通笔记类型
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +// 使用新插入的内容更新摘要
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +// 通过NOTE_ID关联
" END";
/**
* 更新NOTE类型数据时同步更新笔记摘要
* 场景:修改已有笔记内容时(如编辑文本),自动更新对应笔记摘要
*/
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +// 仅处理原类型为笔记的记录
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +// 同步最新内容到摘要
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* 删除NOTE类型数据时清空笔记摘要
* 场景:当删除笔记的正文数据时,自动清空对应笔记的摘要
*/
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +// 清空摘要内容
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
/**
* 笔记级联删除触发器
* 场景当删除note表的记录时自动删除关联的data表数据
* 作用:防止产生孤儿数据(没有对应笔记的数据项)
*/
/**
* Delete datas belong to note which has been deleted
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +// 根据被删除笔记ID清理数据
" END";
/**
* 文件夹级联删除触发器
* 场景:当删除文件夹时,自动删除其下所有笔记
* 注意会触发delete_data_on_delete触发器进行二次清理
*/
/**
* Delete notes belong to folder which has been deleted
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +// 删除所有父目录为被删文件夹的笔记
" END";
/**
* 文件夹移动至回收站操作触发器
* 场景:当文件夹被移动到回收站时,自动将其子项也移动到回收站
* 设计目的:保持文件夹结构的完整性
*/
/**
* Move notes belong to folder which has been moved to trash folder
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +// 检测是否移动到回收站
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +// 批量更新子项父目录为回收站
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 选择原父目录下的所有笔记
" END";
/**
* 初始化系统文件夹(首次创建数据库时调用)
* 插入预定义的系统文件夹:
* 1. 通话记录文件夹ID_CALL_RECORD_FOLDER
* 2. 根文件夹ID_ROOT_FOLDER
* 3. 临时文件夹ID_TEMPARAY_FOLDER
* 4. 回收站ID_TRASH_FOLER
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
// 插入通话记录文件夹
/**
* call record foler for call notes
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);// 标记为系统文件夹
db.insert(TABLE.NOTE, null, values);
// 插入根文件夹(默认笔记存储位置)
/**
* root folder which is default folder
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 插入临时文件夹(用于笔记移动操作时的暂存区)
/**
* temporary folder which is used for moving note
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 插入回收站(软删除数据存储位置)
/**
* create trash folder
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created");
}
private void reCreateDataTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
/**
* 数据库版本升级控制中心
* 升级策略:
* V1 → V2完全重建表结构破坏性升级
* V2 → V3添加Google Task支持 + 创建回收站
* V3 → V4添加版本控制字段
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
// V1到V2升级策略完全重建表结构
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3// 包含V2到V3的升级逻辑
oldVersion++;
}
// V2到V3升级策略非破坏性升级
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;// 需要重建触发器
oldVersion++;
}
// V3到V4升级策略添加版本字段
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 重建触发器(当表结构变化可能影响触发器时)
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 版本检查容错
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
/**
* 升级到V2版本破坏性升级
* 操作说明:
* 1. 删除旧表
* 2. 重建表结构
* 3. 初始化系统文件夹
*/
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db);
createDataTable(db);
}
/**
* 升级到V3版本功能增强
* 主要变更:
* 1. 移除过期的修改时间触发器
* 2. 新增GTASK_ID字段用于Google Tasks同步
* 3. 初始化回收站文件夹
*/
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers// 移除旧版的时间更新触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id// 添加Google Task关联字段
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder// 插入回收站系统文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
* 升级到V4版本数据版本控制
* 新增字段VERSION用于实现乐观锁机制
*/
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}