Permalink
Browse files

feat: mysql insert

  • Loading branch information...
XadillaX committed Aug 31, 2016
1 parent c8b7ce9 commit 5d40852a9991d0b099a97645294f22d32253e94a
Showing with 710 additions and 389 deletions.
  1. +4 −0 lib/adapters/base.js
  2. +133 −2 lib/adapters/mysql.js
  3. +4 −0 lib/field.js
  4. +4 −0 lib/model.js
  5. +4 −0 lib/query.js
  6. +4 −0 lib/toshihiko.js
  7. +112 −0 lib/yukari.js
  8. +2 −1 package.json
  9. +431 −384 test/adapters/mysql.js
  10. +12 −2 test/util/common.js
@@ -43,6 +43,10 @@ class Adapter extends EventEmitter {
callback(new Error("this adapter's find function is not implemented yet."));
});
}

getDBName() {
return "";
}
}

module.exports = Adapter;
@@ -9,6 +9,7 @@
const _ = require("lodash");
const cu = require("config.util");
const debug = require("debug")("toshihiko:adapter:mysql");
const SqlString = require("sqlstring");

const Adapter = require("./base");
const escaper = require("../../util/escaper");
@@ -122,6 +123,10 @@ class MySQLAdapter extends Adapter {
debug("created.", this);
}

getDBName() {
return this.database;
}

makeFieldWhere(model, key, condition, logic) {
/**
* CAUTION:
@@ -420,13 +425,27 @@ class MySQLAdapter extends Adapter {
limit: query._limit
}, options);

if(options.single) {
if(!_options.limit || !_options.limit.length) {
_options.limit = [ 0, 1 ];
} else if(_options.limit.length === 1) {
_options.limit[0] = 1;
} else {
_options.limit[1] = 1;
}
}

const sql = this.makeSql("find", query.model, _options);
this.execute(sql, function(err, rows) {
if(err) {
return callback(err, undefined, sql);
}

return callback(undefined, rows, sql);
if(options.single) {
return callback(undefined, (rows || []).length ? rows[0] : null, sql);
}

return callback(undefined, (rows || []), sql);
});
}

@@ -444,6 +463,118 @@ class MySQLAdapter extends Adapter {
// }, options);
}

insert(model, _data, callback) {
// let it be
// [ { field: foo, value: bar }, ... ]
//
// we consume it as D
const data = Object.keys(_data).reduce((data, key) => {
if(!_data.hasOwnProperty(key)) return data;
if(key.length && key[0] === "$") return data;
const field = model.fieldNamesMap[key];
if(undefined === field) return data;

data.push({
field: field,
value: (_data[key] === null) ? null : field.restore(_data[key])
});
return data;
}, []);

// fill D into sql string
//
// INSERT INTO `name` SET =>
//
// 1. needQuote: `column` = SqlString.escape(value)
// 2. !needQuote: `column` = value
const _set = data.reduce((_set, data) => {
if(data.value === null) {
_set.push(`\`${data.field.column}\` = NULL`);
} else if(data.field.needQuotes) {
_set.push(`\`${data.field.column}\` = ${SqlString.escape(data.value)}`);
} else {
_set.push(`\`${data.field.column}\` = ${data.value}`);
}
return _set;
}, []);

const sql = `INSERT INTO \`${model.name}\` SET ${_set.join(", ")}`;
this.execute(sql, function(err, row) {
if(err) return callback(err, undefined, sql);
if(!row) {
return callback(new Error("no row inserted."), undefined, sql);
}

let where = {};
const primaryKeys = model.primaryKeys;
const autoIncrement = model.ai;

// query the instance from database now
if(row.insertId) {
// if this row has an auto increament id

if(primaryKeys.length === 1) {
// if we have only one primary key
//
// 1. no AI specified in model: consume primary is AI in database, use AI as primary key to query
// 2. primary key is AI: use AI as primary key to query
// 3. primary key is not AI: use primary key to query

if(!autoIncrement || autoIncrement.primaryKey) {
where[primaryKeys[0].name] = row.insertId;
} else {
where[primaryKeys[0].name] = data[primaryKeys[0].name];
}
} else if(primaryKeys.length) {
// if we have several primay keys
//
// step 1.
// use all primary keys to query
//
// step 2. (only have auto increament key)
// overwrite the AI to the certain primary key

// --> step 1.
for(let i = 0; i < primaryKeys.length; i++) {
where[primaryKeys[i].name] = data[primaryKeys[i].name];
}

// --> step 2.
if(autoIncrement && autoIncrement.primaryKey) {
where[autoIncrement.name] = row.insertId;
}
} else {
// if we have no primary key
//
// 1. console a warning to tell developer it's dangrous
// 2. use all columns to query

// fill all fields and console warning
console.error("[TOSHIHIKO WARNING] no primary key while inserting may cause some problems!");
where = data;
}
} else {
// no auto increament id in row

for(let i = 0; i < primaryKeys.length; i++) {
where[primaryKeys[i].name] = data[primaryKeys[i].name];
}
}

model.where(where).findOne(function(err, row) {
if(err) {
return callback(err, undefined, sql);
}

if(!row) {
return callback(new Error("insert successfully but failed to read the record."), undefined, sql);
}

return callback(undefined, row, sql);
});
});
}

execute(sql, params, callback) {
if(typeof params === "function") {
callback = params;
@@ -453,7 +584,7 @@ class MySQLAdapter extends Adapter {
this.mysql.getConnection((err, conn) => {
if(err) return callback(err);

if(params && params.length) {
if(params && (params.length || !Array.isArray(params))) {
sql = this.format(sql, params);
}

@@ -69,6 +69,10 @@ class ToshihikoField {
return this.default;
}

get needQuotes() {
return !!this.type.needQuotes;
}

restore(value) {
return this.type.restore(value);
}
@@ -139,6 +139,10 @@ class ToshihikoModel extends EventEmitter {
find(callback, toJSON, single) {
return (new Query(this)).find(callback, toJSON, single);
}

findOne(callback, toJSON) {
return (new Query(this)).findOne(callback, toJSON);
}
}

module.exports = ToshihikoModel;
@@ -146,6 +146,10 @@ class ToshihikoQuery {
single: !!single
});
}

findOne(callback, toJSON) {
this.find(callback, toJSON, true);
}
}

module.exports = ToshihikoQuery;
@@ -29,6 +29,10 @@ class Toshihiko extends EventEmitter {
}
}

get database() {
return this.adapter.getDBName();
}

execute() {
this.adapter.execute.apply(this.adapter, arguments);
}
@@ -0,0 +1,112 @@
/**
* XadillaX created at 2016-08-28 16:49:05 With ♥
*
* Copyright (c) 2016 Souche.com, all rights
* reserved.
*/
"use strict";

const _ = require("lodash");

class Yukari {
constructor(model, source) {
Object.defineProperties(this, {
$model: { value: model },
$toshihiko: { value: model.parent },
$schema: { value: model.schema },
$origData: { value: {}, writable: true },
$source: { value: source, writable: true },
$dbName: { value: model.parent.database },
$tabelName: { value: model.name },
$cache: { value: model.cache },
$fromCache: { value: false, writable: true },

$adapter: { value: model.parent.adapter },

// to be compatible with elder versions
_initRow: { value: this.fillRowFromSomewhere },
_buildRow: { value: this.buildNewRow },
_fieldAt: { value: this.fieldAt }
});
}

fillRowFromSource(row, rowInOrigName) {
// fill in the original data
this.$origData = {};
for(let i = 0; i < this.$schema.length; i++) {
const field = this.$schema[i];
const colName = rowInOrigName ? field.column : field.name;

// if no fit column, we continue
if(undefined === row[colName]) continue;

this.$origData[field.name] = {
fieldIdx: i,
data: row[colName] === null ? null : field.type.parse(row[colName])
};
}

// fill in the data fields
for(let key in this.$origData) {
if(!this.$origData.hasOwnProperty(key)) continue;
Object.defineProperty(this, key, {
enumerable: true,
configurable: false,
writable: true,
value: _.cloneDeep(this.$origData[key].data)
});
}

if(row.$fromCache) {
this.$fromCache = true;
}
}

buildNewRow(row, rowInOrigName) {
// clear the original data
this.$origData = {};

// fill in the data
for(let i = 0; i < this.$schema.length; i++) {
let field = this.$schema[i];
let value = row[rowInOrigName ? field.column : field.name];

// if no this field specified in `row`,
// we use the default value
if(undefined === value && undefined !== field.defaultValue) {
value = field.defaultValue;
}

// if still undefined
// we continue
if(undefined === value) continue;

Object.defineProperty(this, field.name, {
enumerable: true,
configurable: false,
writable: true,
value: _.cloneDeep(value)
});
}

if(row.$fromCache) {
this.$fromCache = true;
}
}

fieldAt(name) {
if(this.$source !== "new" && this.$origData[name]) {
return this.$origData[name].fieldIdx;
} else if(this.$source === "new") {
for(let i = 0; i < this.$schema.length; i++) {
if(this.$schema[i].name === name) {
return i;
}
}
}

return -1;
}
}

module.exports = Yukari;
@@ -14,7 +14,8 @@
"fbbk-json": "^1.0.0",
"lodash": "4.14.1",
"moment": "^2.14.1",
"otrans": "^1.0.0"
"otrans": "^1.0.0",
"sqlstring": "^2.0.1"
},
"devDependencies": {
"conventional-changelog-cli": "1.2.0",
Oops, something went wrong.

0 comments on commit 5d40852

Please sign in to comment.