plugins/db/dao.js
2024-06-26 12:17:41 +08:00

593 lines
19 KiB
JavaScript

import db from "apigo.cc/apigo/plugins/db"
import redis from "apigo.cc/apigo/plugins/redis"
import logger from "logger"
/*{{$dao := . -}}*/
class DBConnector {
constructor(dbName, redisName) {
this._db = db.fetch(dbName)
this._rd = redis.fetch(redisName)
this._tx = db.Tx
}
query(sql, ...args) {
if (this._tx) return this._tx.query(sql, ...args)
return this._db.query(sql, ...args)
}
query1(sql, ...args) {
if (this._tx) return this._tx.query1(sql, ...args)
return this._db.query1(sql, ...args)
}
query11(sql, ...args) {
if (this._tx) return this._tx.query11(sql, ...args)
return this._db.query11(sql, ...args)
}
query1a(sql, ...args) {
if (this._tx) return this._tx.query1a(sql, ...args)
return this._db.query1a(sql, ...args)
}
exec(sql, ...args) {
if (this._tx) return this._tx.exec(sql, ...args)
return this._db.exec(sql, ...args)
}
insertTo(table, data) {
if (this._tx) return this._tx.insert(table, data)
return this._db.insert(table, data)
}
replaceTo(table, data) {
if (this._tx) return this._tx.replace(table, data)
return this._db.replace(table, data)
}
updateTo(table, data, sql, ...args) {
if (this._tx) return this._tx.update(table, data, sql, ...args)
return this._db.update(table, data, sql, ...args)
}
deleteFrom(table, where, ...args) {
if (this._tx) return this._tx.delete(table, where, ...args)
return this._db.delete(table, where, ...args)
}
begin(){
if(!this._tx) this._tx = this._db.begin()
}
end(success){
if(this._tx) this._tx.finish(success)
this._tx = null
}
}
class BaseTable {
constructor(conn:DBConnector, options) {
this._conn = conn
this._dbName = options.dbName
this._tableName = options.tableName
this._fields = options.fields
this._selectFields = options.selectFields
this._validSetValue = options.validSetValue
this._validField = options.validField
this._validWhere = options.validWhere
this._validSet = options.validSet
this._versionField = options.versionField
this._hasVersion = options.hasVersion
this._autoGenerated = options.autoGenerated
this._autoGeneratedOnUpdate = options.autoGeneratedOnUpdate
}
_fixData(data=null) {
let version = this._hasVersion ? this._getVersion() : 0
if(data) {
for (let f of this._autoGenerated) if (data[f] == null) delete data[f]
for (let f of this._autoGeneratedOnUpdate) if (data[f] == null) delete data[f]
if (this._validField && data[this._validField] === undefined) data[this._validField] = this._validSetValue
if (this._hasVersion) data[this._versionField] = version
}
return version
}
_finishData(r, version) {
if (this._hasVersion){
this._commitVersion(version)
r.version = version
}
return r
}
_getVersion() {
let version = 0
if(this._hasVersion) {
if (this._conn._rd) {
version = this._conn._rd.incr("_DATA_VERSION_"+this._tableName)
if (version > 1) {
this._conn._rd.setEX("_DATA_VERSION_DOING_"+this._tableName+"_" + version, 10, true)
return version
}
this._conn._rd.del("_DATA_VERSION_"+this._tableName)
version = 0
} else if(!globalThis._noRedisWarned) {
globalThis._noRedisWarned = true
logger.warn("use version but not configured redis", {db: "{{$dao.DBName}}", table: this._tableName})
}
// no redis configured or first time will use version from db
let maxVersion = parseInt(this._conn.query11("SELECT MAX(`"+this._versionField+"`) FROM `"+this._tableName+"`") || '0')
version = maxVersion + 1
if (this._conn._rd) {
this._conn._rd.mSet("_DATA_VERSION_"+this._tableName, version, "_DATA_MAX_VERSION_"+this._tableName, version)
this._conn._rd.setEX("_DATA_VERSION_DOING_"+this._tableName+"_" + version, 10, true)
}
}
return version
}
_commitVersion(version) {
if(this._conn._rd){
this._conn._rd.del("_DATA_VERSION_DOING_"+this._tableName+"_"+version)
let seqVersion = parseInt(this._conn._rd.get("_DATA_VERSION_"+this._tableName) || '0')
let currentMaxVersion = parseInt(this._conn._rd.get("_DATA_MAX_VERSION_"+this._tableName) || '0')
for(let i=currentMaxVersion; i<=seqVersion; i++){
if(this._conn._rd.exists("_DATA_VERSION_DOING_"+this._tableName+"_"+i)) break
this._conn._rd.set("_DATA_MAX_VERSION_"+this._tableName, i)
}
}
}
insert(data) {
let version = this._fixData(data)
return this._finishData(this._conn.insertTo(this._tableName, data), version)
}
replace(data) {
let version = this._fixData(data)
return this._finishData(this._conn.replaceTo(this._tableName, data), version)
}
updateBy(data, where, ...args) {
let version = this._fixData(data)
return this._finishData(this._conn.updateTo(this._tableName, data, where, ...args), version)
}
}
class BaseItem {
constructor(table: BaseTable, data) {
this._table = table
this._data = data || {}
this._changed = false
}
_set(key, value){
if(this._data[key] === value) return
this._changed = true
this._data[key] = value
}
_get(key, value){
return this._data[key]
}
getData(){
return this._data
}
getJson(){
return JSON.stringify(this._data)
}
}
class BaseList extends Array {
constructor(table: BaseTable, list:Array<Object>) {
super()
this._table = table
this._list = list || []
}
getData(){
return this._list
}
getJson(){
return JSON.stringify(this._list)
}
getDataBy(...fields){
let list = []
for(let item of this._list){
let o = {}
for(let k in item) if(fields.indexOf(k)!==-1) o[k] = item[k]
list.push(o)
}
return list
}
}
class BaseQuery {
constructor(table: BaseTable) {
this._table = table
this._validWhere = table._validWhere
this._sql = ""
this._fields = table._selectFields
this._where = ""
this._extraSql = ""
this._extraArgs = []
this._args = []
this._leftJoins = []
this._leftJoinArgs = []
this._result = null
this._lastSql = ""
this._lastSqlArgs = []
}
_parseFields(fields, table) {
let fieldArr = fields
if(typeof fields !== 'object' || !(fields instanceof Array)) {
if(typeof fields !== 'string') fields = fields.toString()
if (fields==="" || fields.indexOf('(') !== -1 || fields.indexOf('`') !== -1) return fields
fieldArr = fields.split(",")
}
for(let i=0; i<fieldArr.length; i++){
let field = fieldArr[i]
if(typeof field !== 'string') field = field.toString()
field = field.trim()
let as = ""
if(field.indexOf(' ')!==-1){
let a = field.split(" ")
field = a[0]
if(a[a.length-2].toLowerCase()==="as" && !a[a.length-1].startsWith("`")) a[a.length-1] = "`" + a[a.length-1] + "`"
as = " " + a.slice(1),join(" ")
}
fieldArr[i] = table ? "`"+table+"`.`"+field+"`"+as : "`"+field+"`"+as
}
return fieldArr.join(",")
}
_parse(tag){
if(this._sql !== "") return {sql:this._sql, args:this._args}
let fields = this._fields
let validWhere = this._validWhere
if(tag==="COUNT"){
fields = "COUNT(*)"
}else if(tag==="COUNT_ALL"){
fields = "COUNT(*)"
validWhere = ""
}else if(tag==="ALL"){
validWhere = ""
}else if(tag==="ALL_VERSION"){
validWhere = ""
}
let leftJoinsStr = ""
if(this._leftJoins.length > 0){
leftJoinsStr = " " + this._leftJoins.join(" ")
this._args.unshift(...this._leftJoinArgs)
validWhere = validWhere.replace(/ AND /g, " AND `"+this._table._tableName+"`.")
}
if(this._where==="" && validWhere.startsWith(" AND ")) validWhere = validWhere.substring(5)
if(this._extraArgs) this._args.push(...this._extraArgs)
return {sql:"SELECT " + fields + " FROM `"+this._table._tableName+"`" + leftJoinsStr + " WHERE " + this._where + validWhere + this._extraSql, args:this._args}
}
sql(sql, ...args){
this._sql = sql
this._args = args
return this
}
fields(fields){
this._fields = this._parseFields(fields, "")
return this
}
appendFields(fields){
if(this._fields) this._fields += ", "
this._fields += this._parseFields(fields, "")
return this
}
where(where, ...args){
this._where = where
this._args = args
return this
}
in(field, ...values){
if(field.indexOf("`")===-1) field = "`"+field+"`"
this._where = field+" IN "+db.inKeys(values.length)
this._args = values
return this
}
and(where, ...args){
if(this._where) this._where += " AND "
this._where += where
this._args = this._args.push(...args)
return this
}
or(where, ...args){
if(this._where) this._where += " OR "
this._where += where
this._args = this._args.push(...args)
return this
}
andIn(field, ...values){
if(field.indexOf("`")===-1) field = "`"+field+"`"
if(this._where) this._where += " AND "
this._where += field + " IN "+db.inKeys(values.length)
this._args = this._args.push(...values)
return this
}
orIn(field, ...values){
if(field.indexOf("`")===-1) field = "`"+field+"`"
if(this._where) this._where += " OR "
this._where += field + " IN "+db.inKeys(values.length)
this._args = this._args.push(...values)
return this
}
orderBy(orderBy){
this._extraSql += " ORDER BY " + orderBy
return this
}
groupBy(groupBy){
this._extraSql += " GROUP BY " + groupBy
return this
}
limit(start, num){
if(num===undefined){
num = start
start = 0
}
this._extraSql += " LIMIT ?,?"
this._extraArgs.push(start, num)
return this
}
having(where, ...args){
this._extraSql += " HAVING "+where
this._extraArgs = this._extraArgs.push(...args)
return this
}
extra(sql, ...args){
this._extraSql += sql
this._extraArgs = this._extraArgs.push(...args)
return this
}
leftJoin(joinTable, fields, on, ...args){
if(this._fields.indexOf("`"+this._table._tableName+"`.")===-1){
this._fields = "`"+this._table._tableName+"`."+this._fields.replace(/`, `/g, "`, `"+this._table._tableName+"`.`")
}
if(fields) this._fields += ", "+this._parseFields(fields, joinTable)
this._leftJoins = this._leftJoins.push("LEFT JOIN `" + joinTable + "` ON " + on)
this._leftJoinArgs = this._leftJoinArgs.push(...args)
return this
}
query(){
let {sql,args} = this._parse("")
this._lastSql = sql
this._lastSqlArgs = args
this._result = this._table._conn.query(sql, ...args).result
return this
}
count(){
let {sql,args} = this._parse("COUNT")
this._lastSql = sql
this._lastSqlArgs = args
return parseInt(this._conn.query11(sql, ...args) || '0')
}
queryByPage(start, num){
this.limit(start, num)
return this.query()
}
first(){
let list = this.list()
if(list.length > 0) return list[0]
return null
}
list(){
if(this._result===null) this.query()
return this._result
}
getSql(){
return this._lastSql
}
getSqlArgs(){
return this._lastSqlArgs
}
}
/*{{- range $t := .Tables }}*/
class /*{{$t.TableName}}*/Table extends BaseTable {
constructor(conn:DBConnector) {
//**//let hasVersion = {{$t.HasVersion}}
super(conn, {dbName: "{{$dao.DBName}}", tableName: "{{$t.TableName}}", fields: [
/*{{- range $f := .Fields -}}*/
//**//{name:'{{$f.Name}}',default:{{$f.Default}}},
/*{{- end -}}*/
], selectFields: "{{$t.SelectFields}}", validSetValue: "{{$t.ValidFieldConfig.ValidSetValue}}", validField: "{{$t.ValidField}}", validWhere: "{{$t.ValidWhere}}", validSet: "{{$t.ValidSet}}", versionField: "{{$t.VersionField}}", hasVersion: hasVersion, autoGenerated: [/*{{- range $v := .AutoGenerated -}}*/'{{$v}}', /*{{- end -}}*/], autoGeneratedOnUpdate: [/*{{- range $v := .AutoGeneratedOnUpdate -}}*/'{{$v}}', /*{{- end -}}*/]})
}
new(data = {}) { return data === null ? null : new /*{{$t.TableName}}*/Item(this, data) }
newQuery() {return new /*{{$t.TableName}}*/Query(this)}
/*{{range $k := .UniqueKeys -}}*/
getBy/*{{$k.Name}}*/(/*{{$k.Args}}*/) { return this.new( this._conn.query1("SELECT {{$t.SelectFields}} FROM `/*{{$t.TableName}}*/` WHERE {{$k.Where}}{{$t.ValidWhere}}", /*{{$k.Args}}*/) ) }
/*{{- end }}*/
/*{{ if $t.PrimaryKey -}}*/
get(/*{{$t.PrimaryKey.Args}}*/) {return this.new( this._conn.query1("SELECT /*{{$t.SelectFields}}*/ FROM `/*{{$t.TableName}}*/` WHERE /*{{$t.PrimaryKey.Where}}*//*{{$t.ValidWhere}}", /*{{$t.PrimaryKey.Args}}*/))}
getWithFields(fields,/*{{$t.PrimaryKey.Args}}*/) {return this.new(this._conn.query1("SELECT "+fields+" FROM `/*{{$t.TableName}}*/` WHERE /*{{$t.PrimaryKey.Where}}*//*{{$t.ValidWhere}}", /*{{$t.PrimaryKey.Args}}*/))}
/*{{ if .ValidSet -}}*/
getWithInvalid(/*{{$t.PrimaryKey.Args}}*/) {return this.new(this._conn.query1("SELECT /*{{$t.SelectFields}}*/ FROM `/*{{$t.TableName}}*/` WHERE /*{{$t.PrimaryKey.Where}}", /*{{$t.PrimaryKey.Args}}*/))}
/*{{- end }}*/
update(data, /*{{$t.PrimaryKey.Args}}*/) {
let version = this._fixData(data)
let r = this.updateTo(this._tableName, data, "{{$t.PrimaryKey.Where}}", /*{{$t.PrimaryKey.Args}}*/)
return this._finishData(r, version)
}
/*{{- end }}*/
/*{{- if .InvalidSet }}*/
enable(/*{{$t.PrimaryKey.Args}}*/) {
/*{{- if .HasVersion }}*/
let version = this._getVersion()
let r = this._conn.exec("UPDATE `/*{{$t.TableName}}*/` set /*{{$t.ValidSet}}*/, `/*{{$t.VersionField}}*/`=? WHERE /*{{$t.PrimaryKey.Where}}", version, /*{{$t.PrimaryKey.Args}}*/)
this._commitVersion(version)
r.version = version
return r
/*{{- else }}*/
return this._conn.exec("UPDATE `/*{{$t.TableName}}*/` set /*{{$t.ValidSet}}*/ WHERE /*{{$t.PrimaryKey.Where}}", /*{{$t.PrimaryKey.Args}}*/)
/*{{- end }}*/
}
disable(/*{{$t.PrimaryKey.Args}}*/){
/*{{- if .HasVersion }}*/
let version = this._getVersion()
let r = this._conn.exec("UPDATE `/*{{$t.TableName}}*/` set /*{{$t.InvalidSet}}*/, `/*{{$t.VersionField}}*/`=? WHERE /*{{$t.PrimaryKey.Where}}", version, /*{{$t.PrimaryKey.Args}}*/)
this._commitVersion(version)
r.version = version
return r
/*{{- else }}*/
return this._conn.exec("UPDATE `/*{{$t.TableName}}*/` set /*{{$t.InvalidSet}}*/ WHERE /*{{$t.PrimaryKey.Where}}", /*{{$t.PrimaryKey.Args}}*/)
/*{{- end }}*/
}
/*{{- else }}*/
delete(/*{{$t.PrimaryKey.Args}}*/) {return this._conn.exec("DELETE FROM `/*{{$t.TableName}}*/` WHERE /*{{$t.PrimaryKey.Where}}", /*{{$t.PrimaryKey.Args}}*/)}
deleteBy(where, ...args) {return this.deleteFrom(this._tableName, where, ...args)}
/*{{- end }}*/
/*{{- range $extCode := $t.ExtTableCode }}*/
/*{{$extCode}}*/
/*{{- end }}*/
}
class /*{{$t.TableName}}*/Item extends BaseItem {
constructor(table: /*{{$t.TableName}}*/Table, data) {
if(!data) data = {}
super(table, data)
this._table = table
}
/*{{- range $f := .Fields }}*/
set /*{{$f.Name}}*/(value){this._set('{{$f.Name}}', value)}
get /*{{$f.Name}}*/(){return this._get('{{$f.Name}}')}
/*{{- end }}*/
/*{{- if .PrimaryKey }}*/
save(){
if(!this._changed) return null
let r = this._table.replace(this.getData())
this._changed = false
return r
}
/*{{- if .InvalidSet }}*/
enable(){return this._table.enable(/*{{.PrimaryKey.ItemArgs}}*/)}
disable(){return this._table.disable(/*{{.PrimaryKey.ItemArgs}}*/)}
/*{{- else }}*/
delete(){return this._table.delete(/*{{.PrimaryKey.ItemArgs}}*/)}
/*{{- end }}*/
/*{{- end }}*/
/*{{- range $extCode := $t.ExtItemCode }}*/
/*{{$extCode}}*/
/*{{- end }}*/
}
class /*{{$t.TableName}}*/List extends BaseList {
constructor(table:/*{{$t.TableName}}*/Table, list) {
if(!list) list = []
super(table, list)
this._table = table
for(let item of list) if(item) this.push(new /*{{$t.TableName}}*/Item(this._table, item))
}
/*{{- if .PrimaryKey }}*/
save(){
let result = {changes:0, version:0, sql:[], sqlArgs:[]}
for(let item of this){
let r = item.save()
if(r){
result.changes += r.changes
result.version = r.version
result.sql.push(r.sql)
result.sqlArgs.push(r.sqlArgs)
}
}
return result
}
/*{{- end }}*/
}
class /*{{$t.TableName}}*/Query extends BaseQuery {
constructor(table:/*{{$t.TableName}}*/Table) {
super(table)
this._table = table
}
first(): /*{{$t.TableName}}*/Item {
let item = super.first()
return !item ? null : new /*{{$t.TableName}}*/Item(this._table, item)
}
list(): /*{{$t.TableName}}*/List {
let list = super.list()
return new /*{{$t.TableName}}*/List(this._table, list)
}
/*{{range $idx := .IndexKeys}}*/
by/*{{$idx.Name}}*/(/*{{$idx.Args}}*/){return this.where("{{$idx.Where}}", /*{{$idx.Args}}*/)}
and/*{{$idx.Name}}*/(/*{{$idx.Args}}*/){
if(this._where) this._where += " AND "
this._where += "{{$idx.Where}}"
this._args = this._args.push(/*{{$idx.Args}}*/)
return this
}
or/*{{$idx.Name}}*/(/*{{$idx.Args}}*/){
if(this._where) this._where += " OR "
this._where += "{{$idx.Where}}"
this._args = this._args.push(/*{{$idx.Args}}*/)
return this
}
/*{{- end }}*/
/*{{- if .ValidSet }}*/
queryWithValid(){
let {sql,args} = this._parse("ALL")
this._lastSql = sql
this._lastSqlArgs = args
this._result = this._table._conn.query(sql, ...args).result
return this
}
/*{{- end }}*/
/*{{- if .ValidSet }}*/
countAll(){
let {sql,args} = this._parse("COUNT_ALL")
this._lastSql = sql
this._lastSqlArgs = args
return parseInt(this._table._conn.query11(sql, ...args) || '0')
}
/*{{- end }}*/
/*{{- if .ValidSet }}*/
queryWithValidByPage(start, num){
this.limit(start, num)
return this.queryWithValid()
}
/*{{- end }}*/
/*{{- if .HasVersion }}*/
/*{{- if .ValidSet }}*/
queryByVersion(minVersion, maxVersion, limit, withInvalid){
if(minVersion > 0) withInvalid = true
let parseTag = withInvalid ? "ALL_VERSION" : "VERSION"
/*{{- else }}*/
//**//queryByVersion(minVersion, maxVersion, limit){
//**//let parseTag = "VERSION"
/*{{- end }}*/
if(maxVersion===0){
if(this._table._conn._rd){
maxVersion = parseInt(this._table._conn._rd.GET("_DATA_MAX_VERSION_"+this._table._tableName) || '0')
} else if(!globalThis._noRedisWarned) {
globalThis._noRedisWarned = true
logger.warn("use version but not configured redis", {db:"{{.DBName}}", table:"{{.TableName}}"})
}
if(maxVersion===0){
maxVersion = parseInt(this._table._conn.query11("SELECT MAX(`/*{{.VersionField}}*/`) FROM `/*{{.TableName}}*/`") || '0')
this._lastSql = "SELECT MAX(`/*{{.VersionField}}*/`) FROM `/*{{.TableName}}*/`"
this._lastSqlArgs = []
}
}
this.and("`version` BETWEEN ? AND ?", minVersion+1, maxVersion )
if(limit > 0){
this.orderBy("`/*{{.VersionField}}*/`")
this.limit(0, limit)
}
let {sql,args} = this._parse(parseTag)
this._lastSql = sql
this._lastSqlArgs = args
this._result = this._table._conn.query(sql, ...args).result
this.maxVersion = maxVersion
return this
}
/*{{- end }}*/
}
/*{{- end }}*/
class /*{{$dao.DBName}}*/Dao {
_tables = {}
constructor(dbName, redisName) {this._conn = new DBConnector(dbName, redisName)}
_getTable(tableName, tableType) {
let tb = this._tables[tableName]
if(!tb){
tb = new tableType(this._conn)
this._tables[tableName] = tb
}
return tb
}
begin() {this._conn.begin()}
end(success) {this._conn.end(success)}
/*{{- range $t := .Tables }}*/
get /*{{$t.TableName}}*/(): /*{{$t.TableName}}*/Table {return this._getTable('{{$t.TableName}}', /*{{$t.TableName}}*/Table)}
/*{{- end }}*/
}
export {
/*{{- $dao.DBName}}*/Dao
/*{{- range $t := .Tables -}}*/, /*{{$t.TableName}}*/Table, /*{{$t.TableName}}*/Query, /*{{$t.TableName}}*/Item, /*{{$t.TableName}}*/List
/*{{- end -}}*/
}