|
|
/*
|
|
|
* 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;
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 笔记数据库帮助类
|
|
|
*
|
|
|
* 功能概述:
|
|
|
* 1. 继承SQLiteOpenHelper,管理SQLite数据库的创建和升级
|
|
|
* 2. 定义note表和data表的SQL创建语句
|
|
|
* 3. 定义数据库触发器,实现自动化数据维护
|
|
|
* 4. 管理数据库版本升级逻辑
|
|
|
*
|
|
|
* 数据库设计:
|
|
|
* - note表:存储笔记基本信息(ID、类型、时间、摘要等)
|
|
|
* - data表:存储笔记具体内容(文本、通话记录等)
|
|
|
* - 触发器:自动维护文件夹计数、笔记摘要、级联删除等
|
|
|
*
|
|
|
* 单例模式:
|
|
|
* - 使用getInstance()获取唯一实例
|
|
|
* - 确保整个应用中只有一个数据库连接
|
|
|
*/
|
|
|
public class NotesDatabaseHelper extends SQLiteOpenHelper {
|
|
|
// 数据库文件名
|
|
|
private static final String DB_NAME = "note.db";
|
|
|
|
|
|
// 数据库版本号,当前为版本5
|
|
|
// 版本历史:
|
|
|
// V1: 初始版本
|
|
|
// V2: 重建表结构
|
|
|
// V3: 添加Google Task支持和回收站
|
|
|
// V4: 添加版本号字段
|
|
|
// V5: 当前版本
|
|
|
private static final int DB_VERSION = 5;
|
|
|
|
|
|
/**
|
|
|
* 表名常量接口
|
|
|
* 定义数据库中的表名
|
|
|
*/
|
|
|
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;
|
|
|
|
|
|
/**
|
|
|
* 创建笔记表的SQL语句
|
|
|
*
|
|
|
* note表结构说明:
|
|
|
* - ID: 主键,自增
|
|
|
* - PARENT_ID: 父文件夹ID,0=根文件夹,负数=系统文件夹
|
|
|
* - ALERTED_DATE: 提醒时间,0=无提醒
|
|
|
* - BG_COLOR_ID: 背景颜色ID
|
|
|
* - CREATED_DATE: 创建时间,默认当前时间
|
|
|
* - HAS_ATTACHMENT: 是否有附件,0=无,1=有
|
|
|
* - MODIFIED_DATE: 修改时间,默认当前时间
|
|
|
* - NOTES_COUNT: 笔记数量(仅文件夹有效)
|
|
|
* - SNIPPET: 内容摘要
|
|
|
* - TYPE: 类型,0=笔记,1=文件夹,2=系统文件夹
|
|
|
* - WIDGET_ID: 桌面Widget ID
|
|
|
* - WIDGET_TYPE: Widget类型,-1=无效,0=2x2,1=4x4
|
|
|
* - SYNC_ID: Google同步ID
|
|
|
* - LOCAL_MODIFIED: 本地修改标记
|
|
|
* - ORIGIN_PARENT_ID: 原始父文件夹ID
|
|
|
* - GTASK_ID: Google Task ID
|
|
|
* - VERSION: 版本号
|
|
|
*/
|
|
|
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," +
|
|
|
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," +
|
|
|
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," +
|
|
|
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 ''," +
|
|
|
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
|
|
|
")";
|
|
|
|
|
|
/**
|
|
|
* 创建数据表的SQL语句
|
|
|
*
|
|
|
* data表结构说明:
|
|
|
* - ID: 主键,自增
|
|
|
* - MIME_TYPE: MIME类型,区分数据类型(text_note、call_note)
|
|
|
* - NOTE_ID: 所属笔记ID,外键关联note表
|
|
|
* - CREATED_DATE: 创建时间
|
|
|
* - MODIFIED_DATE: 修改时间
|
|
|
* - CONTENT: 内容文本
|
|
|
* - DATA1-DATA5: 扩展字段,不同类型有不同用途
|
|
|
*
|
|
|
* 采用EAV模型设计:
|
|
|
* - 一条笔记可以有多个data记录
|
|
|
* - 通过MIME_TYPE区分数据类型
|
|
|
* - 通过NOTE_ID关联到note表
|
|
|
*/
|
|
|
private static final String CREATE_DATA_TABLE_SQL =
|
|
|
"CREATE TABLE " + TABLE.DATA + "(" +
|
|
|
DataColumns.ID + " INTEGER PRIMARY KEY," +
|
|
|
DataColumns.MIME_TYPE + " TEXT NOT NULL," +
|
|
|
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
|
|
|
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," +
|
|
|
DataColumns.DATA2 + " INTEGER," +
|
|
|
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
|
|
|
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
|
|
|
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
|
|
|
")";
|
|
|
|
|
|
/**
|
|
|
* 创建数据表索引的SQL语句
|
|
|
*
|
|
|
* 索引说明:
|
|
|
* - 在data表的NOTE_ID字段上创建索引
|
|
|
* - 加速根据笔记ID查询数据记录的操作
|
|
|
* - 提高note-data关联查询的性能
|
|
|
*/
|
|
|
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
|
|
|
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
|
|
|
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
|
|
|
|
|
|
/**
|
|
|
* 触发器:更新笔记父文件夹时,增加目标文件夹的笔记计数
|
|
|
*
|
|
|
* 触发时机:当note表的PARENT_ID字段被更新后
|
|
|
* 触发条件:无(每次PARENT_ID更新都触发)
|
|
|
* 执行动作:将新父文件夹的NOTES_COUNT加1
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 将笔记从一个文件夹移动到另一个文件夹
|
|
|
* - 自动维护文件夹中的笔记数量
|
|
|
*/
|
|
|
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";
|
|
|
|
|
|
/**
|
|
|
* 触发器:更新笔记父文件夹时,减少原文件夹的笔记计数
|
|
|
*
|
|
|
* 触发时机:当note表的PARENT_ID字段被更新后
|
|
|
* 触发条件:无(每次PARENT_ID更新都触发)
|
|
|
* 执行动作:将原父文件夹的NOTES_COUNT减1(如果大于0)
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 使用old.PARENT_ID获取原父文件夹ID
|
|
|
* - 检查NOTES_COUNT>0防止出现负数
|
|
|
*/
|
|
|
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";
|
|
|
|
|
|
/**
|
|
|
* 触发器:插入新笔记时,增加父文件夹的笔记计数
|
|
|
*
|
|
|
* 触发时机:向note表插入新记录后
|
|
|
* 触发条件:无(每次插入都触发)
|
|
|
* 执行动作:将父文件夹的NOTES_COUNT加1
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 创建新笔记
|
|
|
* - 自动维护文件夹中的笔记数量
|
|
|
*/
|
|
|
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";
|
|
|
|
|
|
/**
|
|
|
* 触发器:删除笔记时,减少父文件夹的笔记计数
|
|
|
*
|
|
|
* 触发时机:从note表删除记录后
|
|
|
* 触发条件:无(每次删除都触发)
|
|
|
* 执行动作:将父文件夹的NOTES_COUNT减1(如果大于0)
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 使用old.PARENT_ID获取父文件夹ID
|
|
|
* - 检查NOTES_COUNT>0防止出现负数
|
|
|
*/
|
|
|
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";
|
|
|
|
|
|
/**
|
|
|
* 触发器:插入数据时,更新笔记的内容摘要
|
|
|
*
|
|
|
* 触发时机:向data表插入新记录后
|
|
|
* 触发条件:新记录的MIME_TYPE为普通笔记(DataConstants.NOTE)
|
|
|
* 执行动作:将note表的SNIPPET字段更新为新记录的内容
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 创建新笔记内容
|
|
|
* - 自动更新笔记列表显示的摘要
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 只处理普通文本笔记,不处理通话记录
|
|
|
* - 摘要用于在笔记列表中快速预览内容
|
|
|
*/
|
|
|
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 + ";" +
|
|
|
" END";
|
|
|
|
|
|
/**
|
|
|
* 触发器:更新数据时,更新笔记的内容摘要
|
|
|
*
|
|
|
* 触发时机:更新data表记录后
|
|
|
* 触发条件:旧记录的MIME_TYPE为普通笔记(DataConstants.NOTE)
|
|
|
* 执行动作:将note表的SNIPPET字段更新为新记录的内容
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 编辑笔记内容
|
|
|
* - 自动更新笔记列表显示的摘要
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 检查旧记录的MIME_TYPE,确保是普通笔记
|
|
|
* - 保持摘要与内容同步
|
|
|
*/
|
|
|
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";
|
|
|
|
|
|
/**
|
|
|
* 触发器:删除数据时,清空笔记的内容摘要
|
|
|
*
|
|
|
* 触发时机:从data表删除记录后
|
|
|
* 触发条件:旧记录的MIME_TYPE为普通笔记(DataConstants.NOTE)
|
|
|
* 执行动作:将note表的SNIPPET字段设置为空字符串
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 删除笔记内容
|
|
|
* - 清空笔记列表显示的摘要
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 摘要设置为空字符串而非NULL
|
|
|
* - 保持数据库一致性
|
|
|
*/
|
|
|
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表中所有NOTE_ID等于被删除笔记ID的记录
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 删除笔记
|
|
|
* - 自动清理关联的数据记录,防止数据孤岛
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 这是级联删除操作,确保数据完整性
|
|
|
* - 删除笔记后,其所有内容也会被删除
|
|
|
*/
|
|
|
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 + ";" +
|
|
|
" END";
|
|
|
|
|
|
/**
|
|
|
* 触发器:删除文件夹时,级联删除文件夹内的所有笔记
|
|
|
*
|
|
|
* 触发时机:从note表删除记录后
|
|
|
* 触发条件:无(每次删除都触发)
|
|
|
* 执行动作:删除note表中所有PARENT_ID等于被删除文件夹ID的记录
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 删除文件夹
|
|
|
* - 自动删除文件夹内的所有笔记
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 这是级联删除操作,删除文件夹会删除其下所有笔记
|
|
|
* - 用户删除文件夹前应给予警告
|
|
|
* - 递归删除:如果子笔记是文件夹,也会触发此触发器
|
|
|
*/
|
|
|
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";
|
|
|
|
|
|
/**
|
|
|
* 触发器:移动笔记到回收站时,级联移动子笔记
|
|
|
*
|
|
|
* 触发时机:更新note表记录后
|
|
|
* 触发条件:新记录的PARENT_ID等于回收站文件夹ID(Notes.ID_TRASH_FOLER)
|
|
|
* 执行动作:将所有PARENT_ID等于被移动笔记ID的记录也移动到回收站
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 将文件夹移动到回收站
|
|
|
* - 自动将文件夹内的所有笔记也移动到回收站
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 保持文件夹结构的完整性
|
|
|
* - 防止出现孤儿笔记(父文件夹已删除但子笔记未删除)
|
|
|
* - 递归移动:如果子笔记是文件夹,也会触发此触发器
|
|
|
*/
|
|
|
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";
|
|
|
|
|
|
/**
|
|
|
* 构造方法
|
|
|
*
|
|
|
* 调用父类SQLiteOpenHelper的构造方法:
|
|
|
* - context: 上下文环境
|
|
|
* - DB_NAME: 数据库文件名(note.db)
|
|
|
* - factory: Cursor工厂(null表示使用默认工厂)
|
|
|
* - DB_VERSION: 数据库版本号(5)
|
|
|
*
|
|
|
* @param context 上下文环境,用于访问应用文件和数据库
|
|
|
*/
|
|
|
public NotesDatabaseHelper(Context context) {
|
|
|
super(context, DB_NAME, null, DB_VERSION);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建笔记表
|
|
|
*
|
|
|
* 执行步骤:
|
|
|
* 1. 执行CREATE_NOTE_TABLE_SQL创建note表
|
|
|
* 2. 重新创建note表相关触发器
|
|
|
* 3. 创建系统文件夹(根文件夹、临时文件夹等)
|
|
|
*
|
|
|
* 该方法在数据库首次创建时调用(onCreate方法中)
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象,用于执行SQL语句
|
|
|
*/
|
|
|
public void createNoteTable(SQLiteDatabase db) {
|
|
|
db.execSQL(CREATE_NOTE_TABLE_SQL);
|
|
|
reCreateNoteTableTriggers(db);
|
|
|
createSystemFolder(db);
|
|
|
Log.d(TAG, "note table has been created");
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 重新创建笔记表触发器
|
|
|
*
|
|
|
* 执行步骤:
|
|
|
* 1. 删除所有已存在的note表触发器(如果存在)
|
|
|
* 2. 重新创建所有note表触发器
|
|
|
*
|
|
|
* 触发器列表:
|
|
|
* - increase_folder_count_on_update: 更新文件夹时增加计数
|
|
|
* - decrease_folder_count_on_update: 更新文件夹时减少计数
|
|
|
* - decrease_folder_count_on_delete: 删除笔记时减少计数
|
|
|
* - delete_data_on_delete: 删除笔记时级联删除数据
|
|
|
* - increase_folder_count_on_insert: 插入笔记时增加计数
|
|
|
* - folder_delete_notes_on_delete: 删除文件夹时级联删除笔记
|
|
|
* - folder_move_notes_on_trash: 移动到回收站时级联移动
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 数据库升级后重新创建触发器
|
|
|
* - 确保触发器定义与当前代码一致
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象,用于执行SQL语句
|
|
|
*/
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建系统文件夹
|
|
|
*
|
|
|
* 创建的系统文件夹:
|
|
|
* 1. ID_CALL_RECORD_FOLDER(-2): 通话记录文件夹
|
|
|
* 2. ID_ROOT_FOLDER(0): 根文件夹
|
|
|
* 3. ID_TEMPARAY_FOLDER(-1): 临时文件夹
|
|
|
* 4. ID_TRASH_FOLER(-3): 回收站文件夹
|
|
|
*
|
|
|
* 所有系统文件夹的TYPE字段都设置为TYPE_SYSTEM(2)
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 数据库首次创建时调用
|
|
|
* - 确保系统文件夹存在
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象,用于执行SQL语句
|
|
|
*/
|
|
|
private void createSystemFolder(SQLiteDatabase db) {
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
|
// 创建通话记录文件夹
|
|
|
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
|
|
|
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
|
|
db.insert(TABLE.NOTE, null, values);
|
|
|
|
|
|
// 创建根文件夹
|
|
|
values.clear();
|
|
|
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
|
|
|
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
|
|
db.insert(TABLE.NOTE, null, values);
|
|
|
|
|
|
// 创建临时文件夹
|
|
|
values.clear();
|
|
|
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
|
|
|
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
|
|
db.insert(TABLE.NOTE, null, values);
|
|
|
|
|
|
// 创建回收站文件夹
|
|
|
values.clear();
|
|
|
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
|
|
|
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
|
|
|
db.insert(TABLE.NOTE, null, values);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建数据表
|
|
|
*
|
|
|
* 执行步骤:
|
|
|
* 1. 执行CREATE_DATA_TABLE_SQL创建data表
|
|
|
* 2. 重新创建data表相关触发器
|
|
|
* 3. 创建data表索引(note_id_index)
|
|
|
*
|
|
|
* 该方法在数据库首次创建时调用(onCreate方法中)
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象,用于执行SQL语句
|
|
|
*/
|
|
|
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");
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 重新创建数据表触发器
|
|
|
*
|
|
|
* 执行步骤:
|
|
|
* 1. 删除所有已存在的data表触发器(如果存在)
|
|
|
* 2. 重新创建所有data表触发器
|
|
|
*
|
|
|
* 触发器列表:
|
|
|
* - update_note_content_on_insert: 插入数据时更新摘要
|
|
|
* - update_note_content_on_update: 更新数据时更新摘要
|
|
|
* - update_note_content_on_delete: 删除数据时清空摘要
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 数据库升级后重新创建触发器
|
|
|
* - 确保触发器定义与当前代码一致
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象,用于执行SQL语句
|
|
|
*/
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取单例实例
|
|
|
*
|
|
|
* 使用双重检查锁定模式实现线程安全的单例:
|
|
|
* 1. 检查mInstance是否为null
|
|
|
* 2. 如果为null,创建新实例
|
|
|
* 3. 返回唯一实例
|
|
|
*
|
|
|
* 优点:
|
|
|
* - 确保全局只有一个数据库连接
|
|
|
* - 节省系统资源
|
|
|
* - 避免多线程并发创建多个实例
|
|
|
*
|
|
|
* 使用场景:
|
|
|
* - 整个应用中获取数据库帮助类实例
|
|
|
* - NotesProvider中初始化数据库连接
|
|
|
*
|
|
|
* @param context 上下文环境
|
|
|
* @return NotesDatabaseHelper的唯一实例
|
|
|
*/
|
|
|
static synchronized NotesDatabaseHelper getInstance(Context context) {
|
|
|
if (mInstance == null) {
|
|
|
mInstance = new NotesDatabaseHelper(context);
|
|
|
}
|
|
|
return mInstance;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 数据库首次创建时调用
|
|
|
*
|
|
|
* 执行步骤:
|
|
|
* 1. 创建note表
|
|
|
* 2. 创建data表
|
|
|
*
|
|
|
* 该方法由SQLiteOpenHelper自动调用:
|
|
|
* - 当数据库文件不存在时
|
|
|
* - 当数据库版本号与代码中定义的版本号一致时
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象,表示新创建的数据库
|
|
|
*/
|
|
|
@Override
|
|
|
public void onCreate(SQLiteDatabase db) {
|
|
|
createNoteTable(db);
|
|
|
createDataTable(db);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 数据库升级处理
|
|
|
*
|
|
|
* 升级策略:
|
|
|
* - 根据旧版本号逐步升级到新版本
|
|
|
* - 每个版本都有对应的升级方法
|
|
|
* - 升级过程中可能需要重建触发器
|
|
|
*
|
|
|
* 版本升级流程:
|
|
|
* V1 -> V2: 重建表结构(upgradeToV2)
|
|
|
* V2 -> V3: 添加Google Task支持和回收站(upgradeToV3)
|
|
|
* V3 -> V4: 添加版本号字段(upgradeToV4)
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 升级过程中不删除用户数据
|
|
|
* - 升级失败会抛出IllegalStateException
|
|
|
* - 某些版本升级后需要重建触发器
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象
|
|
|
* @param oldVersion 旧版本号
|
|
|
* @param newVersion 新版本号
|
|
|
*/
|
|
|
@Override
|
|
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
|
boolean reCreateTriggers = false;
|
|
|
boolean skipV2 = false;
|
|
|
|
|
|
// 从版本1升级到版本2
|
|
|
if (oldVersion == 1) {
|
|
|
upgradeToV2(db);
|
|
|
skipV2 = true; // V2升级已在V1升级中完成,跳过V2
|
|
|
oldVersion++;
|
|
|
}
|
|
|
|
|
|
// 从版本2升级到版本3
|
|
|
if (oldVersion == 2 && !skipV2) {
|
|
|
upgradeToV3(db);
|
|
|
reCreateTriggers = true; // V3升级需要重建触发器
|
|
|
oldVersion++;
|
|
|
}
|
|
|
|
|
|
// 从版本3升级到版本4
|
|
|
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");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 升级到版本2 - 重建表结构
|
|
|
*
|
|
|
* 升级内容:
|
|
|
* 1. 删除旧的note表和data表
|
|
|
* 2. 重新创建note表和data表
|
|
|
* 3. 重新创建所有触发器
|
|
|
* 4. 重新创建系统文件夹
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 这是破坏性升级,会丢失所有数据
|
|
|
* - 仅在版本1时使用(初始版本可能存在设计缺陷)
|
|
|
* - 后续版本升级不会执行此方法
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象
|
|
|
*/
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 升级到版本3 - 添加Google Task支持和回收站
|
|
|
*
|
|
|
* 升级内容:
|
|
|
* 1. 删除旧的修改日期触发器(已废弃)
|
|
|
* 2. 在note表中添加GTASK_ID字段
|
|
|
* 3. 创建回收站文件夹(ID_TRASH_FOLER)
|
|
|
*
|
|
|
* 新功能:
|
|
|
* - Google Task同步:通过GTASK_ID字段与Google Tasks同步
|
|
|
* - 回收站功能:删除的笔记先放入回收站,而非直接删除
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - 升级后需要重建触发器(reCreateTriggers=true)
|
|
|
* - GTASK_ID字段默认值为空字符串
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象
|
|
|
*/
|
|
|
private void upgradeToV3(SQLiteDatabase db) {
|
|
|
// 删除旧的触发器(已废弃)
|
|
|
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");
|
|
|
|
|
|
// 添加Google Task ID字段
|
|
|
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
|
|
|
+ " TEXT NOT NULL DEFAULT ''");
|
|
|
|
|
|
// 创建回收站文件夹
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 升级到版本4 - 添加版本号字段
|
|
|
*
|
|
|
* 升级内容:
|
|
|
* 1. 在note表中添加VERSION字段
|
|
|
*
|
|
|
* 新功能:
|
|
|
* - 版本控制:通过VERSION字段跟踪笔记的修改版本
|
|
|
* - 冲突检测:在同步时通过版本号检测冲突
|
|
|
* - 并发控制:防止多人同时修改同一笔记
|
|
|
*
|
|
|
* 注意事项:
|
|
|
* - VERSION字段默认值为0
|
|
|
* - 每次修改笔记时,VERSION字段会自动加1(通过increaseNoteVersion方法)
|
|
|
*
|
|
|
* @param db SQLiteDatabase对象
|
|
|
*/
|
|
|
private void upgradeToV4(SQLiteDatabase db) {
|
|
|
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
|
|
|
+ " INTEGER NOT NULL DEFAULT 0");
|
|
|
}
|
|
|
}
|