1 Star 3 Fork 0

简单的机械键盘 / psqlorm

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
pqmodel.js 59.50 KB
一键复制 编辑 原始数据 按行查看 历史
简单的机械键盘 提交于 2024-03-17 10:17 . v9.3.25
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154
'use strict';
const makeId = require('./makeId.js');
const randstring = require('./randstring.js');
const makeTimestamp = require('./makeTimestamp.js')
let forbidColumnName = [
'like', 'ilike', 'column'
];
let make_timestamp_func = (typ) => {
if (!typ) return null;
let real_typ = typ.trim().toLowerCase()
if (real_typ === 'bigint') return 'bigint';
if (real_typ.indexOf('varchar') === 0 || real_typ.indexOf('timestamp') === 0) {
return () => {
return (new Date()).toLocaleString().replaceAll('/', '-')
}
}
return null
}
/**
* 在原型上设计一个支持自动化初始化的函数支持。
* */
class PostgreModel {
constructor (pqorm = null) {
let init = true;
if (Array.isArray(pqorm)) {
init = pqorm[1] === undefined ? true : !!pqorm[1];
pqorm = pqorm[0];
}
if (!pqorm) {
//开发者应该在原型上提供此函数。
if (this.getORM && typeof this.getORM === 'function') {
pqorm = this.getORM();
}
if (!pqorm) {
pqorm = this.__pqorm__ || this.__pg__ || this.__pqorm || null;
}
if (!pqorm && process.env.PG_HOST && process.env.PG_DATABASE && process.env.PG_USER) {
let dbconfig = {
host: process.env.PG_HOST,
database: process.env.PG_DATABASE,
user: process.env.PG_USER,
port: process.env.PG_PORT || 5432,
max: process.env.PG_MAX || 25,
password: process.env.PG_PASSWORD || '',
idleTimeoutMillis: process.env.PG_IDLE_TIMEOUT || 3600_000,
connectionTimeoutMillis: process.env.PG_CONNECTION_TIMEOUT || 60000,
}
let initORM = require('./pqorm.js').initORM
pqorm = initORM(dbconfig, process.env.PG_SCHEMA || 'public')
PostgreModel.prototype.__pqorm__ = pqorm
}
}
if (pqorm) {
this.orm = pqorm;
this.db = pqorm.db;
this.odb = pqorm.db;
}
this.__auto_id__ = true;
this.selectField = '*';
this.tableName = null;
this.primaryKey = 'id';
this.lastError = null;
this.idPre = '';
this.idLen = 16;
this.pagesize = 100;
this.dataTypeMap = {
'varchar': 'character varying',
'char': 'character',
'text': 'text',
'decimal': 'numeric',
'numeric': 'numeric',
'integer': 'integer',
'int' : 'integer',
'smallint': 'smallint',
'bigint': 'bigint',
'boolean': 'boolean',
//二进制类型
'bytea': 'bytea',
'jsonb': 'jsonb',
'date': 'date',
'time': 'time without time zone',
'timestamp': 'timestamp without time zone',
'timestamptz': 'timestamp with time zone',
};
this.numerics = [
'smallint','bigint','integer','decimal','numeric', 'int'
];
this.strings = [
'char', 'varchar', 'text'
];
this.times = [
'date','time','timestamp','timestampz'
];
//需要携带括号的类型
this.typeWithBrackets = [
'character varying', 'character', 'decimal', 'numeric'
];
this.defaultWithType = [
'varchar', 'char', 'text', 'bytea', 'timestamp', 'timestampz', 'date', 'time'
];
['defaultWithType', 'typeWithBrackets', 'times', 'strings', 'numerics', 'dataTypeMap'].forEach(a => {
Object.defineProperty(this, a, {
value: this[a],
enumerable: false,
configurable: false,
writable: false
});
});
Object.defineProperty(this, '__trigger__', {
value: {},
enumerable: false,
configurable: false,
writable: true
});
Object.defineProperties(this, {
orgMakeId: {
enumerable: false,
configurable: false,
writable: false,
value: makeId
},
_bigId: {
enumerable: false,
configurable: false,
writable: false,
value: makeId.bigId
},
_makeId: {
enumerable: false,
configurable: false,
writable: true,
value: makeId.serialId
},
__pool__: {
enumerable: false,
configurable: false,
writable: true,
value: []
},
maxPool: {
enumerable: false,
configurable: false,
writable: true,
value: 200
},
__pkey_type__: {
enumerable: false,
configurable: false,
writable: true,
value: 'v'
},
__auto_timestamp__: {
enumerable: false,
configurable: false,
writable: true,
value: {}
}
});
this.makeId = makeId.serialId;
this.__bind_model__ = null;
if (init) {
process.nextTick(async () => {
await this.__init__();
});
}
}
async __init__ () {
this.tableName = this.tableName.trim();
if (!this.tableName || typeof this.tableName !== 'string') {
throw new Error('未指定表名称');
}
if ((/[A-Z]+/).test(this.tableName)) {
console.error(`${this.tableName} 表名不支持大写,已自动更改为小写。`);
this.tableName = this.tableName.toLowerCase();
}
if ((/[\s\*\$\@\!\~\%\^\&\(\)\:\.\,\<\>\)\[\]\/\\\|\{\}\=\+]+/).test(this.tableName)) {
throw new Error(`${this.tableName} 数据表名称不合法,不能包含空白字符和特殊字符,支持字母数字下划线。`);
}
if (!this.orm.tableTrigger.hasTable(this.tableName))
this.initTrigger();
if (this.primaryKey.indexOf(',') > 0) {
this.primaryKey = this.primaryKey.split(',').filter(p => p.length > 0);
}
if (Array.isArray(this.primaryKey)) {
if (this.primaryKey.length === 0) this.primaryKey = 'id';
else if (this.primaryKey.length === 1) this.primaryKey = this.primaryKey[0];
}
if (!this.orm.__register__[this.tableName]) {
this.orm.__register__[this.tableName] = this;
let constructor_name = this.constructor.name.toLowerCase();
let kname = 'Model::' + constructor_name;
if (constructor_name && !this.orm.__register__[kname]) {
this.orm.__register__[kname] = this;
}
}
//把table变成函数对象。
let _table = (name='') => {
return this.model(name);
};
for (let k in this.table) {
_table[k] = this.table[k];
}
this.table = _table;
if (!this.table.column || typeof this.table.column !== 'object') {
throw new Error(`${this.constructor.name} 缺少table.column或table.column不是object,请修改。\n`)
}
//判断主键类型并确定生成id的函数。
if (typeof this.primaryKey === 'string') {
let pktype = this.table.column[this.primaryKey].type;
if (pktype && pktype.trim().toLowerCase() === 'bigint') {
this._makeId = makeId.bigId;
this.__pkey_type__ = 'b';
}
}
//检测是否存在自动生成时间戳
let _col = null;
let _timestamp_action = '';
for (let k in this.table.column) {
_col = this.table.column[k]
if (!_col || typeof _col !== 'object') {
console.error(`${this.tableName}: ${k} 未指定为object类型,请修改。`)
continue
}
if (!_col.timestamp || typeof _col.timestamp !== 'string') {
continue
}
_timestamp_action = _col.timestamp.trim().toLowerCase()
if (_timestamp_action === 'insert') {
this.__auto_timestamp__.insert = [
k,
_col.timestampCallback || make_timestamp_func(_col.type)
];
} else if (_timestamp_action === 'update') {
this.__auto_timestamp__.update = [
k,
_col.timestampCallback || make_timestamp_func(_col.type)
];
}
}
Object.defineProperty(this, '__model_proxy__', {
enumerable: false,
configurable: false,
writable: false,
value: new Proxy({}, {
get: (obj, x) => {
throw new Error(`错误所在Model [${this.constructor.name}]: \n获取其他模型不存在,因此无法访问${x}。`)
},
set: (obj, k, v) => {
throw new Error(`错误所在Model [${this.constructor.name}]: \n获取模型不存在,因此无法设置属性:${k}。`)
}
})
});
if (this.init && typeof this.init === 'function') {
queueMicrotask(() => {
this.init()
})
}
}
/**
*
* @param {string} name
* @returns {string|null} 'c' 表示constructor,'t'表示table,null是没有此模型。
*/
hasModel(name) {
let m = this.orm.__register__['Model::' + name.toLowerCase()];
if (m) return 'c';
m = this.orm.__register__[name];
if (m) return 't';
return null;
}
/**
*
* @param {string} name 构造函数的名字或数据库表的名字。
* @returns {PostgreModel}
*/
getModel(name) {
let m = this.orm.__register__['Model::' + name.toLowerCase()];
if (!m) m = this.orm.__register__[name];
if (!m) {
return this.__model_proxy__;
}
//因为Node.js的事件循环机制,不会有冲突发生,不必考虑锁问题。
//在事物操作中调用,会返回经过绑定处理的PostgreModel实例。
if (this.__bind_model__) {
return m.getPool(this.__bind_model__);
}
return m;
}
initTrigger () {
this.orm.tableTrigger.addTable(this.tableName, this.__trigger__);
let triggers = [
//'BeforeUpdate', 'BeforeInsert', 'BeforeDelete',
'Insert', 'Update', 'Delete'
];
triggers.forEach(t => {
let fname = `trigger${t}`;
if (this[fname] && typeof this[fname] === 'function') {
this.__trigger__[ t[0].toLowerCase() + t.substring(1) ] = this[fname];
}
});
}
timestamp(tobj) {
return this.model().timestamp(tobj)
}
/**
*
* @param {string} tname 数据库表的名字
* @returns {Model}
*/
model(tname='') {
if (tname && this.tableName !== tname && this.orm.__register__[tname]) {
let pm = this.orm.__register__[tname].model();
this.__bind_model__ && pm.bind(this.__bind_model__);
return pm;
}
let m = this.orm.model(tname || this.tableName);
m.__auto_id__ = this.__auto_id__;
m.__id_len__ = this.idLen;
m.__id_pre__ = this.idPre;
m.__primary_key__ = this.primaryKey;
m.__pkey_type__ = this.__pkey_type__;
if (!tname || this.tableName === tname) {
this.__auto_timestamp__.insert && (m.__insert_timestamp__ = this.__auto_timestamp__.insert);
this.__auto_timestamp__.update && (m.__update_timestamp__ = this.__auto_timestamp__.update);
}
this.__bind_model__ && m.bind(this.__bind_model__);
return m;
}
newForTransaction(db) {
let m = new this.constructor([this.orm, false])
//model函数会检测此项并自动绑定
m.__bind_model__ = db
m.__pool__ = []
m.getPool = null
m.freePool = null
m.__trigger__ = this.__trigger__
m.__auto_id__ = this.__auto_id__
m.table = this.table
m.primaryKey = this.primaryKey
m._makeId = this._makeId
m.__pkey_type__ = this.__pkey_type__
m.__auto_timestamp__ = this.__auto_timestamp__
return m
}
getPool(db) {
let m = this.__pool__.pop()
if (m) {
m.__bind_model__ = db
return m
}
return this.newForTransaction(db)
}
freePool(m) {
this.__bind_model__ = null
if (this.__pool__.length < this.maxPool) this.__pool__.push(m)
}
autoId(b = null) {
return this.model().autoId(b === null ? this.__auto_id__ : b);
}
getSchema() {
return this.orm ? this.orm.schema : '';
}
/**
* @param {string|object|boolean|number} icond 前置条件,为真则后续的条件才会生效
* @param {(string|object)} cond 条件,如果是字符串,args表示字符串中?要替换的参数
* @param {array} args
*/
whereIf(icond, cond, args=[]) {
if (icond) return this.where(cond, args);
return this;
}
/**
*
* @param {(string|object)} cond 条件,如果是字符串,args表示字符串中?要替换的参数
* @param {array} args
*/
where(cond, args=[]) {
return this.model().where(cond, args);
}
schema(name) {
return this.model().schema(name);
}
_mschema(name = null) {
let h = this.model();
return name ? h.schema(name) : h;
}
connect() {
return this.model().connect();
}
bind(db) {
if (this.__bind_model__) return this;
//要绑定到一个PostgreModel实例,在事务操作中,需要其他模型上的方法,并保证事务操作的原子性。
if (db && (this !== db) && (db instanceof PostgreModel) && db.__bind_model__) {
let mdb = this.model();
mdb.bind(db.__bind_model__);
return this.getPool(mdb);
}
if (db.constructor.name === 'Model')
return this.model().bind(db);
throw new Error('db 不是一个可以进行bind操作的对象。');
}
trigger(on = true) {
return this.model().trigger(on);
}
triggerCommit(on = true) {
return this.model().triggerCommit(on);
}
returning(r) {
return this.model().returning(r);
}
alias(name) {
return this.model().alias(name);
}
join(m, on, join_type = 'inner') {
let tname;
if (typeof m === 'string') {
tname = m;
} else {
tname = m.tableName;
}
return this.model(this.tableName).join(tname, on, join_type);
}
/**
* @param m {object|string} - 通过this.relate获取的模型实例或直接指定表名的字符串
* @param on {string} - join条件
*
* */
innerJoin(m, on) {
return this.join(m, on, 'inner');
}
/**
* @param m {object|string}
* - 通过this.relate获取的模型实例或直接指定表名的字符串
* @param on {string}
* - join条件
* */
leftJoin(m, on) {
return this.join(m, on, 'left');
}
/**
* @param m {object|string} - 通过this.relate获取的模型实例或直接指定表名的字符串
* @param on {string} - join条件
*
* */
rightJoin(m, on, options = {}) {
return this.join(m, on, 'right');
}
/**
* @param count {number} - 限制返回的条数。
* @param offset {number} - 偏移量,默认值为0。
* */
limit(count, offset=0) {
return this.model().limit((count && typeof count === 'number' && count > 0) ? count : 100, offset);
}
/**
*
* @param data {object} - 要插入的数据对象
* @param options {object}
* - schema {string} 数据库schema。
* - returning {string} sql语句的returning列。
* @returns Promise
*/
async insert(data, options = {schema: null}) {
this.__auto_timestamp__.insert && makeTimestamp(data, this.__auto_timestamp__.insert);
this.__auto_timestamp__.update && makeTimestamp(data, this.__auto_timestamp__.update);
let h = this._mschema(options.schema);
if (this.primaryKey && typeof this.primaryKey === 'string'
&& data[this.primaryKey] === undefined && this.__auto_id__)
{
data[this.primaryKey] = this._makeId(this.idLen, this.idPre);
h.returning(this.primaryKey);
}
options.returning && (h = h.returning(options.returning));
return h.insert(data);
}
/**
*
* @param data {array} - 要插入的数据对象
* @param options {object}
* - schema {string} 数据库schema。
* - returning {string} sql语句的returning列。
* @returns Promise
*/
async insertAll(data, options = {schema: null}) {
if (!Array.isArray(data)) {
throw new Error(`data不是数组,无法写入多个条目到数据库。`);
}
let idlist = [];
let h = this._mschema(options.schema);
if (this.__auto_id__ && this.primaryKey && typeof this.primaryKey === 'string') {
h.returning(this.primaryKey);
for (let i=0; i < data.length; i++) {
this.__auto_timestamp__.insert
&&
makeTimestamp(data[i], this.__auto_timestamp__.insert);
this.__auto_timestamp__.update
&&
makeTimestamp(data[i], this.__auto_timestamp__.update);
if (data[i][this.primaryKey] === undefined) {
data[i][this.primaryKey] = this._makeId(this.idLen, this.idPre);
}
idlist.push(data[i].id);
}
}
options.returning && (h = h.returning(options.returning));
return h.insertAll(data);
}
/**
*
* @param cond {object} - 条件
* @param data {object} - 要更新的数据
* @param options {object}
* - schema {string} 数据库schema。
* - returning {string} sql语句的returning列。
* @returns Promise
*/
async update(cond, data, options={schema: null}) {
this.__auto_timestamp__.update && makeTimestamp(data, this.__auto_timestamp__.update);
let h = this._mschema(options.schema);
options.returning && (h = h.returning(options.returning));
return h.where(cond).update(data);
}
/**
*
* @param cond {object} - 条件
* @param args {object}
* - schema {string} 数据库schema。
* - pagesize {number} 分页大小。
* - offset {number} 偏移量。
* - order {string} 排序方式。
* @returns object
*/
async select(cond, args = {schema: null}) {
if (!cond || typeof cond === 'string' || Array.isArray(cond)) {
return this.model().select(cond ? cond : '*');
}
let t = this._mschema(args.schema).where(cond);
let offset = args.offset || 0;
if (args.pagesize !== undefined) {
t = t.limit(args.pagesize, offset);
} else {
t = t.limit(this.pagesize, offset);
}
if (args.order) {
t = t.order(args.order);
}
return t.select(args.field || this.selectField);
}
/**
*
* @param cond {object} - 条件
* @param options {object}
* - schema {string} 数据库schema。
* - field {string|array} 返回的列,默认为selectField设置的值。
* @returns object
*/
async get(cond = {}, options = {field: null, schema: null}) {
return this._mschema(options.schema).where(cond).get(options.field || this.selectField);
}
/**
*
* @param cond {object} - 条件
* @param options {object}
* - schema {string} 数据库schema。
* - returning {string} sql语句的returning列。
* @returns Promise
*/
async delete(cond, options = {schema: null}) {
let h = this._mschema(options.schema);
options.returning && (h = h.returning(options.returning));
return h.where(cond).delete();
}
/**
*
* @param cond {object} - 条件
* @param options {object}
* - schema {string} 数据库schema。
* @returns Promise
*/
async count(cond = {}, options = {column:'*', schema: null}) {
if (!options) options = {};
if (typeof cond === 'string') {
options.column = cond;
cond = {};
}
let col = options.column || '*';
return this._mschema(options.schema).where(cond).count(col);
}
_fmtNum(m, options) {
let col = this.table.column[options.field];
if (!options.to && col && col.to) {
options.to = col.to;
}
if (options.precision === undefined && col.precision !== undefined) {
options.precision = col.precision;
}
switch(options.to) {
case 'int':
return parseInt(m);
case 'float':
return parseFloat(m);
case 'fixed':
case 'fixed-float':
let prec = (options.precision !== undefined && typeof options.precision === 'number')
? options.precision
: 1;
if (options.to === 'fixed')
return parseFloat(m).toFixed(prec);
return parseFloat(parseFloat(m).toFixed(prec));
}
return m;
}
throwNoFieldsError(options) {
if (!options.field) throw new Error('!!必须指定fileds。');
if (this.table.column[options.field] === undefined)
throw new Error(`!!没有column:${options.field}.`);
}
/**
*
* @param cond {object} - 条件
* @param options {object}
* - schema {string} 数据库schema。
* - field {string} 聚合操作的列。
* @returns Promise
*/
async max(cond = {}, options = {schema: null}) {
if (!options) options = {};
if (typeof cond === 'string') {
options.field = cond;
cond = {};
}
if (typeof options === 'string') options = {field: options};
this.throwNoFieldsError(options);
let m = await this._mschema(options.schema).where(cond).max(options.field);
if (!options.to) return m;
return this._fmtNum(m, options);
}
/**
*
* @param cond {object} - 条件
* @param options {object}
* - schema {string} 数据库schema。
* - field {string} 聚合操作的列。
* @returns object
*/
async min(cond = {}, options = {schema: null}) {
if (!options) options = {};
if (typeof cond === 'string') {
options.field = cond;
cond = {};
}
if (typeof options === 'string') options = {field: options};
this.throwNoFieldsError(options);
let m = await this._mschema(options.schema).where(cond).min(options.field);
if (!options.to) return m;
return this._fmtNum(m, options);
}
/**
*
* @param cond {object} - 条件
* @param options {object}
* - schema {string} 数据库schema。
* - field {string} 聚合操作的列。
* @returns Promise
*/
async avg(cond = {}, options = {schema: null}) {
if (!options) options = {};
if (typeof cond === 'string') {
options.field = cond;
cond = {};
}
if (typeof options === 'string') options = {field: options};
this.throwNoFieldsError(options);
let m = await this._mschema(options.schema).where(cond).avg(options.field);
if (!options.to) return m;
return this._fmtNum(m, options);
}
/**
*
* @param cond {object} - 条件
* @param options {object}
* - schema {string} 数据库schema。
* - field {string} 聚合操作的列。
* @returns Promise
*/
async sum(cond = {}, options = {schema: null}) {
if (!options) options = {};
if (typeof cond === 'string') {
options.field = cond;
cond = {};
}
if (typeof options === 'string') options = {field: options};
this.throwNoFieldsError(options);
let m = await this._mschema(options.schema).where(cond).sum(options.field);
if (!options.to) return m;
return this._fmtNum(m, options);
}
quote(a) {
return this.orm.model().quote(a);
}
logSql(callback) {
return this.model().logSql(callback);
}
fetchSql(b=true) {
return this.model().fetchSql(b);
}
order(by, type = '') {
return this.model().order(by, type);
}
_checkFields(fields, options = {}) {
if (!fields || !Array.isArray(fields)) return {ok: false, errcode: 'TYPE_WRONG'};
let notin = [];
for (let k of fields) {
if (!options.dataIn && (k.indexOf('(') >= 0 || k.indexOf('as') > 0)) continue;
if (!this.table.column[k]) {
notin.push(k);
}
}
if (notin.length > 0) return {ok: false, notin, errcode: 'FIELD'};
return {ok: true};
}
/**
*
* @param options {object}
* - field {array|string} 导出的列。
* - pagesize {number} 分页大小。
* - where {object} where条件。
* - offset {number} 偏移量。
* @returns function*
* */
async dataOut(options = {}) {
let cond = options.where || {};
let total = await this.count(cond);
let pagesize = 1000;
if (options.pagesize && typeof options.pagesize === 'number' && options.pagesize > 1) {
pagesize = options.pagesize;
}
if (options.field && options.field !== '*') {
if (typeof options.field === 'string') {
options.field = options.field.split(',').filter(p => {
if (p.length > 0) return p.trim();
});
}
if (Array.isArray(options.field)) {
let st = this._checkFields(options.field);
if (!st.ok) delete options.field;
if (!st.ok) {
throw new Error(`无法导出不存在的列:${st.notin.join()}`);
}
} else {
delete options.field;
}
} else if (!options.field) {
options.field = '*';
}
let totalpage = parseInt(total / pagesize) + ((total % pagesize) ? 0 : 1);
let offset = (options.offset !== undefined && typeof options.offset === 'number') ? options.offset : 0;
if (offset < 0) offset = 0;
//默认返回生成器。
let self = this;
return async function * () {
let ret;
let fields = options.field || '*';
let schema = options.schema || null;
while (true) {
ret = await self.select(cond, {
offset,
pagesize,
field: fields,
schema
});
if (offset < total && ret.length > 0) {
yield ret;
} else {
break;
}
offset += ret.length;
}
};
} // dataOut end
/**
*
* @param callback {function} - 接受参数为导出的数据数组。
* @param options {object} - 和dataOut方法的选项一致。
*/
async dataOutHandle(callback, options = {}) {
if (!options || typeof options !== 'object') options = {}
if (!callback || typeof callback !== 'function')
throw new Error('需要传递callback用于处理导出的数据');
let dg = await this.dataOut(options);
let d = dg();
let r;
while (true) {
r = await d.next();
if (r.done) break;
await callback(r.value);
}
}
/**
*
* @param options {object}
* - data {array} 导入的数据。
* - mode {string} 导入模式,默认为'strict',支持'loose'模式。
* - update {string} 更新方式,默认为'delete-insert',支持 delete-insert|update|none。
* - schema {string} 导入数据库的schema。
* @returns Promise
*/
async dataIn(options = {}) {
if (!options.data || !Array.isArray(options.data) ) {
throw new Error('数据格式错误,请通过选项data传递要导入的数据,数据格式为数组。');
}
//loose or strict
if (options.mode === undefined) options.mode = 'strict';
//delete-insert update none
if (options.update === undefined) options.update = 'update';
let uid = this.primaryKey;
let ks;
let wrongs = [];
let notin = [];
let createList = [];
let updateList = [];
let idlist = [];
let idmap = {};
let val_tmp = '';
let is_primary_field = uid && (typeof uid === 'string');
for (let a of options.data) {
if (typeof a !== 'object' || Array.isArray(a) ) {
wrongs.push(a);
} else {
ks = this._checkFields(Object.keys(a), {dataIn: true});
if (!ks.ok) {
notin.push(a);
continue;
}
}
//主键id是字符串,说明只有一个字段作为主键,如果数据有主键则有可能是更新,否则就是创建。
if (is_primary_field) {
;(a[uid] === undefined) && createList.push(a);
if (a[uid] !== undefined) {
updateList.push(a);
idlist.push(a[uid]);
}
} else if (uid && Array.isArray(uid)) {
//联合主键把主键名字作为条件,只需要添加updateList,交给后续的程序检测处理。
updateList.push(a);
idmap = {};
uid.forEach(k => {
idmap[k] = a[k];
});
idlist.push(idmap);
}
}
if (options.callback && typeof options.callback !== 'function') {
options.callback = null;
}
if (options.mode === 'strict' && (wrongs.length > 0 || notin.length > 0))
return {
ok: false,
dataWrong: wrongs,
fieldWrong: notin
};
let ret = await this.transaction(async (db, ret) => {
if (createList.length > 0) {
options.callback && await options.callback(this, createList, {stage: 'before', command: 'insert', action: 'insert'})
await db.insertAll(createList);
options.callback && await options.callback(this, createList, {stage: 'after', command: 'insert', action: 'insert'})
}
let cond = {};
let updateData = {};
if (idlist.length > 0) {
switch (options.update) {
case 'delete-insert':
options.callback
&&
await options.callback(this, updateList, {stage: 'before', command: 'insert', action: 'delete-insert'});
if (is_primary_field) {
cond[uid] = idlist;
await db.where(cond).delete();
} else {
for (let a of idlist) await db.where(a).delete();
}
updateList.length > 0 && await db.insertAll(updateList);
updateList.length > 0 && options.callback
&&
await options.callback(this, updateList, {stage: 'after', command: 'insert', action: 'delete-insert'});
break;
//先检测是否存在然后确定是更新还是创建
case 'update':
case 'none':
default:
let chklist = [];
if (is_primary_field) {
cond[uid] = idlist;
chklist = await db.where(cond).select(uid);
} else {
chklist = [];
for (let a of idlist) {
val_tmp = await db.where(a).get(uid);
val_tmp && chklist.push(val_tmp);
}
}
let r;
let updInsert = [];
let realUpdate = [];
idmap = {};
chklist.length > 0 && chklist.forEach(a => {
if (is_primary_field)
idmap[ a[uid] ] = a;
else {
ks = [];
uid.forEach(x => { ks.push(a[x]) });
idmap[ ks.join() ] = a;
}
});
for (let d of updateList) {
if (is_primary_field) {
if (idmap[ d[uid] ]) realUpdate.push(d);
else updInsert.push(d);
} else {
ks = [];
uid.forEach(x => { ks.push(d[x]) });
if (idmap[ks.join()]) realUpdate.push(d);
else updInsert.push(d);
}
}
realUpdate.length > 0 && options.callback
&&
await options.callback(this, realUpdate, {stage: 'before', command: 'update', action: 'update'});
if (realUpdate.length > 0 && options.update === 'update') {
cond = {};
for (let d of realUpdate) {
if (is_primary_field) {
cond[uid] = d[uid];
updateData = {...d};
delete updateData[uid];
Object.keys(updateData).length > 0 && await db.where(cond).update(updateData);
} else {
uid.forEach(x => {cond[x] = d[x]});
await db.where(cond).update(d);
}
}
}
realUpdate.length > 0 && options.callback
&&
await options.callback(this, realUpdate, {stage: 'after', command: 'update', action: 'update'});
if (updInsert.length > 0) {
options.callback
&&
await options.callback(this, updInsert, {stage: 'before', command: 'insert', action: 'insert'});
await db.insertAll(updInsert);
options.callback
&&
await options.callback(this, updInsert, {stage: 'after', command: 'insert', action: 'insert'});
}
break;
}
}
}, options.schema || null);
ret.dataWrong = wrongs;
ret.fieldWrong = notin;
return ret;
}
/**
* @param {string} gby
* */
group(gby) {
return this.model().group(gby);
}
/**
*
* @param gby {string} - group操作的列,多个列使用 , 连接。
* @param options {object}
* - field string类型,返回的列,默认和参数gby一致。
* - order string类型,排序方式。
* - where 条件,使用object类型,参考where接口。
*/
async groupSelect(gby, options={}) {
let t = this.model().where(options.where || {}).group(gby);
options.order && (t = t.order(options.order));
return await t.select(options.field || gby);
}
/**
*
* @param callback {function} - async 声明的函数,接受参数:
* - db 数据库连接实例,参考Model部分。
* - ret object类型,result属性作为返回数据,使用ret.throwFailed方法抛出错误终止事物。
* @param schema {string}
* @returns
*/
async transaction (callback, schema = '') {
if (typeof callback !== 'function' || callback.constructor.name !== 'AsyncFunction') {
throw new Error(`transaction 执行的回调函数必须使用 async 声明。`);
}
return await this.orm.transaction(async (db, handle) => {
db = db.table(this.tableName);
let hdb = this.getPool(db);
let result = await callback(hdb, handle);
this.freePool(hdb);
return result;
}, schema);
}
/**
*
* Sync仅支持一种操作:从模型同步到数据库,方便在修改程序后,直接同步到数据库,
* 而如果要从数据库同步到程序文件,则需要独立出模型文件,这很麻烦,而且双向操作容易出问题。
* 因为Postgresql的复杂度,并且此工具主要作为Web服务场景,所以类型支持不会很全面,只支持常用几种类型,
* 而对于索引,不做复杂的支持,可以通过数据库完成。
* 目前来说,索引是为了简单化处理存在的,所以默认的,index字段使用数组。数组中的值就是要创建索引的字段。
* 而对于复杂的索引以及其他情况则需要自己处理。
* 这里创建的索引采用自动化的形式,也就是交给postgresql自行处理。
*
* table结构:
*
* {
* column : {
* id : {
* type : 'varchar(20)',
* notNull : true,
* default : ''
* },
* username : {
* type : 'varchar(40)',
* notNull : true,
* default : ''
* },
* passwd : {
* type : 'varchar(200)',
* notNull : true,
* default : ''
* }
* },
* index : [
* ],
* unique : [
* ]
*
* }
*
* 为了数据安全,检测到数据库没有字段会添加,但是如果程序没有编写字段,数据库有字段不会删除。
* 确定是否为字段重命名,需要提供一个oldName字段,只要存在此字段并且检测到当前oldName确实和存在的字段相同,则会进行重命名处理。
* 这时候,如果需要再次重命名,则可以修改字段的key值,并让oldName保存之前的key值。
*
*/
/**
*
* @param schema {string} - 要创建的schema名称。
*/
async createSchema (schema) {
return await this.db.query(`create schema if not exists ${schema}`);
}
_checkFixColumn() {
let col = '';
let illegal_count = 0;
let cobj;
let col_preg = /^[a-z][a-z0-9\_]{1,}$/i;
for (let k in this.table.column) {
col = k.toLowerCase()
/* if (forbidColumnName.indexOf(col) >= 0) {
console.error(`\x1b[2;31;47m!!!${this.tableName} column ${k} 命名和sql关键字冲突,请修改。\x1b[0m`);
process.exit(1);
illegal_count++;
continue;
} */
if (col !== k) {
setTimeout(() => {
console.error(`\x1b[2;31;47m!!!${this.tableName} 因为postgresql特点,column ${k}会被转换为小写,这容易导致一些问题,请在代码中修改字段名字为小写。\x1b[0m`);
}, 900);
illegal_count++;
continue;
}
if (!col_preg.test(k)) {
console.error(`\x1b[2;31;47m!! column ${k} 不符合要求,支持字母数字下划线,并且字母开头。\x1b[0m`);
process.exit(1);
}
cobj = this.table.column[k];
if (!cobj.type && !cobj.ref) {
console.error(`\x1b[2;31;47m!! column ${col} 没有设置type指定类型,也没有使用ref指定外键关联,请检查。\x1b[0m`);
process.exit(1);
}
}
return illegal_count > 0 ? false : true;
}
fmtColName(col) {
if (forbidColumnName.indexOf(col) >= 0) return `"${col}"`;
return col;
}
/**
*
* @param debug {boolean}
* - 调试模式,会输出sql以及其他提示信息。
* @param force {boolean}
* - 是否强制同步,默认为false,若为true则会强制把数据库改为和table结构一致。
*/
async sync (debug=false, force=false, dropNotExistCol=false) {
if (!this.table) {
console.error('没有table对象');
return false;
}
if (!this.tableName) {
console.error('tableName不能为空。');
return false;
}
debug && console.log(`检测数据表 ${this.tableName} 的column...`);
if (!this._checkFixColumn()) {
return false;
}
debug && console.log(`开始同步表结构(start to sync table) ${this.tableName}`);
if (this.table.column === undefined || typeof this.table.column !== 'object') {
throw new Error('column属性必须为object类型');
}
if (Object.keys(this.table.column).length <= 0) {
throw new Error('column没有定义字段');
}
this.schema_oid = null;
let oid = await this.db.query(`SELECT * FROM pg_namespace WHERE nspname = '${this.orm.schema}'`);
if (oid.rowCount > 0) {
this.schema_oid = oid.rows[0].oid;
}
//检测是否存在外键
let tmp_col, refarr, refmodel, refm, refModName;
for (let k in this.table.column) {
tmp_col = this.table.column[k];
if (tmp_col.ref) {
refarr = this._parseRef(tmp_col.ref, k);
refModName = (this.modelPath && (refarr[0].indexOf('/') < 0))
? (this.modelPath + '/' + refarr[0])
: refarr[0];
if (refModName.length < 4 || refModName.substring(refModName.length - 3) !== '.js') refModName += '.js';
refmodel = require(refModName);
refm = new refmodel(this.orm);
await refm.sync(debug, force);
tmp_col.type = refm.table.column[ refarr[1] ].type;
//外键暂时不支持跨越schema,尽管postgresql支持,这会导致不可预知的混乱。
tmp_col.references = `REFERENCES ${this.orm.schema}.${refm.tableName} (${refarr[1]})`;
if (tmp_col.refActionUpdate === undefined) tmp_col.refActionUpdate = 'cascade';
if (tmp_col.refActionDelete === undefined) tmp_col.refActionDelete = 'cascade';
if (tmp_col.refActionDelete) {
tmp_col.references += ' ON DELETE ' + tmp_col.refActionDelete;
}
if (tmp_col.refActionUpdate) {
tmp_col.references += ' ON UPDATE ' + tmp_col.refActionUpdate;
}
tmp_col.refconstraint = `${this.tableName}_${k}_fkey`;
}
}
let database = this.db.options.database;
let curTableName = `${this.orm.schema}.${this.tableName}`;
let sql = `select * from information_schema.tables where table_catalog=$1 and table_schema=$2 and table_name=$3`;
let r = await this.db.query(sql, [
database, this.orm.schema, this.tableName
]);
let qtag = randstring(12);
//没有表,需要创建
if (r.rowCount <= 0) {
sql = `create table if not exists ${curTableName} (`;
let tmp = '';
let pt = '';
for (let k in this.table.column) {
if (this.table.column[k].drop || this.table.column[k].ignore) {
continue;
}
if (this.primaryKey === k) {
sql += `${this.fmtColName(k)} ${this.table.column[k].type} primary key,`;
} else {
sql += `${this.fmtColName(k)} ${this.table.column[k].type} `;
tmp = this.table.column[k];
if (tmp.notNull !== false) {
sql += `not null `;
}
//自动检测默认值
pt = this._parseType(tmp.type);
if (tmp.default === undefined) {
if (this._isArray(tmp.type)) {
tmp.default = '{}';
} else if (this.numerics.indexOf(pt) >= 0) {
tmp.default = 0;
} else if (this.strings.indexOf(pt) >= 0) {
tmp.default = '';
}
}
if (tmp.default !== undefined) {
if (tmp.default === null) {
sql += 'default null ';
} else {
sql += `default $_${qtag}_$${tmp.default}$_${qtag}_$ `;
}
}
if (tmp.ref && tmp.references) {
sql += tmp.references;
}
sql += `,`;
}
}
//检测是否为联合主键
if (this.primaryKey && Array.isArray(this.primaryKey)) {
sql = `${sql.trim()} primary key(${this.primaryKey.join()}))`;
} else {
sql = `${sql.trim().substring(0, sql.length-1)})`;
}
if (debug) {
console.log(sql);
}
await this.db.query(sql);
await this._syncIndex(curTableName, debug);
await this._syncUnique(curTableName, debug);
await this._syncReferences(curTableName, debug);
return;
}
let fields = 'column_name,data_type,column_default,character_maximum_length,'
+'numeric_precision,numeric_scale,is_nullable';
r = await this.db.query(
`select ${fields} from information_schema.columns where table_name=$1 AND table_schema=$2 AND table_catalog=$3`,
[this.tableName, this.orm.schema, database]
);
let inf = {};
for (let i=0; i < r.rows.length; i++) {
inf[r.rows[i].column_name] = r.rows[i];
}
//若存在dropIndex但是不存在removeIndex则指向dropIndex
if (this.table.dropIndex && !(this.table.removeIndex)) {
this.table.removeIndex = this.table.dropIndex;
}
await this._syncColumn(inf, curTableName, debug, force, dropNotExistCol);
await this._syncIndex(curTableName, debug);
await this._syncUnique(curTableName, debug);
await this._removeIndex(curTableName, debug);
await this._syncReferences(curTableName, debug);
if (debug) {
console.log(' - 表结构同步完成 - ');
}
}
/**
*
* @param {object} data 数据对象。
* @param {boolean} quiet 默认为true,不抛出错误,而是删除不存在的列,为false检测到不存在的列会抛出错误。
* @returns this
*/
check(data, quiet = true) {
let cols = this.table.column;
for (let k in data) {
if (cols[k] === undefined) {
if (!quiet) {
throw new Error(`column ${k} 没有定义。`);
} else {
delete data[k];
}
}
}
return this;
}
/**
* 检测字段的执行流程:
* 检测是否需要重命名,如果重命名但是可能用户已经
* 通过数据库客户端进行操作并在这之后更改的程序,这时候不会进行更新。
* 检测是否存在,不存在则创建。
* 检测类型是否需要更改。
* 检测not null和default是否需要更改,必须在类型检测之后。
*
* typeLock 为true表示不进行类型更新。
*/
async _syncColumn(inf, curTableName, debug = false, force = false, dropNotExistCol = false) {
let qtag = randstring(12);
let pt = '';
let real_type = '';
let col = null;
let sql = '';
if (debug) {
console.log('-- 检测并同步columns(checking columns)...');
}
let renameTable = {};
for (let k in this.table.column) {
col = this.table.column[k];
if (col.ignore) {
continue;
}
if (col.drop) {
try {
sql = `alter table ${curTableName} drop column if exists ${this.fmtColName(k)}`;
await this.db.query(sql);
} catch (err) {
}
continue;
}
if (col.oldName && typeof col.oldName === 'string' && col.oldName.trim()) {
if (inf[k] === undefined && inf[col.oldName.trim()]) {
await this.db.query(`alter table ${curTableName} rename ${this.fmtColName(col.oldName)} to ${this.fmtColName(k)}`);
//保证后续的检测不会错误的创建字段。
inf[k] = inf[col.oldName.trim()];
//执行重命名之后,在强制更新模式,检测inf字段,oldName已经不在this.table.column中。
renameTable[col.oldName.trim()] = true;
}
}
pt = this._parseType(col.type);
real_type = this._realType(pt);
if (inf[k] === undefined) {
sql = `alter table ${curTableName} add column ${this.fmtColName(k)} ${col.type}`;
if (col.notNull !== false) {
sql += ` not null`;
}
if (col.default === undefined) {
if (this._isArray(col.type)) {
col.default = '{}';
} else if (this.numerics.indexOf(pt) >= 0) {
col.default = 0;
} else if (this.strings.indexOf(pt) >= 0) {
col.default = '';
}
}
if (col.default !== undefined) {
if (col.default === null) {
sql += ' default null';
} else {
sql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
}
}
debug && console.log(sql);
await this.db.query(sql);
continue;
}
if (col.typeLock) continue;
/**
* 如果没有在支持的类型中,则相当于是typeLock为true的结果,
* 因为这时候,无法判断真实的类型名称和默认值格式。
*/
if (real_type === null) {
continue;
}
if (this._compareType(inf[k], col, real_type) === false) {
/**
* 在涉及到隐含类型转换时,会自动转换,否则需要指定转换规则。
* 比如遇到问题是字符串类型 => 时间 | 数字
* 但是目前测试varchar转换任何其他非字符串类型都会出问题,根本就不支持,其using语法并不能解决问题。
*/
sql = `alter table ${curTableName} alter column ${this.fmtColName(k)} type ${col.type}`;
if (inf[k].data_type === 'text' || inf[k].data_type.indexOf('character') >= 0) {
if (this.strings.indexOf(this._parseType(col.type)) < 0) {
//sql += ` using ${k}::${col.type}`;
if (col.force) {
//强制更新,先创建临时表名,然后drop,最后改名。
sql = `alter table ${curTableName} drop column ${this.fmtColName(k)}`;
await this.db.query(sql);
sql = `alter table ${curTableName} add column ${this.fmtColName(k)} ${col.type}`;
if (col.notNull !== false) {
sql += ' not null';
}
if (col.default !== undefined) {
if (col.default === null) {
sql += ' default null ';
} else {
sql += ` default $_${qtag}_$${col.default}$_${qtag}_$`;
}
}
await this.db.query(sql);
col.changed = true;
continue;
} else {
console.error(` -- ${k} ${col.type} 从字符串类型转向其他类型无转换规则,或者设置force选项强制操作。`);
}
}
}
debug && console.log(sql);
try {
await this.db.query(sql);
col.changed = true;
} catch (err) {
console.error('Error:',err.message);
continue;
}
}
if (col.default !== undefined) {
let real_default = this._realDefault(k, col.default);
if (real_default !== inf[k].column_default) {
let default_value = col.default === null ? 'null' : `$_${qtag}_$${col.default}$_${qtag}_$`;
sql = `alter table ${curTableName} alter column ${this.fmtColName(k)} set default ${default_value}`;
await this.db.query(sql);
}
}
if (col.notNull === undefined || col.notNull) {
if (inf[k].is_nullable === 'YES') {
await this.db.query(`alter table ${curTableName} alter column ${this.fmtColName(k)} set not null`);
}
} else {
if (inf[k].is_nullable === 'NO') {
//难以把列恢复为允许null,请自行修改或重新创建列。
}
}
}
//force模式检测若有数据表字段,在程序中未定义,则直接删除。
if (dropNotExistCol) {
for (let k in inf) {
if (!this.table.column[k] && !renameTable[k]) {
await this.db.query(`alter table ${curTableName} drop column ${this.fmtColName(k)}`);
}
}
await this._autoRemoveIndex(debug);
}
}
async removeIndexes(debug=false, indexes=null) {
if (Array.isArray(debug)) {
indexes = debug;
debug = false;
}
let curTableName = `${this.orm.schema}.${this.tableName}`;
if (!indexes || !Array.isArray(indexes)) {
indexes = [].concat(this.table.index || [], this.table.unique || []);
}
return this._removeIndex(curTableName, debug, indexes);
}
async recoverIndex(debug=false) {
let curTableName = `${this.orm.schema}.${this.tableName}`;
await this._syncIndex(curTableName, debug);
await this._syncUnique(curTableName, debug);
}
async _autoRemoveIndex(debug=false) {
//在pg_indexes中不能带上schema
let sql = `select * from pg_indexes where `
+ `tablename='${this.tableName}' and schemaname = '${this.orm.schema}' `
+ `and indexname != '${this.tableName}_pkey';`;
let remove_index = [];
let remove_unique = [];
let r = await this.db.query(sql);
if (r.rowCount === 0) return false;
let indexTable = {};
let now_index_list = [];
for (let idx of r.rows) {
indexTable[idx.indexname] = idx;
now_index_list.push(idx.indexname);
}
let makeIndexName = (name) => {
return name.split(',').map(x => x.trim()).filter(p => p.length > 0).join('_');
}
let allIndex = {};
if (this.table.index && Array.isArray(this.table.index)) {
this.table.index.forEach(a => {
allIndex[`${this.tableName}_${makeIndexName(a)}_idx`] = a;
});
}
let allUnique = {};
if (this.table.unique && Array.isArray(this.table.unique)) {
this.table.unique.forEach(a => {
allUnique[`${this.tableName}_${makeIndexName(a)}_idx`] = a;
});
}
for (let ix of now_index_list) {
if (!allIndex[ix] && !allUnique[ix]) {
debug && console.log('自动删除不需要的索引(auto remove unnecessary index):', ix);
await this.db.query(`drop index ${this.orm.schema}.${ix};`);
}
}
for (let k in allIndex) {
if (!indexTable[k]) continue;
if (indexTable[k].indexdef.toLowerCase().indexOf('create index') !== 0) {
debug && console.log('自动删除类型不一致的索引(auto remove index with inconsistent type):', k);
await this.db.query(`drop index ${this.orm.schema}.${k};`);
}
}
for (let k in allUnique) {
if (!indexTable[k]) continue;
if (indexTable[k].indexdef.toLowerCase().indexOf('create unique') !== 0) {
debug && console.log('自动删除类型不一致的索引(auto remove index with inconsistent type):', k);
await this.db.query(`drop index ${this.orm.schema}.${k};`);
}
}
}
async _checkIndex(indname, debug = false) {
let indsplit = indname.split(',').map(x => x.trim()).filter(p => p.length > 0);
if (indsplit.length <= 0) {
debug && console.error(` \x1b[2;35m-- ${indname} 错误的索引项。\x1b[0m`);
return false;
}
let tmp = null;
for (let i = 0; i < indsplit.length; i++) {
tmp = this.table.column[ indsplit[i] ];
if (tmp === undefined || tmp.drop || tmp.ignore) {
if (debug) {
console.error(
` \x1b[2;35m-- 忽略 index ${indname} -- 请检查索引相关的column是否存在。\x1b[0m`
);
}
return false;
}
}
/**
* postgresql 会把联合索引多个字段使用 _ 连接。
*/
let indtext = indsplit.join('_');
//在pg_indexes中不能带上schema
let sql = `select * from pg_indexes where `
+ `tablename='${this.tableName}' and schemaname = '${this.orm.schema}' `
+ `and indexname = '${this.tableName}_${indtext}_idx'`;
let r = await this.db.query(sql);
if (r.rowCount > 0) {
return false;
}
return indsplit.join(',');
}
async _syncIndex(curTableName, debug = false) {
if (this.table.index === undefined) {
return;
}
if (!this.table.index || !Array.isArray(this.table.index) ) {
console.error('index 属性必须为数组类型,其中值为字符串');
return;
}
if (debug) {
console.log('-- 检查并同步索引(checking index)...');
}
let indname = '';
let indchk = null;
let checkColumn = (cname) => {
let nsplit = []
if (cname.indexOf(',') > 0) {
nsplit = cname.split(',').filter(p => p.length > 0);
} else {
nsplit.push(cname);
}
for (let n of nsplit) {
if (this.table.column[n] === undefined) return false;
}
return true;
}
let compareIndex = (a, b) => {
if (a.replaceAll(' ', '') === b.replaceAll(' ', '')) return true;
return false;
};
let checkInUnique = (name) => {
if (!Array.isArray(this.table.unique)) return true;
for (let a of this.table.unique) {
if (compareIndex(name, a)) return true;
}
return false;
};
for (let i = 0; i < this.table.index.length; i++) {
indname = this.table.index[i];
if (this.table.removeIndex !== undefined
&& Array.isArray(this.table.removeIndex)
&& this.table.removeIndex.indexOf(indname) >= 0)
{
continue;
}
if (checkColumn(indname) === false) {
console.error(`\x1b[2;35m-- ${indname} : 没有此列或包含不存在的列,无法创建索引。\x1b[0m`);
continue;
}
if (checkInUnique(indname)) {
console.error(`\x1b[2;35m-- ${indname} : 同时配置了index索引和unique索引,请修改。\x1b[0m`);
continue;
}
indchk = await this._checkIndex(indname, debug);
if (indchk === false) {
continue;
}
let ind_using = '';
if (this.table.column[indname]) {
if (this.table.column[indname].type.toLowerCase() === 'jsonb') {
ind_using = ' using gin';
} else if (this.table.column[indname].indexType) {
ind_using = ` using ${this.table.column[indname].indexType}`;
}
}
await this.db.query(`create index on ${curTableName} ${ind_using}(${this.fmtColName(indchk)})`);
}
}
async _syncUnique(curTableName, debug = false) {
if (this.table.unique === undefined) {
return;
}
if (!this.table.unique || !Array.isArray(this.table.unique) ) {
console.error('unique 属性必须为数组类型,其中值为字符串');
return;
}
if (debug) {
console.log('-- 检查并同步unique索引(checking unique index)...');
}
if (!this.table.unique || !Array.isArray(this.table.unique) ) {
return;
}
let indname = '';
let indchk = null;
for (let i = 0; i < this.table.unique.length; i++) {
indname = this.table.unique[i];
if (this.table.removeIndex !== undefined
&& Array.isArray(this.table.removeIndex)
&& this.table.removeIndex.indexOf(indname) >= 0)
{
continue;
}
indchk = await this._checkIndex(indname, debug);
if (indchk === false) {
continue;
}
await this.db.query(`create unique index on ${curTableName} (${this.fmtColName(indchk)})`);
}
}
async _removeIndex(curTableName, debug=false, indexs=null) {
let real_indexs = indexs || this.table.removeIndex;
if (!real_indexs || !Array.isArray(real_indexs) ) {
return false;
}
debug && console.log('try to remove the unnecessary index...');
let tind = '';
let sql = '';
let indchk = '';
for (let i = 0; i < real_indexs.length; i++) {
//表示没有此索引
indchk = await this._checkIndex(real_indexs[i]);
if (indchk) {
continue;
}
if (real_indexs[i].trim() === '') {
continue;
}
tind = real_indexs[i].split(',').map(x => x.trim()).filter(p => p.length > 0).join('_');
sql = `drop index ${curTableName}_${tind}_idx`;
try {
debug && console.log(sql);
await this.db.query(sql);
} catch (err) {
debug && console.error(err);
}
}
}
async _syncReferences(curTableName, debug = false) {
let tmp_col;
let refs_now_list = [];
let all_keys = [];
let ind = 0;
let qtag = Math.random().toString(16).substring(2)
for (let k in this.table.column) {
all_keys.push(`$_${qtag}_$${this.tableName}_${k}_fkey$_${qtag}_$`);
tmp_col = this.table.column[k];
if (!tmp_col.ref) continue;
refs_now_list.push(tmp_col.refconstraint);
}
let ref_keys = [];
let sql = '';
if (all_keys.length > 0) {
sql = `SELECT * FROM pg_constraint WHERE connamespace=${this.schema_oid} AND contype='f'`
+ ` AND conname IN (${all_keys.join(',')})`;
let r = await this.db.query(sql);
let refs = r.rows;
for (let a of refs) {
ref_keys.push(a.conname);
}
}
for (let a of ref_keys) {
if (refs_now_list.indexOf(a) < 0) {
sql = `alter table ${curTableName} drop constraint ${a}`;
debug && console.log('-- drop constraint:', sql);
await this.db.query(sql);
}
}
for (let k in this.table.column) {
tmp_col = this.table.column[k];
if (!tmp_col.ref) continue;
ind = ref_keys.indexOf(tmp_col.refconstraint);
if (ind >= 0 && tmp_col.changed) {
sql = `alter table ${curTableName} drop constraint ${tmp_col.refconstraint}`;
debug && console.log(sql);
await this.db.query(sql);
}
if (ind >= 0 && tmp_col.changed || ind < 0) {
sql = `alter table ${curTableName} add foreign key (${k}) ${tmp_col.references}`;
debug && console.log('-- create foreign key:', sql);
await this.db.query(sql);
}
}
}
_compareType(f, col, real_type) {
if (this.typeWithBrackets.indexOf(real_type) < 0) {
return (f.data_type === real_type);
}
//获取括号以及包含的字符串
let btstr = this._parseBrackets(col.type);
//字符串类型
if (f.data_type.indexOf('character') == 0) {
return (`${f.data_type}(${f.character_maximum_length})` === `${real_type}${btstr}`);
}
return (`${f.data_type}(${f.numeric_precision},${f.numeric_scale})` === `${real_type}${btstr}`);
}
_parseType(t) {
let br = t.indexOf('(');
if (br > 0) {
return t.substring(0,br).trim().toLowerCase();
}
br = t.indexOf('[');
if (br > 0) {
return t.substring(0,br).trim().toLowerCase();
}
return t.trim().toLowerCase();
}
_parseBrackets(t) {
let ind = t.indexOf('(');
if (ind < 0) {
return '';
}
return t.substring(ind).trim();
}
_realType(t) {
return this.dataTypeMap[t] || null;
}
_isArray(t) {
if (t.indexOf('[') > 0) return true;
return false;
}
_realDefault(t, val) {
if (t === 'boolean') {
return (val === 't' ? 'true' : 'false');
}
if (this.defaultWithType.indexOf(t) < 0) {
return val;
}
let rt = this.dataTypeMap[t];
return `${val === '' ? "''" : val}::${rt}`;
}
//refstr model:column
_parseRef(refstr, curColumn) {
if (refstr.indexOf(':') > 0) {
let i = refstr.length - 1;
while (i > 0) {
if (refstr[i] === ':') break;
i--;
}
if (i === refstr.length - 1) return [refstr, curColumn];
return [refstr.substring(0, i), refstr.substring(i+1)];
}
return [refstr, curColumn];
}
}
module.exports = PostgreModel;
JavaScript
1
https://gitee.com/daoio/psqlorm.git
git@gitee.com:daoio/psqlorm.git
daoio
psqlorm
psqlorm
master

搜索帮助