This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,3 @@
{
"deploymentTarget": "12"
}
@@ -0,0 +1,358 @@
import {SQLiteConfig,SQLiteResult,SQLiteExecuteBatchParams,SQLiteDbFileInfo} from '../interface.uts';
import { URL, URLResourceKey } from 'Foundation';
import { Attribute } from 'UIKit';
/**
* SQLite数据库操作类
* 提供SQLite数据库的创建、查询、更新等基本操作,支持加密和事务处理
*/
export class xSqlite {
/** 数据库配置信息 */
config : SQLiteConfig = {
password : null,
encryption : false,
locateFile:null
};
private db : xSqliteHelp|null = null;
constructor(config ?: SQLiteConfig ) {
if(config!=null){
let realCofing:SQLiteConfig = config!;
let directory:null|string = realCofing.defaultDirectory
if(typeof directory == 'string' && directory !=null){
directory = UTSiOS.convert2AbsFullPath(directory!)
}
realCofing.defaultDirectory = directory
this.config = realCofing;
}
this.db = new xSqliteHelp(
config = SQLiteConfigBySt(
password = this.config.password,
encryption = this.config.encryption,
defaultDirectory = this.config.defaultDirectory,
)
)
}
private converFormat(result?:SQLiteResultBySt):SQLiteResult{
if(result==null){
return {
rows: [] ,
columns: [] ,
changes: null,
lastInsertRowid: null,
error: null,
};
}
return {
rows: (result?.rows==null?[]:result?.rows),
columns: (result?.columns==null?[]:result?.columns),
changes: (result?.changes==null?null:result?.changes),
maps: (result?.maps==null?null:result?.maps),
lastInsertRowid: (result?.lastInsertRowid==null?null:result?.lastInsertRowid),
error: (result?.error==null?null:result?.error),
};
}
/**
* 创建数据库
* @param fileName 数据库文件名,如果为null则使用默认名称'sqlite'
* @returns 创建成功返回true,失败返回false
*/
createDb(fileName:string|null = null) : boolean{
if (this.db==null){
return false;
}
this.db!.createDb(filename = fileName)
return true
}
/**
* 执行SQL语句
* @param sql SQL语句
* @param params SQL参数数组
* @returns SQLiteResult 执行结果,包含changes(影响行数)和lastInsertRowid(最后插入行ID)
*/
run(sql: string, params: any[] = []): SQLiteResult {
try {
if (this.db==null) throw new Error('Database not initialized');
const result = this.db!.run(sql, params = params);
return this.converFormat(result)
} catch (error) {
return { error: error.message };
}
}
/**
* 查询数据
* @param sql 查询SQL语句
* @param params 查询参数数组
* @returns SQLiteResult 查询结果,包含rows(数据行)和columns(列名)
*/
query(sql: string, params: Array<any> = [] as any[]): SQLiteResult {
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const result = this.db!.query(sql, params = params);
return this.converFormat(result)
}
/**
* 插入数据
* @param table 表名
* @param data 要插入的数据对象
* @returns SQLiteResult 插入结果
*/
insert(table: string, data: UTSJSONObject): SQLiteResult {
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const dataMap = new Map<string,any>();
for(const key in data){
const item = data.getAny(key);
dataMap.set(key,item)
}
const result = this.db!.insert(table = table, data = dataMap);
return this.converFormat(result)
}
/**
* 更新数据
* @param table 表名
* @param data 要更新的数据对象
* @param whereName WHERE条件语句
* @param params WHERE条件参数数组
* @returns SQLiteResult 更新结果
*/
update(table: string, data: UTSJSONObject, whereName: string, params: any[] = []): SQLiteResult {
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const dataMap = new Map<string,any>();
for(const key in data){
const item = data.getAny(key);
dataMap.set(key,item)
}
const result = this.db!.update(table = table, data = dataMap, where = whereName, params = params);
return this.converFormat(result)
}
/**
* 删除数据
* @param table 表名
* @param whereName WHERE条件语句
* @param params WHERE条件参数数组
* @returns SQLiteResult 删除结果
*/
delete(table: string, whereName: string, params: any[] = []): SQLiteResult {
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const result = this.db!.delete(table = table, where = whereName ,params = params);
return this.converFormat(result)
}
/**
* 检查表是否存在
* @param tableName 表名
* @returns 表存在返回true,不存在返回false
*/
tableExists(tableName: string): boolean {
if (this.db==null){
console.error('Database not initialized')
return false;
}
return this.db!.tableExists(tableName = tableName);
}
/**
* 创建数据表
* @param tableName 表名
* @param columns 列定义对象,key为列名,value为列类型定义
* @returns SQLiteResult 创建结果
*/
createTable(tableName: string, columns: UTSJSONObject): SQLiteResult {
const dataMap = new Map<string,string>();
for(const key in columns){
const item = columns.getString(key)!;
dataMap.set(key,item)
}
console.log('createTabled:',this.db)
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
try {
const result = this.db!.createTable(tableName = tableName, columns = dataMap);
return this.converFormat(result)
} catch (error) {
console.error(error)
return { error: error.message };
}
}
/**
* 删除数据表
* @param tableName 要删除的表名
* @returns SQLiteResult 删除结果
*/
dropTable(tableName: string): SQLiteResult {
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const result = this.db!.dropTable(tableName = tableName);
return this.converFormat(result)
}
/**
* 关闭数据库连接
* 释放数据库资源,关闭后需要重新创建才能使用
*/
close(): void {
if (this.db!=null) {
this.db!.close();
this.db = null;
}
}
/**
* 开始事务
* 开始一个新的事务,在提交或回滚之前,所有操作都在事务内
* @returns SQLiteResult 事务开始结果
*/
beginTransaction(): SQLiteResult{
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const result = this.db!.beginTransaction();
return this.converFormat(result)
}
/**
* 提交事务
* 提交当前事务的所有操作
* @returns SQLiteResult 事务提交结果
*/
commit(): SQLiteResult {
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const result = this.db!.commit();
return this.converFormat(result)
}
/**
* 回滚事务
* 撤销当前事务中的所有操作
* @returns SQLiteResult 事务回滚结果
*/
rollback(): SQLiteResult {
if (this.db==null){
console.error('Database not initialized')
return { error:"Database not initialized" };
}
const result = this.db!.rollback();
return this.converFormat(result)
}
/**
* 批量执行SQL语句
* @param statements SQL语句数组,每个元素包含sql和params
* @returns SQLiteResult[] 每个语句的执行结果数组
*/
executeBatch(statements: SQLiteExecuteBatchParams[]): SQLiteResult[] {
if (this.db==null){
console.error('Database not initialized')
return [];
}
const list = new Array<SQLiteExecuteBatchParamsBySt>()
for(const item in statements){
list.push(SQLiteExecuteBatchParamsBySt(
sql = item.sql,
params = item.params
))
}
const result = this.db!.executeBatch(statements = list);
let resultList = [] as SQLiteResult[]
for(const item in result){
resultList.push(this.converFormat(item))
}
return resultList
}
/**
* 保存数据库到本地文件
* @param filename 保存的文件名,可选,默认为'sqlite'
* @returns SQLiteResult 保存结果
*/
saveLocal(filename?: string): SQLiteResult {
if (this.db==null){
return {error:'Database not initialized'};
}
const result = this.db!.saveLocal(filename = filename);
return this.converFormat(result)
}
/**
* 从本地文件加载数据库
* @param filename 要加载的数据库文件名
* @returns SQLiteResult 加载结果
*/
loadLocal(filename:string): SQLiteResult{
const result = this.db!.loadLocal(filename = filename);
return this.converFormat(result)
}
/**
* 获取数据库文件路径获取前会备份到缓存目录
*/
getDatabasePath():string|null{
if (this.db==null){
console.error('Database not initialized')
return null
}
const result = this.db!.getDatabasePath();
return result
}
/**
* 当前数据库的目录
*/
setDefaultDirectory(directory: string){
if (this.db==null){
console.error('Database not initialized')
return
}
let rdirectory = directory;
if(rdirectory!=''){
rdirectory = UTSiOS.convert2AbsFullPath(directory)
}
this.db!.setDefaultDirectory(directory = rdirectory);
}
/**
* 设置数据库密码
* 空值或者null即删除密码
*/
setPassword(password: string|null = null){
if (this.db==null){
console.error('Database not initialized')
return
}
this.db!.setPassword(password);
}
}
@@ -0,0 +1,522 @@
import Foundation
import SQLite3
// UTS
import DCloudUTSFoundation
struct SQLiteConfigBySt {
var password: String?
var encryption: Bool
var locateFile: ((String) -> String)?
var defaultDirectory: String?
init(password: String? = nil, encryption: Bool = false, locateFile: ((String) -> String)? = nil, defaultDirectory: String? = nil) {
self.password = password
self.encryption = encryption
self.locateFile = locateFile
self.defaultDirectory = defaultDirectory
}
}
struct SQLiteResultBySt {
var rows: [[Any]]?
var columns: [String]?
var changes: Int?
var lastInsertRowid: Int64?
var error: String?
var maps: [[String: Any]]?
}
struct SQLiteExecuteBatchParamsBySt {
var sql: String
var params: [Any]?
}
class xSqliteHelp {
private var db: OpaquePointer?
private var isInTransaction: Bool = false
private var config: SQLiteConfigBySt
private var fileURLPath: URL?
init(config: SQLiteConfigBySt = SQLiteConfigBySt()) {
self.config = config
}
//
func createDb(filename: String? = nil) -> OpaquePointer? {
let fname = filename ?? "sqlite"
let fileURL: URL
if let defaultDir = config.defaultDirectory {
fileURL = URL(fileURLWithPath: defaultDir).appendingPathComponent("\(fname).db")
//
let dirURL = fileURL.deletingLastPathComponent()
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: dirURL.path) {
do {
try fileManager.createDirectory(at: dirURL, withIntermediateDirectories: true, attributes: nil)
} catch {
// print(": \(error)")
return nil
}
}
} else {
fileURL = try! FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("\(fname).db")
}
var dbPointer: OpaquePointer?
if sqlite3_open(fileURL.path, &dbPointer) == SQLITE_OK {
db = dbPointer
if config.encryption && config.password != nil {
_ = run("PRAGMA key = ?", params: [config.password!])
}
self.fileURLPath = fileURL
return db
} else {
sqlite3_close(dbPointer)
return nil
}
}
// SQL
func run(_ sql: String, params: [Any] = []) -> SQLiteResultBySt {
guard let db = db else {
return SQLiteResultBySt(error: "Database not initialized")
}
var statement: OpaquePointer?
if sqlite3_prepare_v2(db, sql, -1, &statement, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db))
return SQLiteResultBySt(error: errmsg)
}
for (index, param) in params.enumerated() {
let idx = Int32(index + 1)
switch param {
case let text as String:
sqlite3_bind_text(statement, idx, (text as NSString).utf8String, -1, nil)
case let num as Int:
sqlite3_bind_int64(statement, idx, Int64(num))
case let num as Int64:
sqlite3_bind_int64(statement, idx, num)
case let num as Double:
sqlite3_bind_double(statement, idx, num)
case let data as Data:
data.withUnsafeBytes { bytes in
sqlite3_bind_blob(statement, idx, bytes.baseAddress, Int32(data.count), nil)
}
case is NSNull:
sqlite3_bind_null(statement, idx)
default:
"\(param)".utf8CString.withUnsafeBufferPointer { cString in
sqlite3_bind_text(statement, idx, cString.baseAddress, -1, nil)
}
}
}
let result = sqlite3_step(statement)
if result != SQLITE_DONE && result != SQLITE_ROW {
let errmsg = String(cString: sqlite3_errmsg(db))
sqlite3_finalize(statement)
return SQLiteResultBySt(error: errmsg)
}
let changes = sqlite3_changes(db)
let lastId = sqlite3_last_insert_rowid(db)
sqlite3_finalize(statement)
return SQLiteResultBySt(changes: Int(changes), lastInsertRowid: lastId)
}
//
func query(_ sql: String, params: [Any] = []) -> SQLiteResultBySt {
guard let db = db else {
return SQLiteResultBySt(error: "Database not initialized")
}
var statement: OpaquePointer?
if sqlite3_prepare_v2(db, sql, -1, &statement, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db))
return SQLiteResultBySt(error: errmsg)
}
for (index, param) in params.enumerated() {
let idx = Int32(index + 1)
switch param {
case let text as String:
sqlite3_bind_text(statement, idx, (text as NSString).utf8String, -1, nil)
case let num as Int:
sqlite3_bind_int64(statement, idx, Int64(num))
case let num as Int64:
sqlite3_bind_int64(statement, idx, num)
case let num as Double:
sqlite3_bind_double(statement, idx, num)
case let data as Data:
data.withUnsafeBytes { bytes in
sqlite3_bind_blob(statement, idx, bytes.baseAddress, Int32(data.count), nil)
}
case is NSNull:
sqlite3_bind_null(statement, idx)
default:
"\(param)".utf8CString.withUnsafeBufferPointer { cString in
sqlite3_bind_text(statement, idx, cString.baseAddress, -1, nil)
}
}
}
var rows: [[Any]] = []
var columns: [String] = []
var maps: [[String: Any]] = []
//
let columnCount = sqlite3_column_count(statement)
for i in 0..<columnCount {
if let name = sqlite3_column_name(statement, i) {
columns.append(String(cString: name))
}
}
//
while sqlite3_step(statement) == SQLITE_ROW {
var row: [Any] = []
var map: [String: Any] = [:]
for i in 0..<columnCount {
let type = sqlite3_column_type(statement, i)
let columnName = columns[Int(i)]
var value: Any = ""
switch type {
case SQLITE_TEXT:
if let text = sqlite3_column_text(statement, i) {
value = String(cString: text)
}
case SQLITE_INTEGER:
value = sqlite3_column_int64(statement, i)
case SQLITE_FLOAT:
value = sqlite3_column_double(statement, i)
case SQLITE_BLOB:
let blobLength = sqlite3_column_bytes(statement, i)
if let blobData = sqlite3_column_blob(statement, i) {
value = Data(bytes: blobData, count: Int(blobLength))
} else {
value = Data()
}
default:
value = ""
}
row.append(value)
map[columnName] = value
}
rows.append(row)
maps.append(map)
}
sqlite3_finalize(statement)
return SQLiteResultBySt(rows: rows, columns: columns, maps: maps)
}
//
func insert(table: String, data: [String: Any]) -> SQLiteResultBySt {
let keys = Array(data.keys)
let values = keys.map { data[$0]! }
let sql = "INSERT INTO \(table) (\(keys.joined(separator: ","))) VALUES (\(keys.map { _ in "?" }.joined(separator: ",")))"
return run(sql, params: values)
}
//
func update(table: String, data: [String: Any], where: String, params: [Any] = []) -> SQLiteResultBySt {
let keys = Array(data.keys)
var values = keys.map { data[$0]! }
let sets = keys.map { "\($0) = ?" }.joined(separator: ",")
values.append(contentsOf: params)
let sql = "UPDATE \(table) SET \(sets) WHERE \(`where`)"
return run(sql, params: values)
}
//
func delete(table: String, where: String, params: [Any] = []) -> SQLiteResultBySt {
let sql = "DELETE FROM \(table) WHERE \(`where`)"
return run(sql, params: params)
}
//
func saveLocal(filename: String? = nil) -> SQLiteResultBySt {
guard let db = self.db else {
return SQLiteResultBySt(error: "Database not initialized")
}
do {
let fname = filename ?? "sqlite"
let fileManager = FileManager.default
// 使
let targetDir = self.config.defaultDirectory != nil ? URL(fileURLWithPath: self.config.defaultDirectory!) : fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
//
var dbPath = String(cString: sqlite3_db_filename(self.db!, nil))
// dbPath使fileURLPath
if dbPath.isEmpty {
if let fileURL = self.fileURLPath {
dbPath = fileURL.path
} else {
return SQLiteResultBySt(error: "无法获取数据库文件路径")
}
}
let sourceUrl = URL(fileURLWithPath: dbPath)
let targetUrl = targetDir.appendingPathComponent("\(fname).db")
//
if !fileManager.fileExists(atPath: sourceUrl.path) {
return SQLiteResultBySt(error: "未能找到数据库文件")
}
guard let sourceAttrs = try? fileManager.attributesOfItem(atPath: sourceUrl.path),
sourceAttrs[.size] as? UInt64 ?? 0 > 0 else {
return SQLiteResultBySt(error: "数据库文件无效或损坏")
}
//
sqlite3_exec(db, "PRAGMA wal_checkpoint(FULL)", nil, nil, nil)
//
let tempUrl = targetDir.appendingPathComponent("\(fname)_temp.db")
//
if fileManager.fileExists(atPath: tempUrl.path) {
try fileManager.removeItem(at: tempUrl)
}
//
try fileManager.copyItem(at: sourceUrl, to: tempUrl)
//
guard let tempAttrs = try? fileManager.attributesOfItem(atPath: tempUrl.path),
tempAttrs[.size] as? UInt64 ?? 0 > 0 else {
try? fileManager.removeItem(at: tempUrl)
return SQLiteResultBySt(error: "备份文件创建失败")
}
//
if fileManager.fileExists(atPath: targetUrl.path) {
try fileManager.removeItem(at: targetUrl)
}
//
try fileManager.moveItem(at: tempUrl, to: targetUrl)
return SQLiteResultBySt()
} catch {
return SQLiteResultBySt(error: error.localizedDescription)
}
}
//
func loadLocal(filename: String) -> SQLiteResultBySt {
let fileManager = FileManager.default
let sourceDir = self.config.defaultDirectory != nil ? URL(fileURLWithPath: self.config.defaultDirectory!) : fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let sourceUrl = sourceDir.appendingPathComponent("\(filename).db")
let targetUrl = sourceDir.appendingPathComponent("sqlite.db")
do {
//
if !fileManager.fileExists(atPath: sourceUrl.path) {
return SQLiteResultBySt(error: "没有数据库")
}
guard let sourceAttrs = try? fileManager.attributesOfItem(atPath: sourceUrl.path),
sourceAttrs[.size] as? UInt64 ?? 0 > 0 else {
return SQLiteResultBySt(error: "数据库文件无效或损坏")
}
//
let tempUrl = sourceDir.appendingPathComponent("\(filename)_temp.db")
//
if fileManager.fileExists(atPath: tempUrl.path) {
try fileManager.removeItem(at: tempUrl)
}
//
try fileManager.copyItem(at: sourceUrl, to: tempUrl)
//
guard let tempAttrs = try? fileManager.attributesOfItem(atPath: tempUrl.path),
tempAttrs[.size] as? UInt64 ?? 0 > 0 else {
try? fileManager.removeItem(at: tempUrl)
return SQLiteResultBySt(error: "数据库文件损坏")
}
//
if fileManager.fileExists(atPath: targetUrl.path) {
try fileManager.removeItem(at: targetUrl)
}
//
try fileManager.moveItem(at: tempUrl, to: targetUrl)
//
if let _ = self.createDb() {
return SQLiteResultBySt()
} else {
return SQLiteResultBySt(error: "数据库文件损坏")
}
} catch {
//
let tempUrl = sourceDir.appendingPathComponent("\(filename)_temp.db")
try? fileManager.removeItem(at: tempUrl)
return SQLiteResultBySt(error: "数据库文件损坏")
}
}
//
func setDefaultDirectory(directory: String) {
self.config.defaultDirectory = directory
}
//
func setPassword(_ password: String?) {
config.password = password
config.encryption = password != nil && !password!.isEmpty
}
//
func tableExists(tableName: String) -> Bool {
let result = query("SELECT name FROM sqlite_master WHERE type='table' AND name=?", params: [tableName])
return (result.rows?.isEmpty == false)
}
//
func createTable(tableName: String, columns: [String: String]) -> SQLiteResultBySt {
let columnDefinitions = columns.map { key, value in "\(key) \(value)" }.joined(separator: ",")
let sql = "CREATE TABLE IF NOT EXISTS \(tableName) (\(columnDefinitions))"
return run(sql)
}
//
func dropTable(tableName: String) -> SQLiteResultBySt {
let sql = "DROP TABLE IF EXISTS \(tableName)"
return run(sql)
}
//
func close() {
if let db = db {
sqlite3_close(db)
self.db = nil
}
}
//
func beginTransaction() -> SQLiteResultBySt {
if isInTransaction {
return SQLiteResultBySt(error: "Transaction already in progress")
}
isInTransaction = true
return run("BEGIN TRANSACTION")
}
//
func commit() -> SQLiteResultBySt {
if !isInTransaction {
return SQLiteResultBySt(error: "No transaction in progress")
}
isInTransaction = false
return run("COMMIT")
}
//
func rollback() -> SQLiteResultBySt {
if !isInTransaction {
return SQLiteResultBySt(error: "No transaction in progress")
}
isInTransaction = false
return run("ROLLBACK")
}
// SQL
func executeBatch(statements: [SQLiteExecuteBatchParamsBySt]) -> [SQLiteResultBySt] {
var results: [SQLiteResultBySt] = []
var hasError = false
let wasInTransaction = isInTransaction
if !wasInTransaction {
_ = beginTransaction()
}
for stmt in statements {
let result = run(stmt.sql, params: stmt.params ?? [])
results.append(result)
if result.error != nil {
hasError = true
break
}
}
if hasError && !wasInTransaction {
_ = rollback()
} else if !wasInTransaction {
_ = commit()
}
return results
}
//
func getDatabasePath() -> String? {
let fileManager = FileManager.default
guard let db = self.db else {
return nil
}
do {
//
var dbPath = String(cString: sqlite3_db_filename(self.db!, nil))
// dbPath使fileURLPath
if dbPath.isEmpty {
if let fileURL = self.fileURLPath {
dbPath = fileURL.path
} else {
return nil
}
}
//
let cacheURL = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let tempURL = cacheURL.appendingPathComponent("tmui4x_xSqlite_DbackTemp.db")
//
if fileManager.fileExists(atPath: tempURL.path) {
try fileManager.removeItem(at: tempURL)
}
//
try FileManager.default.copyItem(atPath: dbPath, toPath: tempURL.path)
return tempURL.path
} catch {
return nil
}
}
}