smartclick-pgsqlorm
Version:
orm for postgresql
327 lines (259 loc) • 8.63 kB
JavaScript
'use strict'
const fs = require('fs')
function fmt_table_name(name) {
let arr = name.split('')
let narr = []
arr.forEach((x, index) => {
if (/[A-Z]/.test(x) && index > 1 && /[a-z]/.test(arr[index-1])) {
narr.push('_')
}
narr.push(x.toLowerCase())
})
return narr.join('')
}
function makeTable(name, separate=false) {
let exp = separate ? 'module.exports =' : 'let table =';
let ust = separate ? `'use strict'\n` : '';
return `${ust}
/**
* @typedef {object} column
* @property {string} type - 类型
* @property {string} refActionDelete - 外键删除后的行为,可以设置为cascade,具体参考数据库文档。
* @property {string} refActionUpdate - 外键更新后的行为,可以设置为cascade,具体参考数据库文档。
* @property {string} ref - 外键引用,格式 ModelName:COLUMN,示例:'users:id'
* @property {string} indexType - 索引类型,需要指定索引具体类型时使用此属性。
* @property {string|number} default - 默认值。
* @property {boolean} notNull - 不允许为null,默认为true。
* @property {string} oldName - 原column名称,如果要重命名字段,需要oldName指定原有名字。
* @property {boolean} typeLock - 是否锁定类型,如果为true则会锁定类型,不会更新。
* @property {function|RegExp|array} validate - 数据验证,如果是函数类型,返回false表示验证失败。
*
* 如果指定ref,type会保持和外键引用的字段一致。
*/
/**
* @typeof {object} dataTypes
* @property {string} INT - 'int'
* @property {string} BIGINT - 'bigint'
* @property {string} SMALLINT - 'smallint'
* @property {string} TEXT - 'text'
* @property {string} JSONB - 'jsonb'
* @property {string} TIME - 'time'
* @property {string} DATE - 'date'
* @property {string} TIMESTAMP - 'timestamp'
* @property {string} TIMESTAMPZ - 'timestamp with zone'
* @property {string} BOOLEAN - 'boolean'
* @property {string} BYTE - 'bytea'
* @property {string} BID - 'bigint'
* @property {string} BIGSERIAL - 'bigserial'
* @property {string} ID - 'varchar(16)'
* @property {string} UID - 'varchar(18)'
* @property {string} OPENID - 'varchar(32)'
* @property {function} CHAR - CHAR(50) 返回 'char(50)'
* @property {function} STRING - STRING(50) 返回 'varchar(50)'
* @property {function} NUMBER - NUMBER(9,3) 返回 'numeric(9,3)'
* @property {function} NUMERIC - NUMERIC(9,3) 返回 'numeric(9,3)'
* @property {function} ARRAY - ARRAY('int') 返回 'int[]'
*
* dataTypes对常用的类型提供了一个中间层:
* 它的主要目的是提供统一格式并尽可能防止出错:尽可能避免大小写不一致、前后有空格等问题。
*/
${separate ? 'const dataTypes = require(\'psqlorm\').dataTypes\n' : ''}
${exp} {
column: {
/**
* @type {column}
* id默认是主键,不需要再加入到unique索引。
* */
id: {
type: dataTypes.ID
},
/**
* @type {column}
* */
name: {
type: dataTypes.STRING(30),
default: ''
},
detail: {
type: dataTypes.STRING(200),
default: ''
},
/**
* @type {column}
* */
create_time: {
type: dataTypes.BIGINT,
default: 0,
//执行insert时自动生成时间戳
timestamp: 'insert'
},
/**
* @type {column}
* */
update_time: {
type: dataTypes.BIGINT,
default: 0,
//执行update时自动生成时间戳
timestamp: 'update'
},
},
//主键,字符串类型会按照增长序列算法自动生成。
primaryKey: 'id',
//索引
index: [
'create_time',
'update_time'
],
//唯一索引,注意:主键本身就是唯一索引,不必在此重复。
//联合唯一索引使用 , 分隔,示例:'name,orgid'
unique: [
]
}
`
}
let example_code = `
//示例:定义update、delete、insert的触发器,触发器在执行sql之后才会执行。
/*
triggerInsert (tg) {
console.log(tg);
}
triggerDelete (tg) {
console.log(tg);
}
triggerUpdate (tg) {
console.log(tg);
}
*/
//执行触发器需要显式调用trigger或在事务中调用triggerCommit。
//只有调用trigger的sql执行才会执行触发器函数。
//在事务中,trigger()会根据状态标记自动识别是执行完sql触发还是事务提交以后再触发。。
//示例函数,请根据实际需求修改代码。
async create(data) {
return this.returning(['id', 'name']).insert(data)
}
/**
* ------ 事务处理示例函数
* ------ 示例代码仅作基本使用的参考...
*/
async example_transaction(data) {
//只有使用参数传递的db执行sql才是事务操作。
/**
* @param {Model} db db是PostgreModel实例。
* @param {Object} handle handle用于设置事务执行状态或返回数据。
*/
let ret = await this.transaction(async (db, handle) => {
let r = await db.where('name = ?', ['']).select()
if (r.length === 0) {
//事务执行失败,抛出错误。
handle.throwFailed('没有查询到name为空的数据。')
}
/*
如果不使用bind绑定,则执行sql就不是事务操作。
你可以只用db,并指定model:
db.model('users').where({role: 'test'}).update(data)
*/
/*
let users = this.getModel('users').bind(db)
await users.where({role: 'test'}).update(data)
*/
//此返回值将作为事务的返回结果,其等效形式为:handle.result = 'ok'
return 'ok'
})
//如果执行成功,输出为:true ok
console.log(ret.ok, ret.result)
return ret
}
`
function makeModel (name, orgname, separate=false, exampleCode='') {
let imp_table = `const table = require('./tables/${orgname}.js')\n`
let tableCode = separate ? imp_table : makeTable(name, separate)
let mstr = `'use strict'
const {PostgreModel, dataTypes} = require('psqlorm')
${tableCode}
class ${name} extends PostgreModel {
constructor (pqorm) {
//必须存在并且写在最前面。
super(pqorm)
//主键id前缀,建议不要超过2字符,请确保前缀和idLen的长度 <= 数据库字段的最大长度。
this.idPre = ''
//id的长度,默认为16,为保证有序增长,建议id长度不要低于16。
//this.idLen = 16
//数据表真正的名称,注意:postgresql不支持表名大写,更改名称请使用小写字母。
this.tableName = '${fmt_table_name(name)}'
this.table = table
this.columns = Object.keys(this.table.column)
}
//一些在构造函数执行后才可以初始化的操作,写在init函数中。
async init() {
}
${exampleCode}
}
module.exports = ${name}
`
return mstr
}
let name_preg = /^[a-z][a-z0-9_]{1,60}$/i
function checkName (name) {
return name_preg.test(name)
}
let mdir = 'model'
let mlist = []
let separate = false
let make_example_code = false
for (let i = 2; i < process.argv.length; i++) {
if (process.argv[i].indexOf('--mdir=') === 0) {
let t = process.argv[i].substring( '--mdir='.length )
if (t.length > 0) {
mdir = t
}
continue
}
if (['-s', '--separate'].indexOf(process.argv[i]) >= 0) {
separate = true
continue
}
if (['-e', '--example'].indexOf(process.argv[i]) >= 0) {
make_example_code = true
continue
}
mlist.push(process.argv[i])
}
try {
fs.accessSync(mdir)
} catch (err) {
fs.mkdirSync(mdir)
}
let cpath
let table_dir
for (let c of mlist) {
if (c.indexOf('/') >= 0) {
let arr = c.split('/').filter(p => p.length > 0)
c = arr[arr.length - 1]
}
if (!checkName(c)) {
console.error(`${c} 不符合命名要求。(the name is illegal.)\n`
+ `要求至少2个字符,最多60字符,字母开头,支持:字母 数字 和 _`)
continue
}
cpath = `${mdir}/${c}.js`
table_dir = `${mdir}/tables`
try {
fs.accessSync(cpath)
console.error(`${c}.js已经存在(${c}.js already exist).`)
continue
} catch (err) {}
if (separate) {
try {
fs.accessSync(table_dir)
} catch (err) {
fs.mkdirSync(table_dir)
}
}
try {
fs.writeFileSync(cpath, makeModel(`${c[0].toUpperCase()}${c.substring(1)}`, c, separate, make_example_code ? example_code : ''), {encoding: 'utf8'})
separate && fs.writeFileSync(table_dir+`/${c}.js`, makeTable(fmt_table_name(c), separate), {encoding: 'utf8'})
} catch (err) {
console.error(err)
}
}