├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── lib ├── parser.js ├── query.js └── schema.js ├── package.json └── test ├── index.js └── lib ├── parser.js ├── query.js └── schema.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "think" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | sudo: false 5 | script: 6 | - "npm test" 7 | after_success: 8 | - 'npm install coveralls && ./node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ThinkJS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # think-model-mysql 2 | 3 | [![npm](https://img.shields.io/npm/v/think-model-mysql.svg?style=flat-square)]() 4 | [![Travis](https://img.shields.io/travis/thinkjs/think-model-mysql.svg?style=flat-square)]() 5 | [![Coveralls](https://img.shields.io/coveralls/thinkjs/think-model-mysql/master.svg?style=flat-square)]() 6 | [![David](https://img.shields.io/david/thinkjs/think-model-mysql.svg?style=flat-square)]() 7 | 8 | 9 | Mysql model adapter 10 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare class ThinkModelMysql { 2 | 3 | } 4 | 5 | export = ThinkModelMysql; 6 | 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Abstract = require('think-model-abstract'); 2 | const Query = require('./lib/query.js'); 3 | const Schema = require('./lib/schema.js'); 4 | const Parser = require('./lib/parser.js'); 5 | 6 | module.exports = class Mysql extends Abstract {}; 7 | 8 | module.exports.Query = Query; 9 | module.exports.Schema = Schema; 10 | module.exports.Parser = Parser; 11 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | const helper = require('think-helper'); 2 | const { Parser } = require('think-model-abstract'); 3 | 4 | module.exports = class MysqlParser extends Parser { 5 | /** 6 | * parse key 7 | * @param {String} key [] 8 | * @return {String} [] 9 | */ 10 | parseKey(key = '') { 11 | key = key.trim(); 12 | if (helper.isEmpty(key)) return ''; 13 | if (helper.isNumberString(key)) return key; 14 | if (!(/[,'"*()`.\s]/.test(key))) { 15 | key = '`' + key + '`'; 16 | } 17 | return key; 18 | } 19 | /** 20 | * escape string 21 | * @param {String} str [] 22 | * @return {String} [] 23 | */ 24 | escapeString(str) { 25 | if (!str) return ''; 26 | 27 | // eslint-disable-next-line no-control-regex 28 | return str.replace(/[\0\n\r\b\t\\'"\x1a]/g, s => { 29 | switch (s) { 30 | case '\0': 31 | return '\\0'; 32 | case '\n': 33 | return '\\n'; 34 | case '\r': 35 | return '\\r'; 36 | case '\b': 37 | return '\\b'; 38 | case '\t': 39 | return '\\t'; 40 | case '\x1a': 41 | return '\\Z'; 42 | default: 43 | return '\\' + s; 44 | } 45 | }); 46 | } 47 | 48 | /** 49 | * get insert sql 50 | * @param {Object} options 51 | */ 52 | buildInsertSql(options) { 53 | const isUpdate = helper.isObject(options.update) || helper.isArray(options.update); 54 | if (options.replace || !isUpdate) { 55 | return super.buildInsertSql(options); 56 | } 57 | 58 | const table = this.parseTable(options.table); 59 | const values = options.values[0] !== '(' ? `(${options.values})` : options.values; 60 | 61 | let sets = []; 62 | if (helper.isArray(options.update)) { 63 | sets = options.update.map(field => { 64 | field = this.parseKey(field); 65 | return field + '=' + `VALUES(${field})`; 66 | }); 67 | } else { 68 | for (const key in options.update) { 69 | const value = this.parseValue(options.update[key]); 70 | if (helper.isString(value) || helper.isNumber(value) || helper.isBoolean(value)) { 71 | sets.push(this.parseKey(key) + '=' + value); 72 | } 73 | } 74 | } 75 | 76 | const duplicateUpdate = sets.length ? ' ON DUPLICATE KEY UPDATE ' + sets.join(',') : ''; 77 | const lock = this.parseLock(options.lock); 78 | const comment = this.parseComment(options.comment); 79 | 80 | return `INSERT INTO ${table} (${options.fields}) VALUES ${values}${duplicateUpdate}${lock}${comment}`; 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /lib/query.js: -------------------------------------------------------------------------------- 1 | const { Query } = require('think-model-abstract'); 2 | const Mysql = require('think-mysql'); 3 | const helper = require('think-helper'); 4 | /** 5 | * mysql query 6 | */ 7 | module.exports = class MysqlQuery extends Query { 8 | select(options, cache) { 9 | if (!this.config.jsonFormat) { 10 | return super.select(options, cache); 11 | } 12 | 13 | return Promise.all([ 14 | super.select(options, cache), 15 | this.schema.getSchema() 16 | ]).then(([data, schema]) => { 17 | const keys = Object.keys(schema).filter(key => schema[key].tinyType === 'json'); 18 | (Array.isArray(data) ? data : [data]).forEach(row => { 19 | keys.filter(key => row[key] !== undefined && !helper.isArray(row[key]) && !helper.isObject(row[key])).forEach(key => { 20 | row[key] = JSON.parse(row[key]); 21 | }); 22 | }); 23 | return data; 24 | }); 25 | } 26 | 27 | /** 28 | * get socket 29 | * @param {String|Object} sql 30 | */ 31 | socket(sql) { 32 | return super.socket(sql, Mysql); 33 | } 34 | /** 35 | * execute sql 36 | */ 37 | execute(sqlOptions, connection) { 38 | return super.execute(sqlOptions, connection).then(data => { 39 | if (data.insertId) { 40 | this.lastInsertId = data.insertId; 41 | } 42 | return data.affectedRows || 0; 43 | }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /lib/schema.js: -------------------------------------------------------------------------------- 1 | const helper = require('think-helper'); 2 | const { Schema } = require('think-model-abstract'); 3 | const Debounce = require('think-debounce'); 4 | 5 | const debounce = new Debounce(); 6 | 7 | const SCHEMAS = {}; 8 | 9 | /** 10 | * mysql Schema 11 | */ 12 | module.exports = class MysqlSchema extends Schema { 13 | _getItemSchemaValidate(fieldData) { 14 | const validate = {}; 15 | switch (fieldData.tinyType) { 16 | case 'tinyint': 17 | validate.int = { min: 0, max: 255 }; 18 | break; 19 | case 'smallint': 20 | validate.int = { min: fieldData.unsigned ? 0 : -32768, max: 32767 }; 21 | break; 22 | case 'int': 23 | validate.int = { min: fieldData.unsigned ? 0 : -2147483648, max: 2147483647 }; 24 | break; 25 | // case 'bigint': 26 | // validate.int = {min: fieldData.unique ? 0 : -9223372036854775808, max: 9223372036854775807}; 27 | // break; 28 | case 'date': 29 | validate.date = true; 30 | break; 31 | }; 32 | return validate; 33 | } 34 | _parseItemSchema(item) { 35 | item.type = item.type || 'varchar(100)'; 36 | const pos = item.type.indexOf('('); 37 | item.tinyType = (pos === -1 ? item.type : item.type.slice(0, pos)).toLowerCase(); 38 | if (item.default && !helper.isFunction(item.default) && item.tinyType.indexOf('int') > -1) { 39 | item.default = parseInt(item.default); 40 | } 41 | if (item.type.indexOf('unsigned') > -1) { 42 | item.unsigned = true; 43 | item.type = item.type.replace('unsigned', '').trim(); 44 | } 45 | if (!item.validate) { 46 | item.validate = this._getItemSchemaValidate(item); 47 | } 48 | return item; 49 | } 50 | /** 51 | * get table schema 52 | * @param {String} table 53 | */ 54 | getSchema(table = this.table) { 55 | const cacheKey = `${this.config.database}.${table}`; 56 | if (SCHEMAS[cacheKey]) return Promise.resolve(SCHEMAS[cacheKey]); 57 | return debounce.debounce(`getTable${table}Schema`, () => { 58 | const columnSql = `SHOW COLUMNS FROM ${this.parser.parseKey(table)}`; 59 | return this.query.query(columnSql).then(data => { 60 | let ret = {}; 61 | data.forEach(item => { 62 | ret[item.Field] = { 63 | name: item.Field, 64 | type: item.Type, 65 | required: item.Null === 'NO', 66 | default: '', 67 | primary: item.Key === 'PRI', 68 | unique: item.Key === 'UNI', 69 | autoIncrement: item.Extra.toLowerCase() === 'auto_increment' 70 | }; 71 | }); 72 | ret = helper.extend(ret, this.schema); 73 | for (const key in ret) { 74 | ret[key] = this._parseItemSchema(ret[key]); 75 | } 76 | SCHEMAS[cacheKey] = ret; 77 | return ret; 78 | }); 79 | }); 80 | } 81 | /** 82 | * parse type 83 | * @param {String} tinyType 84 | * @param {Mixed} value 85 | */ 86 | parseType(tinyType, value) { 87 | if (tinyType === 'enum' || tinyType === 'set' || tinyType === 'bigint' || tinyType === 'bigint unsigned') return value; 88 | if (tinyType.indexOf('int') > -1) return parseInt(value, 10) || 0; 89 | if (['double', 'float', 'decimal'].indexOf(tinyType) > -1) return parseFloat(value, 10) || 0; 90 | if (tinyType === 'bool') return value ? 1 : 0; 91 | if (this.config.jsonFormat && tinyType === 'json') { 92 | return helper.isNullOrUndefined(value) ? null : JSON.stringify(value); 93 | } 94 | return value; 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "think-model-mysql", 3 | "version": "1.1.7", 4 | "description": "Mysql adapter for ThinkJS 3.x", 5 | "scripts": { 6 | "test": "npm run lint && npm run test-cov", 7 | "test-cov": "nyc ava test/ && nyc report --reporter=html", 8 | "lint": "eslint ./lib index.js", 9 | "prepublish": "npm test" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/thinkjs/think-model-mysql.git" 14 | }, 15 | "keywords": [ 16 | "thinkjs", 17 | "orm", 18 | "mysql", 19 | "adapter", 20 | "model" 21 | ], 22 | "author": "lizheming", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/thinkjs/think-model-mysql/issues" 26 | }, 27 | "homepage": "https://github.com/thinkjs/think-model-mysql#readme", 28 | "dependencies": { 29 | "think-debounce": "^1.0.4", 30 | "think-helper": "^1.1.2", 31 | "think-model-abstract": "^1.2.2", 32 | "think-mysql": "^1.2.4" 33 | }, 34 | "devDependencies": { 35 | "ava": "^0.19.1", 36 | "eslint": "^4.19.1", 37 | "eslint-config-think": "^1.0.1", 38 | "muk": "^0.5.3", 39 | "nyc": "^10.3.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const {test} = require('ava'); 2 | test.todo('todo'); -------------------------------------------------------------------------------- /test/lib/parser.js: -------------------------------------------------------------------------------- 1 | const ava = require('ava'); 2 | const helper = require('think-helper'); 3 | const Base = require('../../lib/parser'); 4 | 5 | ava.test('escapeString is function', t => { 6 | const instance = new Base(); 7 | t.true(helper.isFunction(instance.escapeString)); 8 | }); 9 | 10 | ava.test('escapeString, empty', t => { 11 | const instance = new Base(); 12 | const data = instance.escapeString(); 13 | t.is(data, ''); 14 | }); 15 | 16 | ava.test('escapeString, \\n', t => { 17 | const instance = new Base(); 18 | const data = instance.escapeString('\n'); 19 | t.is(data, '\\n'); 20 | }); 21 | 22 | ava.test('escapeString, \\0', t => { 23 | const instance = new Base(); 24 | const data = instance.escapeString('\0'); 25 | t.is(data, '\\0'); 26 | }); 27 | 28 | ava.test('escapeString, \\r', t => { 29 | const instance = new Base(); 30 | const data = instance.escapeString('\r'); 31 | t.is(data, '\\r'); 32 | }); 33 | 34 | ava.test('escapeString, \\b', t => { 35 | const instance = new Base(); 36 | const data = instance.escapeString('\b'); 37 | t.is(data, '\\b'); 38 | }); 39 | 40 | ava.test('escapeString, \\t', t => { 41 | const instance = new Base(); 42 | const data = instance.escapeString('\t'); 43 | t.is(data, '\\t'); 44 | }); 45 | 46 | ava.test('escapeString, \\Z', t => { 47 | const instance = new Base(); 48 | const data = instance.escapeString('\u001a'); 49 | t.is(data, '\\Z'); 50 | }); 51 | 52 | ava.test('escapeString, \\"', t => { 53 | const instance = new Base(); 54 | const data = instance.escapeString('"'); 55 | t.is(data, '\\"'); 56 | }); 57 | 58 | ava.test('parseKey is function', t => { 59 | const cases = [ 60 | ['key', '`key`'], 61 | [' ', ''], 62 | [' 3 ', '3'], 63 | ['3,', '3,'], 64 | ['3\'', '3\''], 65 | ['3"', '3"'], 66 | ['3*', '3*'], 67 | ['3(', '3('], 68 | ['3)', '3)'], 69 | ['3 3', '3 3'], 70 | ['`3`', '`3`'], 71 | ['3.3', '3.3'], 72 | ['li.zheming', 'li.zheming'] 73 | ]; 74 | t.plan(cases.length); 75 | const instance = new Base(); 76 | cases.forEach(([param, expect]) => t.is(instance.parseKey(param), expect)); 77 | }); 78 | 79 | ava.test('buildInsertSql with super', t => { 80 | const instance = new Base(); 81 | instance.__proto__.__proto__.buildInsertSql = function() { 82 | t.pass(); 83 | return 'lizheming'; 84 | }; 85 | 86 | const params = [ 87 | [{}, null], 88 | [{}, true] 89 | ]; 90 | 91 | t.plan(params.length * 2); 92 | params.forEach(param => 93 | t.is(instance.buildInsertSql.apply(instance, param), 'lizheming') 94 | ); 95 | }); 96 | 97 | ava.test('buildInsertSql with array update', t => { 98 | const instance = new Base(); 99 | const options = { 100 | table: 'user', 101 | fields: 'id, name, email', 102 | values: '1, "lizheming", "i@imnerd.org"', 103 | update: ['id', 'title'], 104 | lock: 'lock', 105 | comment: 'comment' 106 | }; 107 | 108 | instance.parseTable = function(table) { 109 | t.is(table, options.table); 110 | return table; 111 | }; 112 | 113 | instance.parseKey = function(key) { 114 | return '$' + key + '$'; 115 | }; 116 | 117 | instance.parseLock = function(lock) { 118 | t.is(lock, 'lock'); 119 | return ''; 120 | }; 121 | 122 | instance.parseComment = function(comment) { 123 | t.is(comment, 'comment'); 124 | return ''; 125 | }; 126 | 127 | t.is( 128 | instance.buildInsertSql(options), 129 | 'INSERT INTO user (id, name, email) VALUES (1, "lizheming", "i@imnerd.org") ON DUPLICATE KEY UPDATE $id$=VALUES($id$),$title$=VALUES($title$)' 130 | ); 131 | }); 132 | 133 | ava.test('buildInsertSql with object update', t => { 134 | const instance = new Base(); 135 | const options = { 136 | table: 'user', 137 | fields: 'id, name, email', 138 | values: '(1, "lizheming", "i@imnerd.org")', 139 | update: { 140 | name: 'lizheming111', 141 | title: { a: 1 } 142 | }, 143 | lock: 'lock', 144 | comment: 'comment' 145 | }; 146 | 147 | instance.parseTable = function(table) { 148 | t.is(table, options.table); 149 | return table; 150 | }; 151 | 152 | instance.parseKey = function(key) { 153 | return '$' + key + '$'; 154 | }; 155 | 156 | instance.parseValue = function(value) { 157 | if (typeof value === 'string') { 158 | t.is(value, 'lizheming111'); 159 | return '`' + value + '`'; 160 | } else { 161 | t.deepEqual(value, { a: 1 }); 162 | return value; 163 | } 164 | }; 165 | 166 | instance.parseLock = function(lock) { 167 | t.is(lock, 'lock'); 168 | return ' lock2'; 169 | }; 170 | 171 | instance.parseComment = function(comment) { 172 | t.is(comment, 'comment'); 173 | return ' comment2'; 174 | }; 175 | 176 | t.is( 177 | instance.buildInsertSql(options), 178 | 'INSERT INTO user (id, name, email) VALUES (1, "lizheming", "i@imnerd.org") ON DUPLICATE KEY UPDATE $name$=`lizheming111` lock2 comment2' 179 | ); 180 | }); 181 | 182 | ava.test('buildInsertSql with empty update', t => { 183 | const instance = new Base(); 184 | const options = { 185 | table: 'user', 186 | fields: 'id, name, email', 187 | values: '(1, "lizheming", "i@imnerd.org")', 188 | update: { 189 | // name: 'lizheming111', 190 | title: { a: 1 } 191 | }, 192 | lock: 'lock', 193 | comment: 'comment' 194 | }; 195 | 196 | instance.parseLock = instance.parseComment = () => ''; 197 | 198 | t.is( 199 | instance.buildInsertSql(options), 200 | 'INSERT INTO `user` (id, name, email) VALUES (1, "lizheming", "i@imnerd.org")' 201 | ); 202 | }); 203 | -------------------------------------------------------------------------------- /test/lib/query.js: -------------------------------------------------------------------------------- 1 | const ava = require('ava'); 2 | const helper = require('think-helper'); 3 | const Base = require('../../lib/query'); 4 | const Parser = require('../../lib/parser'); 5 | 6 | ava.test('select jsonFormat false', t => { 7 | t.plan(2); 8 | 9 | const instance = new Base(); 10 | instance.__proto__.__proto__.select = function(options, cache) { 11 | t.is(options, 1); 12 | t.is(cache, 2); 13 | } 14 | instance.select(1, 2); 15 | }) 16 | 17 | ava.test('select jsonFormat true', async t => { 18 | t.plan(2); 19 | 20 | const instance = new Base({jsonFormat: true}); 21 | instance.schema = { 22 | getSchema() { 23 | return { 24 | title: { 25 | tinyType: 'varchar' 26 | }, 27 | content: { 28 | tinyType: 'varchar' 29 | }, 30 | json: { 31 | tinyType: 'json' 32 | } 33 | }; 34 | } 35 | } 36 | instance.__proto__.__proto__.select = function() { 37 | return {title: 'hello', content: 'world', json: JSON.stringify([1,2,3,4])}; 38 | } 39 | const data = await instance.select(); 40 | t.deepEqual(data, {title: 'hello', content: 'world', json: [1,2,3,4]}); 41 | 42 | 43 | instance.__proto__.__proto__.select = function() { 44 | return [{title: 'hello', content: 'world', json: JSON.stringify([1,2,3,4])}]; 45 | } 46 | const data2 = await instance.select(); 47 | t.deepEqual(data2, [{title: 'hello', content: 'world', json: [1,2,3,4]}]); 48 | }); 49 | 50 | ava.test('socket is function', t => { 51 | const instance = new Base(); 52 | t.true(helper.isFunction(instance.socket)); 53 | }); 54 | 55 | ava.test('parser is getter', t => { 56 | const instance = new Base(); 57 | instance.parser = new Parser(); 58 | const parser = instance.parser; 59 | t.true(parser instanceof Parser, true); 60 | }); 61 | 62 | ava.test('parser is getter 2', t => { 63 | const instance = new Base(); 64 | instance.parser = new Parser(); 65 | const parser = instance.parser; 66 | const parser2 = instance.parser; 67 | t.true(parser instanceof Parser, true); 68 | t.true(parser === parser2, true); 69 | }); 70 | 71 | ava.test('query', async t => { 72 | t.plan(2); 73 | 74 | const instance = new Base(); 75 | instance.socket = t => { 76 | return { 77 | query: function(sql) { 78 | return Promise.resolve(sql); 79 | } 80 | }; 81 | }; 82 | const data = await instance.query('SELECT * FROM think_user'); 83 | t.is(data, 'SELECT * FROM think_user'); 84 | t.is(instance.lastSql, 'SELECT * FROM think_user'); 85 | }); 86 | 87 | ava.test('execute', async t => { 88 | t.plan(2); 89 | 90 | const instance = new Base(); 91 | instance.socket = t => { 92 | return { 93 | execute: function(sql) { 94 | return Promise.resolve({ 95 | insertId: 1000, 96 | affectedRows: 10 97 | }); 98 | } 99 | }; 100 | }; 101 | const data = await instance.execute('DELETE FROM think_user'); 102 | t.is(data, 10); 103 | t.is(instance.lastInsertId, 1000); 104 | }); 105 | 106 | ava.test('execute, empty return', async t => { 107 | t.plan(2); 108 | 109 | const instance = new Base(); 110 | instance.socket = t => { 111 | return { 112 | execute: function(sql) { 113 | return Promise.resolve({ 114 | }); 115 | } 116 | }; 117 | }; 118 | const data = await instance.execute('DELETE FROM think_user'); 119 | t.is(data, 0); 120 | t.is(instance.lastInsertId, 0); 121 | }); 122 | 123 | // ava.test('close', t => { 124 | // const instance = new Base({buffer_tostring: true}); 125 | // let flag = false; 126 | // instance._socket = { 127 | // close: t => { 128 | // flag = true; 129 | // } 130 | // }; 131 | // instance.close(); 132 | // t.is(flag, true); 133 | // }); 134 | 135 | // ava.test('close', t => { 136 | // const instance = new Base({buffer_tostring: true}); 137 | // const flag = false; 138 | // instance.close(); 139 | // t.is(flag, false); 140 | // }); 141 | 142 | // ava.test('startTrans', async t => { 143 | // const instance = new Base(); 144 | // let flag = false; 145 | // instance.execute = function(sql) { 146 | // t.is(sql, 'START TRANSACTION'); 147 | // flag = true; 148 | // return Promise.resolve(); 149 | // }; 150 | // const data = await instance.startTrans(); 151 | // t.true(flag, true); 152 | // instance.transTimes = 1; 153 | // }); 154 | 155 | // ava.test('startTrans, is started', async t => { 156 | // const instance = new Base(); 157 | // instance.transTimes = 1; 158 | // let flag = false; 159 | // instance.execute = function(sql) { 160 | // t.is(sql, 'START TRANSACTION'); 161 | // flag = true; 162 | // return Promise.resolve(); 163 | // }; 164 | // const data = await instance.startTrans(); 165 | // t.is(flag, false); 166 | // instance.transTimes = 1; 167 | // }); 168 | 169 | // ava.test('commit, not start', async t => { 170 | // const instance = new Base(); 171 | // let flag = false; 172 | // instance.execute = function(sql) { 173 | // t.is(sql, 'ROLLBACK'); 174 | // flag = true; 175 | // return Promise.resolve(); 176 | // }; 177 | // const data = await instance.commit(); 178 | // t.false(flag); 179 | // instance.transTimes = 0; 180 | // }); 181 | 182 | // ava.test('commit', async t => { 183 | // const instance = new Base(); 184 | // instance.transTimes = 1; 185 | // let flag = false; 186 | // instance.execute = function(sql) { 187 | // t.is(sql, 'COMMIT'); 188 | // flag = true; 189 | // return Promise.resolve(); 190 | // }; 191 | // const data = await instance.commit(); 192 | // t.true(flag); 193 | // instance.transTimes = 0; 194 | // }); 195 | 196 | // ava.test('rollback, not start', async t => { 197 | // const instance = new Base(); 198 | // let flag = false; 199 | // instance.execute = function(sql) { 200 | // t.is(sql, 'ROLLBACK'); 201 | // flag = true; 202 | // return Promise.resolve(); 203 | // }; 204 | // const data = await instance.rollback(); 205 | // t.false(flag); 206 | // instance.transTimes = 0; 207 | // }); 208 | 209 | // ava.test('rollback', async t => { 210 | // const instance = new Base(); 211 | // instance.transTimes = 1; 212 | // let flag = false; 213 | // instance.execute = function(sql) { 214 | // t.is(sql, 'ROLLBACK'); 215 | // flag = true; 216 | // return Promise.resolve(); 217 | // }; 218 | // const data = await instance.rollback(); 219 | // t.true(flag); 220 | // instance.transTimes = 0; 221 | // }); 222 | -------------------------------------------------------------------------------- /test/lib/schema.js: -------------------------------------------------------------------------------- 1 | const { test } = require('ava'); 2 | const Query = require('../../lib/query'); 3 | const Parser = require('../../lib/parser'); 4 | const Schema = require('../../lib/schema'); 5 | 6 | test('_getItemSchemaValidate', t => { 7 | const schema = new Schema(); 8 | const data = [ 9 | [{ tinyType: 'tinyint' }, { int: { min: 0, max: 255 } }], 10 | [{ tinyType: 'smallint' }, { int: { min: -32768, max: 32767 } }], 11 | [{ tinyType: 'smallint', unsigned: 1 }, { int: { min: 0, max: 32767 } }], 12 | [{ tinyType: 'int' }, { int: { min: -2147483648, max: 2147483647 } }], 13 | [{ tinyType: 'int', unsigned: 1 }, { 14 | int: { min: 0, max: 2147483647 } 15 | }], 16 | [{ tinyType: 'date' }, { date: true }], 17 | [{ unsigned: true }, {}] 18 | ]; 19 | 20 | t.plan(data.length); 21 | data.forEach(([params, except]) => 22 | t.deepEqual(schema._getItemSchemaValidate(params), except) 23 | ); 24 | }); 25 | 26 | test('_parseItemSchema', t => { 27 | const schema = new Schema(); 28 | const data = [ 29 | [ 30 | { type: 'INT', default: '3', validate: 'hello' }, 31 | { type: 'INT', tinyType: 'int', default: 3, validate: 'hello' } 32 | ], 33 | [ 34 | { type: 'INT unsigned', default: '3' }, 35 | { type: 'INT', tinyType: 'int unsigned', default: 3, unsigned: true, validate: {} } 36 | ], 37 | // [ 38 | // { default: function() { return 'lizheming' } }, 39 | // { type: 'varchar(100)', tinyType: 'varchar', validate: {}, default: function() { return 'lizheming' } } 40 | // ], 41 | [ 42 | {}, 43 | { type: 'varchar(100)', tinyType: 'varchar', validate: {} } 44 | ] 45 | ]; 46 | 47 | t.plan(data.length); 48 | data.forEach(([params, except]) => 49 | t.deepEqual(schema._parseItemSchema(params), except) 50 | ); 51 | }); 52 | 53 | test('schema parser and query object', t => { 54 | t.plan(2); 55 | 56 | const schema = new Schema(); 57 | schema.query = new Query(); 58 | schema.parser = new Parser(); 59 | t.true(schema.query instanceof Query); 60 | t.true(schema.parser instanceof Parser); 61 | }); 62 | 63 | test('parser is getter', t => { 64 | const instance = new Schema(); 65 | instance.query = new Query(); 66 | instance.parser = new Parser(); 67 | const parser = instance.parser; 68 | t.true(parser instanceof Parser, true); 69 | }); 70 | 71 | test('parser is getter 2', t => { 72 | const instance = new Schema(); 73 | instance.query = new Query(); 74 | instance.parser = new Parser(); 75 | const parser = instance.parser; 76 | const parser2 = instance.parser; 77 | t.true(parser instanceof Parser, true); 78 | t.true(parser === parser2, true); 79 | }); 80 | 81 | test('query is getter', t => { 82 | const instance = new Schema(); 83 | instance.query = new Query(); 84 | instance.parser = new Parser(); 85 | const query = instance.query; 86 | t.true(query instanceof Query, true); 87 | }); 88 | 89 | test('query is getter 2', t => { 90 | const instance = new Schema(); 91 | instance.query = new Query(); 92 | instance.parser = new Parser(); 93 | const query = instance.query; 94 | const query2 = instance.query; 95 | t.true(query instanceof Query, true); 96 | t.true(query === query2, true); 97 | }); 98 | 99 | test('schema get empty schema', async t => { 100 | const schema = new Schema({}); 101 | schema.query = new Query(); 102 | schema.parser = new Parser(); 103 | const result = await schema.getSchema().catch(() => { 104 | return {}; 105 | }); 106 | t.deepEqual(result, {}); 107 | }); 108 | 109 | test('schema get empty schema 2', async t => { 110 | const schema = new Schema({}); 111 | schema.query = new Query(); 112 | schema.parser = new Parser(); 113 | const result = await schema.getSchema().catch(() => { 114 | return {}; 115 | }); 116 | const result2 = await schema.getSchema().catch(() => { 117 | return {}; 118 | }); 119 | t.deepEqual(result, {}); 120 | t.deepEqual(result, result2); 121 | }); 122 | 123 | test('schema get normal schema', async t => { 124 | const schema = new Schema({}, {}); 125 | schema.query = new Query(); 126 | schema.parser = new Parser(); 127 | const tableFields = [ 128 | { 129 | Field: 'id', 130 | Type: 'int(10) unsigned', 131 | Null: 'NO', 132 | Key: 'PRI', 133 | Default: null, 134 | Extra: 'auto_increment' 135 | }, 136 | { 137 | Field: 'title', 138 | Type: 'varchar(255)', 139 | Null: 'NO', 140 | Key: '', 141 | Default: null, 142 | Extra: '' 143 | } 144 | ]; 145 | 146 | Object.defineProperty(schema, 'query', { 147 | value: { 148 | query: sql => Promise.resolve(tableFields) 149 | } 150 | }); 151 | 152 | const result = await schema.getSchema('post1'); 153 | t.deepEqual(result, { 154 | id: { 155 | name: 'id', 156 | type: 'int(10)', 157 | required: true, 158 | default: '', 159 | primary: true, 160 | unique: false, 161 | autoIncrement: true, 162 | tinyType: 'int', 163 | unsigned: true, 164 | validate: { 165 | int: { 166 | min: 0, 167 | max: 2147483647 168 | } 169 | } 170 | }, 171 | title: { 172 | name: 'title', 173 | type: 'varchar(255)', 174 | required: true, 175 | default: '', 176 | primary: false, 177 | unique: false, 178 | autoIncrement: false, 179 | tinyType: 'varchar', 180 | validate: {} 181 | } 182 | }); 183 | }); 184 | 185 | test('schema get normal schema 2', async t => { 186 | const schema = new Schema({}, { 187 | title: { 188 | default: 'title' 189 | } 190 | }, 'test'); 191 | schema.query = new Query(); 192 | schema.parser = new Parser(); 193 | const tableFields = [ 194 | { 195 | Field: 'id', 196 | Type: 'int(10) unsigned', 197 | Null: 'NO', 198 | Key: 'PRI', 199 | Default: null, 200 | Extra: 'auto_increment' 201 | }, 202 | { 203 | Field: 'title', 204 | Type: 'varchar(255)', 205 | Null: 'NO', 206 | Key: '', 207 | Default: null, 208 | Extra: '' 209 | } 210 | ]; 211 | 212 | Object.defineProperty(schema, 'query', { 213 | value: { 214 | query: sql => Promise.resolve(tableFields) 215 | } 216 | }); 217 | 218 | const result = await schema.getSchema('post2'); 219 | t.deepEqual(result, { 220 | id: { 221 | name: 'id', 222 | type: 'int(10)', 223 | required: true, 224 | default: '', 225 | primary: true, 226 | unique: false, 227 | autoIncrement: true, 228 | tinyType: 'int', 229 | unsigned: true, 230 | validate: { 231 | int: { 232 | min: 0, 233 | max: 2147483647 234 | } 235 | } 236 | }, 237 | title: { 238 | name: 'title', 239 | type: 'varchar(255)', 240 | required: true, 241 | default: 'title', 242 | primary: false, 243 | unique: false, 244 | autoIncrement: false, 245 | tinyType: 'varchar', 246 | validate: {} 247 | } 248 | }); 249 | }); 250 | 251 | test('schema get normal schema 3', async t => { 252 | const schema = new Schema({}, { 253 | title: { 254 | default: 'title' 255 | } 256 | }, 'test'); 257 | schema.query = new Query(); 258 | schema.parser = new Parser(); 259 | const tableFields = [ 260 | { 261 | Field: 'id', 262 | Type: 'int(10) unsigned', 263 | Null: 'NO', 264 | Key: 'PRI', 265 | Default: '111', 266 | Extra: 'auto_increment' 267 | }, 268 | { 269 | Field: 'title', 270 | Type: 'varchar(255)', 271 | Null: 'NO', 272 | Key: '', 273 | Default: null, 274 | Extra: '' 275 | } 276 | ]; 277 | 278 | Object.defineProperty(schema, 'query', { 279 | value: { 280 | query: sql => Promise.resolve(tableFields) 281 | } 282 | }); 283 | 284 | const result = await schema.getSchema('post3'); 285 | t.deepEqual(result, { 286 | id: { 287 | name: 'id', 288 | type: 'int(10)', 289 | required: true, 290 | default: '', 291 | primary: true, 292 | unique: false, 293 | autoIncrement: true, 294 | tinyType: 'int', 295 | unsigned: true, 296 | validate: { 297 | int: { 298 | min: 0, 299 | max: 2147483647 300 | } 301 | } 302 | }, 303 | title: { 304 | name: 'title', 305 | type: 'varchar(255)', 306 | required: true, 307 | default: 'title', 308 | primary: false, 309 | unique: false, 310 | autoIncrement: false, 311 | tinyType: 'varchar', 312 | validate: {} 313 | } 314 | }); 315 | }); 316 | test('schema get normal schema 4', async t => { 317 | const schema = new Schema({}, { 318 | id: { 319 | validate: { 320 | int: { min: 0, max: 1 } 321 | } 322 | }, 323 | title: { 324 | default: 'title' 325 | } 326 | }, 'test'); 327 | schema.query = new Query(); 328 | schema.parser = new Parser(); 329 | const tableFields = [ 330 | { 331 | Field: 'id', 332 | Type: 'int(10) unsigned', 333 | Null: 'NO', 334 | Key: 'PRI', 335 | Default: '111', 336 | Extra: 'auto_increment' 337 | }, 338 | { 339 | Field: 'title', 340 | Type: 'varchar(255)', 341 | Null: 'NO', 342 | Key: '', 343 | Default: null, 344 | Extra: '' 345 | } 346 | ]; 347 | 348 | Object.defineProperty(schema, 'query', { 349 | value: { 350 | query: sql => Promise.resolve(tableFields) 351 | } 352 | }); 353 | 354 | const result = await schema.getSchema('post4'); 355 | t.deepEqual(result, { 356 | id: { 357 | name: 'id', 358 | type: 'int(10)', 359 | required: true, 360 | default: '', 361 | primary: true, 362 | unique: false, 363 | autoIncrement: true, 364 | tinyType: 'int', 365 | unsigned: true, 366 | validate: { 367 | int: { 368 | min: 0, 369 | max: 1 370 | } 371 | } 372 | }, 373 | title: { 374 | name: 'title', 375 | type: 'varchar(255)', 376 | required: true, 377 | default: 'title', 378 | primary: false, 379 | unique: false, 380 | autoIncrement: false, 381 | tinyType: 'varchar', 382 | validate: {} 383 | } 384 | }); 385 | }); 386 | 387 | test('schema parse type', t => { 388 | t.plan(15); 389 | 390 | const schema = new Schema({ jsonFormat: true }); 391 | t.is(schema.parseType('enum', '1'), '1'); 392 | t.is(schema.parseType('set', 'True'), 'True'); 393 | t.is(schema.parseType('bigint', 'False'), 'False'); 394 | t.is(schema.parseType('int(10)', '3'), 3); 395 | t.is(schema.parseType('int(10)', 'fasdfadf'), 0); 396 | t.is(schema.parseType('double', '3.3'), 3.3); 397 | t.is(schema.parseType('float', '3.3'), 3.3); 398 | t.is(schema.parseType('float', 'fasdfasdf'), 0); 399 | t.is(schema.parseType('decimal', '3.3'), 3.3); 400 | t.is(schema.parseType('bool', '0'), 1); 401 | t.is(schema.parseType('bool', ''), 0); 402 | t.is(schema.parseType('xxx', 'aaa'), 'aaa'); 403 | t.is(schema.parseType('json', [1, 2, 3, 4]), '[1,2,3,4]'); 404 | t.is(schema.parseType('json', null), null); 405 | t.is(schema.parseType('json', undefined), null); 406 | }); 407 | --------------------------------------------------------------------------------