autojs-sqlite
Version:
autojs中二代api里esm环境下使用sqlite数据库的orm框架,风格参考sequelize
428 lines (427 loc) • 12.1 kB
JavaScript
/*
* @Author: 抠脚本人
* @QQ: 742374184
* @Date: 2022-12-15 18:09:37
* @LastEditTime: 2022-12-19 12:35:52
* @Description: 数据库模块
* 灵感来之不易,积累创造奇迹
*/
import { install } from "rhino";
import path from "path";
import fs from "fs";
import EventEmitter from "events";
install();
export const DataTypes = {
DATE: "DATETIME",
DATEONLY: "DATE",
TEXT: "TEXT",
INTEGER: "INTEGER",
BOOLEAN: "TINYINT(1)",
};
const ContentValues = android.content.ContentValues;
// 继承SQLiteOpenHelper,参见Android文档 https://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper
const SQLHelper = await $java.defineClass(
class SQLHelper extends android.database.sqlite.SQLiteOpenHelper {
/**
* 构造一个数据库文件
* @param {android.content.Context} context
* @param {string} dbPath
*/
constructor(context, dbPath, version) {
// 调用父类构造函数,指定数据库路径、版本等
super(context, dbPath, null, version);
}
/**
* 重写父类方法,在数据库打开时执行创建表的语句
* @param {android.database.sqlite.SQLiteDatabase} db
*/
onCreate(db) {
console.log("create", db);
}
/**
* 更新数据库版本
* @param {android.database.sqlite.SQLiteDatabase} db
* @param {number} oldVersion
* @param {number} newVersion
*/
onUpgrade(db, oldVersion, newVersion) {
console.log(`onUpgrade: ${oldVersion} to ${newVersion}`);
}
}
);
class SQLiteHelper {
static dataToContent(data) {
const values = new ContentValues();
const arr = Object.entries(data);
for (const kv of arr) {
let targetValue;
switch (typeof kv[1]) {
case "number":
if (Number.isInteger(kv[1])) {
targetValue = $java.boxInt(kv[1]);
} else {
targetValue = $java.boxDouble(kv[1]);
}
break;
case "string":
targetValue = kv[1];
break;
default:
break;
}
values.put(kv[0], targetValue);
}
return values;
}
static toRawCreate(tableName, attributes) {
let result = `CREATE TABLE IF NOT EXISTS ${tableName}(\n`;
Object.entries(attributes).forEach(entry => {
let value = entry[1];
if (typeof value === "string") {
result += `'${entry[0]}' ` + value + ",\n";
} else {
result += `'${entry[0]}' ` + value.type;
if (value.primaryKey) {
result += " PRIMARY KEY";
}
if (value.autoIncrement) {
result += " AUTOINCREMENT";
}
if (value.notNull) {
result += " NOT NULL";
}
if (value.defaultValue) {
result += ` DEFAULT ${value.defaultValue}`;
}
if (value.unique) {
result += " UNIQUE";
}
result += ",\n";
}
});
return result.replace(/,\n$/, "\n)");
}
static toSet(set) {
let result = "set ";
Object.entries(set).forEach(entry => {
let value = entry[1];
if (typeof value === "string") {
result += `${entry[0]}='${value}' , `;
} else if (typeof value === "number") {
result += `${entry[0]}=${value} , `;
} else {
let item = entry[0];
Object.entries(value).forEach(entry2 => {
let target = entry2[1];
if (typeof target === "string") {
target = `'${target}'`;
}
result += item + entry2[0] + target + " , ";
});
}
});
return result.replace(/ , $/, "");
}
static toWhereClause(where) {
let result = {
clause: "",
args: [],
};
Object.entries(where).forEach(entry => {
let value = entry[1];
if (typeof value === "string") {
result.clause += `${entry[0]} = ? and `;
result.args.push(value);
} else if (typeof value === "number") {
result.clause += `${entry[0]} = ? and `;
result.args.push(String(value));
} else {
let item = entry[0];
Object.entries(value).forEach(entry2 => {
let target = entry2[1];
if (typeof target === "number") {
target = String(target);
}
result.clause += item + " " + entry2[0] + " ? and ";
result.args.push(target);
});
}
});
result.clause = result.clause.replace(/ and $/, "");
return result;
}
static toRawWhere(where) {
let result = "where ";
Object.entries(where).forEach(entry => {
let value = entry[1];
if (typeof value === "string") {
result += `${entry[0]}='${value}' and `;
} else if (typeof value === "number") {
result += `${entry[0]}=${value} and `;
} else {
let item = entry[0];
Object.entries(value).forEach(entry2 => {
let target = entry2[1];
if (typeof target === "string") {
target = `'${target}'`;
}
result += item + entry2[0] + target + " and ";
});
}
});
return result.replace(/ and $/, "");
}
/**
* 将查询结果转为数组
* @param {android.database.Cursor} cursor 查询结果
* @returns 结果转为数组
*/
static resultToArray(cursor) {
let result = [];
if (cursor.getCount() == 0) {
return result;
}
let columns = cursor.getColumnCount();
const typeMap = new Map();
cursor.moveToFirst();
for (let index = 0; index < columns; index++) {
typeMap.set(index, [cursor.getColumnName(index), cursor.getType(index)]);
}
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
const student = {};
for (let index = 0; index < columns; index++) {
let value;
/** @type {[string,string]}*/
let info = typeMap.get(index);
let type = info[1];
switch (type) {
case 0:
value = null;
case 1:
value = cursor.getInt(index);
break;
case 2:
value = cursor.getDouble(index);
break;
case 3:
value = cursor.getString(index);
break;
case 4:
value = cursor.getBlob(index);
break;
}
student[info[0]] = value;
}
result.push(student);
}
cursor.close();
return result;
}
}
class Model {
/**@type {android.database.sqlite.SQLiteDatabase} */
#db;
#tableName;
constructor(db, tableName) {
this.#db = db;
this.#tableName = tableName;
}
get tableName() {
return this.#tableName;
}
insertItem(data, update = false) {
const values = SQLiteHelper.dataToContent(data);
// 插入一条数据
try {
return this.#db.insertOrThrow(this.#tableName, null, values);
} catch (error) {
/**@type {string} */
let message = error.message.split("\n")[0];
let unique_column = message.match(/UNIQUE constraint failed: .+?\.(.+?) /);
if (update && unique_column) {
let column = unique_column[1];
let item = this.findOne({ [column]: data[column] });
this.update(data, item);
const pk = this.#db.rawQuery(`SELECT col.name FROM pragma_table_info (?) as col WHERE col.pk = 1;`, [this.#tableName]);
const pkName = SQLiteHelper.resultToArray(pk)[0].name;
return item[pkName];
} else {
console.error(message);
return -1;
}
}
}
/**
* 添加数据
* @param {object[]} data 数据
*/
insert(data, update = false) {
let result = [];
for (const item of data) {
result.push(this.insertItem(item, update));
}
return result;
}
delete(where) {
let rawWhere = SQLiteHelper.toWhereClause(where);
return this.#db.delete(this.#tableName, rawWhere.clause, rawWhere.args);
}
update(value, where) {
const values = SQLiteHelper.dataToContent(value);
const whereInfo = SQLiteHelper.toWhereClause(where);
return this.#db.update(this.#tableName, values, whereInfo.clause, whereInfo.args);
}
query(where) {
let whereClause = where ? SQLiteHelper.toWhereClause(where) : { args: [] };
let rawWhere = where ? `WHERE ${whereClause.clause}` : "";
const cursor = this.#db.rawQuery(`SELECT * FROM ${this.#tableName} ${rawWhere}`, whereClause.args);
return SQLiteHelper.resultToArray(cursor);
}
findOne(where) {
let rawWhere = SQLiteHelper.toWhereClause(where);
const cursor = this.#db.rawQuery(`SELECT * FROM ${this.#tableName} WHERE ${rawWhere.clause} limit 1`, rawWhere.args);
return cursor.getCount() ? SQLiteHelper.resultToArray(cursor)[0] : null;
}
findByPk(id) {
const pk = this.#db.rawQuery(`SELECT col.name FROM pragma_table_info (?) as col WHERE col.pk = 1;`, [this.#tableName]);
const pkName = SQLiteHelper.resultToArray(pk)[0].name;
id = [String(id)];
/* const pk = this.#db.rawQuery(`pragma table_info ('${this.#tableName}')`, null);
const arr = SQLiteHelper.resultToArray(pk);
const pkInfo = arr.find(item => item.pk === 1);
id = [String(id)]; */
const cursor = this.#db.rawQuery(`SELECT * FROM ${this.#tableName} WHERE ${pkName} = ?`, id);
return cursor.getCount() ? SQLiteHelper.resultToArray(cursor)[0] : null;
}
count(where) {
let whereClause = where ? SQLiteHelper.toWhereClause(where) : { args: [] };
let rawWhere = where ? `WHERE ${whereClause.clause}` : "";
const cursor = this.#db.rawQuery(`SELECT * FROM ${this.#tableName} ${rawWhere}`, rawWhere.args);
return cursor.getCount();
}
}
export class SQLite {
#db;
emitter;
/**
* 构造一个数据string
* @param {string} name
* @param {object} config
* @return sqlite数据库
*/
constructor(name, config = {}) {
// 数据库路径
const dbPath = path.join(process.cwd(), `${name}.db`);
if (config.force) {
fs.rmSync(dbPath, { force: true });
}
const sqlHelper = new SQLHelper($autojs.androidContext, dbPath, config.version ?? 1);
const db = config.readonly ? sqlHelper.getReadableDatabase() : sqlHelper.getWritableDatabase();
// 设置数据库对象的方法均在io线程执行,避免阻塞node.js当前线程
// $java.setThreadMode(db, "io");
this.emitter = new EventEmitter();
this.#db = db;
}
get version() {
return this.#db.getVersion();
}
get pageSize() {
return this.#db.getPageSize();
}
get maximumSize() {
return this.#db.getMaximumSize();
}
get path() {
return this.#db.getPath();
}
get inTransaction() {
return this.#db.inTransaction();
}
getTable(tableName) {
let arr = this.rawQuery("SELECT * FROM sqlite_master WHERE name=?", [tableName]);
return arr.length ? new Model(this.#db, tableName) : null;
}
createTable(tableName, attributes) {
//生成sql语句
const sql = SQLiteHelper.toRawCreate(tableName, attributes);
this.#db.execSQL(sql);
return new Model(this.#db, tableName);
}
rawCreateTable(tableName, sql) {
this.#db.execSQL(sql);
return new Model(this.#db, tableName);
}
/**
* 使用原始语句查询
* @param {string} sql 原始语句
* @param {any[]} binding 绑定的值
*/
rawQuery(sql, binding = []) {
let strArr = binding.map(item => String(item));
const cursor = this.#db.rawQuery(sql, strArr);
return SQLiteHelper.resultToArray(cursor);
}
rawSql(sql, binding) {
try {
if (binding !== undefined) {
let strArr = binding.map(item => String(item));
this.#db.execSQL(sql, strArr);
} else {
this.#db.execSQL(sql);
}
} catch (error) {
console.error(error);
}
}
/**
*
* @param {(sqlite:this)=>void} callback
* @returns
*/
begin(callback) {
let emitter = this.emitter;
this.#db.beginTransactionWithListener({
onBegin() {
emitter.emit("begin");
},
onCommit() {
emitter.emit("commit");
},
onRollback() {
emitter.emit("rollback");
},
});
try {
callback?.(this);
} catch (error) {
let message = error.message.split("\n")[0];
console.error(message);
this.endTransaction();
}
return emitter;
}
end() {
try {
this.#db.endTransaction();
} catch (error) {
let message = error.message.split("\n")[0];
console.error(message);
}
}
succeed() {
try {
this.#db.setTransactionSuccessful();
} catch (error) {
let message = error.message.split("\n")[0];
console.error(message);
}
}
close() {
this.#db.close();
}
merge() {
this.succeed();
this.end();
}
}