├── .gitignore ├── .npmignore ├── index.js ├── license.txt ├── mongodb.js ├── mysql.js ├── package.json ├── pg-lo.js ├── pg.js ├── readme.md └── sqlserver.js /.gitignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | test.js 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | mongodb-nosqlembedded.js -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | Array.prototype.sqlagent = function(onItem, callback) { 2 | 3 | var self = this; 4 | var item = self.shift(); 5 | 6 | if (!item) { 7 | callback(); 8 | return self; 9 | } 10 | 11 | onItem.call(self, item, function(val) { 12 | if (val === false) { 13 | self.length = 0; 14 | callback(); 15 | } else 16 | setImmediate(() => self.sqlagent(onItem, callback)); 17 | }); 18 | 19 | return self; 20 | }; -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | Copyright 2012-2017 (c) Peter Širka 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a 5 | copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit 9 | persons to whom the Software is furnished to do so, subject to the 10 | following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 18 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 21 | USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /mongodb.js: -------------------------------------------------------------------------------- 1 | const database = require('mongodb'); 2 | const Fs = require('fs'); 3 | const columns_cache = {}; 4 | const CONNECTIONS = {}; 5 | const NOOP = function(){}; 6 | const PROJECTION = { _id: 1 }; 7 | const FILEREADERFILTER = {}; 8 | const REG_APO = /'/; 9 | const OPTIONS = { useNewUrlParser: true, useUnifiedTopology: true }; 10 | 11 | require('./index'); 12 | 13 | function SqlBuilder(skip, take, agent) { 14 | this.agent = agent; 15 | this.builder = {}; 16 | this._order = null; 17 | this._skip = skip >= 0 ? skip : 0; 18 | this._take = take >= 0 ? take : 0; 19 | this._set = null; 20 | this._inc = null; 21 | this._scope = 0; 22 | this._fields; 23 | this._is = false; 24 | this._isfirst = false; 25 | this._prepare; 26 | } 27 | 28 | SqlBuilder.prototype = { 29 | get data() { 30 | var obj = {}; 31 | if (this._set) 32 | obj.$set = this._set; 33 | if (this._inc) 34 | obj.$inc = this._inc; 35 | return obj; 36 | } 37 | }; 38 | 39 | SqlBuilder.prototype.callback = function(fn) { 40 | this.$callback = fn; 41 | return this; 42 | }; 43 | 44 | SqlBuilder.prototype.assign = function() { 45 | throw new Error('This method is not supported in MongoDB.'); 46 | }; 47 | 48 | SqlBuilder.prototype.replace = function(builder, reference) { 49 | var self = this; 50 | 51 | self.builder = reference ? builder.builder : copy(builder.builder); 52 | self.scope = builder.scope; 53 | 54 | if (builder._order) 55 | self._order = reference ? builder._order : copy(builder._order); 56 | 57 | self._skip = builder._skip; 58 | self._take = builder._take; 59 | 60 | if (builder._set) 61 | self._set = reference ? builder._set : copy(builder._set); 62 | 63 | if (builder._inc) 64 | self._inc = reference ? builder._inc : copy(builder._inc); 65 | 66 | if (builder._prepare) 67 | self._prepare = reference ? builder._prepare : copy(builder._prepare); 68 | 69 | if (builder._fields) 70 | self._fields = reference ? builder._fields : copy(builder._fields); 71 | 72 | self._is = builder._is; 73 | self._isfirst = builder._isfirst; 74 | 75 | return self; 76 | }; 77 | 78 | SqlBuilder.prototype.debug = function(type) { 79 | var obj = {}; 80 | obj.type = type; 81 | obj.condition = this.builder; 82 | this._fields && (obj.project = this._fields); 83 | this._order && (obj.sort = this._order); 84 | this._set && (obj.set = this._set); 85 | this._inc && (obj.inc = this._inc); 86 | obj.take = this._take; 87 | obj.skip = this._skip; 88 | return obj; 89 | }; 90 | 91 | function copy(source) { 92 | 93 | var keys = Object.keys(source); 94 | var i = keys.length; 95 | var target = {}; 96 | 97 | while (i--) { 98 | var key = keys[i]; 99 | target[key] = source[key]; 100 | } 101 | 102 | return target; 103 | } 104 | 105 | SqlBuilder.prototype.clone = function() { 106 | var builder = new SqlBuilder(0, 0, this.agent); 107 | return builder.replace(this); 108 | }; 109 | 110 | SqlBuilder.prototype.join = function(name) { 111 | throw new Error('SqlBuilder.join(' + name + ') is not supported.'); 112 | }; 113 | 114 | SqlBuilder.prototype.set = function(name, value) { 115 | var self = this; 116 | if (!self._set) 117 | self._set = {}; 118 | 119 | if (typeof(name) === 'string') { 120 | self._set[name] = value; 121 | return self; 122 | } 123 | 124 | var keys = Object.keys(name); 125 | for (var i = 0, length = keys.length; i < length; i++) { 126 | var key = keys[i]; 127 | if (key !== '_id' && key[0] !== '$' && name[key] !== undefined) 128 | self._set[key] = name[key]; 129 | } 130 | 131 | return self; 132 | }; 133 | 134 | SqlBuilder.prototype.primary = SqlBuilder.prototype.primaryKey = function(name) { 135 | console.log('SqlBuilder.primary(' + name + ') is not supported.'); 136 | // not implemented 137 | return this; 138 | }; 139 | 140 | SqlBuilder.prototype.remove = SqlBuilder.prototype.rem = function(name) { 141 | if (this._set) 142 | delete this._set[name]; 143 | if (this._inc) 144 | delete this._inc[name]; 145 | return this; 146 | }; 147 | 148 | SqlBuilder.prototype.schema = function(name) { 149 | console.log('SqlBuilder.schema(' + name + ') is not supported.'); 150 | return this; 151 | }; 152 | 153 | SqlBuilder.prototype.fields = function() { 154 | var self = this; 155 | 156 | if (arguments[0] instanceof Array) { 157 | var arr = arguments[0]; 158 | for (var i = 0, length = arr.length; i < length; i++) 159 | self.field(arr[i]); 160 | return self; 161 | } 162 | 163 | for (var i = 0; i < arguments.length; i++) 164 | self.field(arguments[i]); 165 | return self; 166 | }; 167 | 168 | SqlBuilder.prototype.field = function(name, visible) { 169 | var self = this; 170 | if (!self._fields) 171 | self._fields = {}; 172 | self._fields[name] = visible === false ? 0 : 1; 173 | return self; 174 | }; 175 | 176 | SqlBuilder.prototype.raw = function(name, value) { 177 | var self = this; 178 | if (!self._set) 179 | self._set = {}; 180 | self._set[name] = value; 181 | return self; 182 | }; 183 | 184 | SqlBuilder.prototype.inc = function(name, type, value) { 185 | 186 | var self = this; 187 | var can = false; 188 | 189 | if (!self._inc) 190 | self._inc = {}; 191 | 192 | if (value === undefined) { 193 | value = type; 194 | type = '+'; 195 | can = true; 196 | } 197 | 198 | if (value === undefined) 199 | value = 1; 200 | 201 | if (typeof(name) === 'string') { 202 | 203 | if (can && typeof(value) === 'string') { 204 | type = value[0]; 205 | switch (type) { 206 | case '+': 207 | case '-': 208 | case '*': 209 | case '/': 210 | value = value.substring(1).parseFloat(); 211 | break; 212 | default: 213 | type = '+'; 214 | value = value.parseFloat(); 215 | break; 216 | } 217 | } else { 218 | if(type !== '-') 219 | type = '+'; 220 | if (value == null) 221 | value = 1; 222 | } 223 | 224 | if (!value) 225 | return self; 226 | 227 | if (type === '-') 228 | value = value * -1; 229 | 230 | if (value === '$') 231 | throw new Error('SqlBuilder.inc(' + name + ') can\'t contain "$" value.'); 232 | 233 | self._inc[name] = value; 234 | return self; 235 | } 236 | 237 | var keys = Object.keys(name); 238 | 239 | for (var i = 0, length = keys.length; i < length; i++) { 240 | var key = keys[i]; 241 | name[key] && self.inc(key, name[key]); 242 | } 243 | 244 | return self; 245 | }; 246 | 247 | SqlBuilder.prototype.sort = function(name, desc) { 248 | return this.order(name, desc); 249 | }; 250 | 251 | SqlBuilder.prototype.order = function(name, desc) { 252 | 253 | var self = this; 254 | if (self._order === null) 255 | self._order = {}; 256 | 257 | var key = '<' + name + '.' + (desc || 'false') + '>'; 258 | 259 | if (columns_cache[key]) { 260 | self._order[columns_cache[key].name] = columns_cache[key].value; 261 | return; 262 | } 263 | 264 | var lowered = name.toLowerCase(); 265 | var index = lowered.lastIndexOf('desc'); 266 | 267 | if (index !== -1 || lowered.lastIndexOf('asc') !== -1) { 268 | name = name.split(' ')[0]; 269 | desc = index !== -1; 270 | } 271 | 272 | columns_cache[key] = {}; 273 | columns_cache[key].name = name; 274 | columns_cache[key].value = self._order[name] = desc ? -1 : 1; 275 | return self; 276 | }; 277 | 278 | SqlBuilder.prototype.random = function() { 279 | console.log('SqlBuilder.random() is not supported.'); 280 | return this; 281 | }; 282 | 283 | SqlBuilder.prototype.skip = function(value) { 284 | var self = this; 285 | self._skip = self.parseInt(value); 286 | return self; 287 | }; 288 | 289 | SqlBuilder.prototype.limit = function(value) { 290 | return this.take(value); 291 | }; 292 | 293 | SqlBuilder.prototype.page = function(value, max) { 294 | var self = this; 295 | value = self.parseInt(value) - 1; 296 | max = self.parseInt(max); 297 | if (value < 0) 298 | value = 0; 299 | self._skip = value * max; 300 | self._take = max; 301 | return self; 302 | }; 303 | 304 | SqlBuilder.prototype.parseInt = function(num) { 305 | if (typeof(num) === 'number') 306 | return num; 307 | if (!num) 308 | return 0; 309 | num = parseInt(num); 310 | if (isNaN(num)) 311 | num = 0; 312 | return num; 313 | }; 314 | 315 | SqlBuilder.prototype.take = function(value) { 316 | var self = this; 317 | self._take = self.parseInt(value); 318 | return self; 319 | }; 320 | 321 | SqlBuilder.prototype.first = function() { 322 | var self = this; 323 | self._skip = 0; 324 | self._take = 1; 325 | self._isfirst = true; 326 | return self; 327 | }; 328 | 329 | SqlBuilder.prototype.where = function(name, operator, value) { 330 | return this.push(name, operator, value); 331 | }; 332 | 333 | SqlBuilder.prototype.push = function(name, operator, value) { 334 | var self = this; 335 | 336 | if (value === undefined) { 337 | value = operator; 338 | operator = '='; 339 | } else if (operator === '!=') 340 | operator = '<>'; 341 | 342 | var type = typeof(value); 343 | 344 | if (name[0] === '!' && type !== 'function') { 345 | name = name.substring(1); 346 | value = ObjectID.parse(value); 347 | } 348 | 349 | switch (operator) { 350 | case '=': 351 | self.$scope(name, value, type, 1); 352 | break; 353 | case '<>': 354 | self.$scope(name, { $ne: value }, type, 5); 355 | break; 356 | case '>': 357 | self.$scope(name, { $gt: value }, type, 6); 358 | break; 359 | case '<': 360 | self.$scope(name, { $lt: value }, type, 7); 361 | break; 362 | case '>=': 363 | self.$scope(name, { $gte: value }, type, 8); 364 | break; 365 | case '<=': 366 | self.$scope(name, { $lte: value }, type, 9); 367 | break; 368 | } 369 | 370 | self.checkOperator(); 371 | self._is = true; 372 | return self; 373 | }; 374 | 375 | SqlBuilder.prototype.checkOperator = function() { 376 | // Not implemented 377 | return this; 378 | }; 379 | 380 | SqlBuilder.prototype.clear = function() { 381 | this._take = 0; 382 | this._skip = 0; 383 | this._scope = 0; 384 | this._order = null; 385 | this._set = null; 386 | this._inc = null; 387 | this.builder = {}; 388 | return this; 389 | }; 390 | 391 | SqlBuilder.escape = SqlBuilder.prototype.escape = function(value) { 392 | console.log('SqlBuilder.escape() is not supported.'); 393 | return value; 394 | }; 395 | 396 | SqlBuilder.column = function(name) { 397 | console.log('SqlBuilder.column() is not supported.'); 398 | return name; 399 | }; 400 | 401 | SqlBuilder.prototype.group = function() { 402 | console.log('SqlBuilder.group() is not supported.'); 403 | return this; 404 | }; 405 | 406 | SqlBuilder.prototype.having = function() { 407 | console.log('SqlBuilder.having() is not supported.'); 408 | return this; 409 | }; 410 | 411 | SqlBuilder.prototype.and = function() { 412 | var self = this; 413 | self._scope = 2; 414 | return self; 415 | }; 416 | 417 | SqlBuilder.prototype.or = function() { 418 | var self = this; 419 | self._scope = 1; 420 | return self; 421 | }; 422 | 423 | SqlBuilder.prototype.end = function() { 424 | var self = this; 425 | self._scope = 0; 426 | return self; 427 | }; 428 | 429 | SqlBuilder.prototype.scope = function(fn) { 430 | var self = this; 431 | fn.call(self); 432 | self._scope = 0; 433 | return self; 434 | }; 435 | 436 | SqlBuilder.prototype.$scope = function(name, obj, type, code, raw) { 437 | 438 | var self = this; 439 | var is = false; 440 | 441 | if (type === 'function') { 442 | if (!self._prepare) 443 | self._prepare = []; 444 | self._prepare.push({ context: self.builder, name: name, value: obj, scope: self._scope, type: code, raw: raw }); 445 | is = true; 446 | } 447 | 448 | if (self._scope === 0) { 449 | self.builder[name] = obj; 450 | return self; 451 | } 452 | 453 | if (self._scope === 1) { 454 | if (!self.builder['$or']) 455 | self.builder['$or'] = []; 456 | var filter = {}; 457 | filter[name] = obj; 458 | self.builder['$or'].push(filter); 459 | if (is) 460 | self._prepare[self._prepare.length - 1].index = self.builder['$or'].length - 1; 461 | } 462 | 463 | if (self._scope === 2) { 464 | if (!self.builder['$and']) 465 | self.builder['$and'] = []; 466 | var filter = {}; 467 | filter[name] = obj; 468 | self.builder['$and'].push(filter); 469 | if (is) 470 | self._prepare[self._prepare.length - 1].index = self.builder['$and'].length - 1; 471 | } 472 | 473 | return self; 474 | }; 475 | 476 | SqlBuilder.prototype.in = function(name, value) { 477 | var self = this; 478 | self.$scope(name, { '$in': value }, typeof(value), 4); 479 | return self; 480 | }; 481 | 482 | SqlBuilder.prototype.like = function(name, value, where) { 483 | var self = this; 484 | var type = typeof(value); 485 | var val = type === 'function' ? '' : value.toString(); 486 | 487 | switch (where) { 488 | case 'beg': 489 | case 'begin': 490 | self.$scope(name, { $regex: '^' + val }, type, 2, value); 491 | break; 492 | case 'end': 493 | self.$scope(name, { $regex: val + '$' }, type, 2, value); 494 | break; 495 | case '*': 496 | default: 497 | self.$scope(name, { $regex: val }, type, 2, value); 498 | break; 499 | } 500 | 501 | self._is = true; 502 | return self; 503 | }; 504 | 505 | SqlBuilder.prototype.between = function(name, valueA, valueB) { 506 | var self = this; 507 | var typeA = typeof(valueA); 508 | var typeB = typeof(valueB); 509 | self.$scope(name, { $gte: valueA, $lte: valueB }, typeA === 'function' || typeB === 'function' ? 'function' : typeA, 3); 510 | self._is = true; 511 | return self; 512 | }; 513 | 514 | SqlBuilder.prototype.query = function(name, value) { 515 | return this.$scope(name, value, undefined, 10); 516 | }; 517 | 518 | SqlBuilder.prototype.sql = function() { 519 | console.log('SqlBuilder.sql() is not supported.'); 520 | return this; 521 | }; 522 | 523 | SqlBuilder.prototype.toString = function() { 524 | console.log('SqlBuilder.toString() is not supported.'); 525 | return this; 526 | }; 527 | 528 | SqlBuilder.prototype.toQuery = function() { 529 | console.log('SqlBuilder.toQuery() is not supported.'); 530 | return this; 531 | }; 532 | 533 | SqlBuilder.prototype.prepare = function() { 534 | 535 | if (!this._prepare) 536 | return this; 537 | 538 | for (var i = 0, length = this._prepare.length; i < length; i++) { 539 | 540 | var prepare = this._prepare[i]; 541 | // prepare.type 1 - where "=" 542 | // prepare.type 2 - like 543 | // prepare.type 3 - between 544 | // prepare.type 4 - in 545 | 546 | // or 547 | if (prepare.scope === 1) { 548 | prepare.context['$or'][prepare.index] = prepare.value(); 549 | continue; 550 | } 551 | 552 | // and 553 | if (prepare.scope === 2) { 554 | prepare.context['$and'][prepare.index] = prepare.value(); 555 | continue; 556 | } 557 | 558 | if (prepare.type === 1) { 559 | prepare.context[prepare.name] = prepare.value(); 560 | continue; 561 | } 562 | 563 | if (prepare.type === 2) { 564 | prepare.value.$regex += prepare.raw(); 565 | continue; 566 | } 567 | 568 | if (prepare.type === 3) { 569 | if (typeof(prepare.value.$gte) === 'function') 570 | prepare.value.$gte = prepare.value.$gte(); 571 | if (typeof(prepare.value.$lte) === 'function') 572 | prepare.value.$lte = prepare.value.$lte(); 573 | continue; 574 | } 575 | 576 | if (prepare.type === 4) { 577 | prepare.value.$in = prepare.value.$in(); 578 | continue; 579 | } 580 | 581 | if (prepare.type === 5) { 582 | prepare.value.$ne = prepare.value.$ne(); 583 | continue; 584 | } 585 | 586 | if (prepare.type === 6) { 587 | prepare.value.$gt = prepare.value.$gt(); 588 | continue; 589 | } 590 | 591 | if (prepare.type === 7) { 592 | prepare.value.$lt = prepare.value.$lt(); 593 | continue; 594 | } 595 | 596 | if (prepare.type === 8) { 597 | prepare.value.$gte = prepare.value.$gte(); 598 | continue; 599 | } 600 | 601 | if (prepare.type === 9) { 602 | prepare.value.$lte = prepare.value.$lte(); 603 | continue; 604 | } 605 | } 606 | 607 | return this; 608 | }; 609 | 610 | SqlBuilder.prototype.make = function(fn) { 611 | var self = this; 612 | fn.call(self, self); 613 | return self.agent || self; 614 | }; 615 | 616 | function Agent(name, error) { 617 | this.connection = name; 618 | this.isErrorBuilder = typeof(global.ErrorBuilder) !== 'undefined' ? true : false; 619 | this.errors = this.isErrorBuilder ? error : null; 620 | this.clear(); 621 | this.$events = {}; 622 | 623 | // Hidden: 624 | // this.$when; 625 | } 626 | 627 | Agent.prototype = { 628 | get $() { 629 | return new SqlBuilder(0, 0, this); 630 | }, 631 | get $$() { 632 | var self = this; 633 | return function() { 634 | return self.$id; 635 | }; 636 | } 637 | }; 638 | 639 | Agent.embedded = function() { 640 | require('mongodb-nosqlembedded').init(Agent); 641 | }; 642 | 643 | // Debug mode (output to console) 644 | Agent.debug = false; 645 | 646 | Agent.connect = function(conn, callback) { 647 | database.connect(conn, OPTIONS, function(err, db) { 648 | if (err) { 649 | if (callback) 650 | return callback(err); 651 | throw err; 652 | } 653 | 654 | if (db.db) { 655 | // new mongodb 656 | if (conn[conn.length - 1] === '/') 657 | conn = conn.substring(0, conn.length - 1); 658 | var name = conn.substring(conn.lastIndexOf('/') + 1); 659 | var index = name.indexOf('?'); 660 | if (index !== -1) 661 | name = name.substring(0, index); 662 | db = db.db(name); 663 | } 664 | 665 | CONNECTIONS[conn] = db; 666 | callback && callback(); 667 | }); 668 | return function(error) { 669 | return new Agent(conn, error); 670 | }; 671 | }; 672 | 673 | Agent.prototype.promise = function(index, fn) { 674 | var self = this; 675 | 676 | if (typeof(index) === 'function') { 677 | fn = index; 678 | index = undefined; 679 | } 680 | 681 | return new Promise(function(resolve, reject) { 682 | self.exec(function(err, result) { 683 | if (err) 684 | reject(err); 685 | else 686 | resolve(fn ? fn(result) : result); 687 | }, index); 688 | }); 689 | }; 690 | 691 | Agent.prototype.emit = function(name, a, b, c, d, e, f, g) { 692 | var evt = this.$events[name]; 693 | if (evt) { 694 | var clean = false; 695 | for (var i = 0, length = evt.length; i < length; i++) { 696 | if (evt[i].$once) 697 | clean = true; 698 | evt[i].call(this, a, b, c, d, e, f, g); 699 | } 700 | if (clean) { 701 | evt = evt.remove(n => n.$once); 702 | if (evt.length) 703 | this.$events[name] = evt; 704 | else 705 | this.$events[name] = undefined; 706 | } 707 | } 708 | return this; 709 | }; 710 | 711 | Agent.prototype.on = function(name, fn) { 712 | 713 | if (!fn.$once) 714 | this.$free = false; 715 | 716 | if (this.$events[name]) 717 | this.$events[name].push(fn); 718 | else 719 | this.$events[name] = [fn]; 720 | return this; 721 | }; 722 | 723 | Agent.prototype.once = function(name, fn) { 724 | fn.$once = true; 725 | return this.on(name, fn); 726 | }; 727 | 728 | Agent.prototype.removeListener = function(name, fn) { 729 | var evt = this.$events[name]; 730 | if (evt) { 731 | evt = evt.remove(n => n === fn); 732 | if (evt.length) 733 | this.$events[name] = evt; 734 | else 735 | this.$events[name] = undefined; 736 | } 737 | return this; 738 | }; 739 | 740 | Agent.prototype.removeAllListeners = function(name) { 741 | if (name === true) 742 | this.$events = EMPTYOBJECT; 743 | else if (name) 744 | this.$events[name] = undefined; 745 | else 746 | this.$events[name] = {}; 747 | return this; 748 | }; 749 | 750 | Agent.prototype.clear = function() { 751 | this.command = []; 752 | this.db = null; 753 | this.done = null; 754 | this.last = null; 755 | this.id = null; 756 | this.$id = null; 757 | this.isCanceled = false; 758 | this.index = 0; 759 | this.isPut = false; 760 | this.skipCount = 0; 761 | this.skips = {}; 762 | this.$primary = '_id'; 763 | this.results = {}; 764 | this.builders = {}; 765 | 766 | if (this.$when) 767 | this.$when = undefined; 768 | 769 | if (this.errors && this.isErrorBuilder) 770 | this.errors.clear(); 771 | else if (this.errors) 772 | this.errors = null; 773 | 774 | return this; 775 | }; 776 | 777 | Agent.prototype.when = function(name, fn) { 778 | 779 | if (!this.$when) 780 | this.$when = {}; 781 | 782 | if (this.$when[name]) 783 | this.$when[name].push(fn); 784 | else 785 | this.$when[name] = [fn]; 786 | 787 | return this; 788 | }; 789 | 790 | Agent.prototype.priority = function() { 791 | var self = this; 792 | var length = self.command.length - 1; 793 | 794 | if (!length) 795 | return self; 796 | 797 | var last = self.command[length]; 798 | for (var i = length; i > -1; i--) 799 | self.command[i] = self.command[i - 1]; 800 | 801 | self.command[0] = last; 802 | return self; 803 | }; 804 | 805 | Agent.prototype.default = function(fn) { 806 | fn.call(this.results, this.results); 807 | return this; 808 | }; 809 | 810 | Agent.query = function() { 811 | console.log('Agent.query() is not supported.'); 812 | return Agent; 813 | }; 814 | 815 | Agent.prototype.skip = function(name) { 816 | var self = this; 817 | if (name) 818 | self.skips[name] = true; 819 | else 820 | self.skipCount++; 821 | return self; 822 | }; 823 | 824 | Agent.prototype.primaryKey = Agent.prototype.primary = function() { 825 | console.log('Agent.primary() is not supported.'); 826 | return this; 827 | }; 828 | 829 | Agent.prototype.expected = function(name, index, property) { 830 | 831 | var self = this; 832 | 833 | if (typeof(index) === 'string') { 834 | property = index; 835 | index = undefined; 836 | } 837 | 838 | return function() { 839 | var output = self.results[name]; 840 | if (!output) 841 | return null; 842 | if (index === undefined) 843 | return property === undefined ? output : get(output, property); 844 | output = output[index]; 845 | return output ? get(output, property) : null; 846 | }; 847 | }; 848 | 849 | Agent.prototype.prepare = function(fn) { 850 | var self = this; 851 | self.command.push({ type: 'prepare', fn: fn }); 852 | return self; 853 | }; 854 | 855 | Agent.prototype.modify = function(fn) { 856 | var self = this; 857 | self.command.push({ type: 'modify', fn: fn }); 858 | return self; 859 | }; 860 | 861 | Agent.prototype.bookmark = function(fn) { 862 | var self = this; 863 | self.command.push({ type: 'bookmark', fn: fn }); 864 | return self; 865 | }; 866 | 867 | Agent.prototype.put = function(value) { 868 | var self = this; 869 | self.command.push({ type: 'put', params: value, disable: value == null }); 870 | return self; 871 | }; 872 | 873 | Agent.prototype.lock = function() { 874 | return this.put(this.$$); 875 | }; 876 | 877 | Agent.prototype.unlock = function() { 878 | this.command.push({ 'type': 'unput' }); 879 | return this; 880 | }; 881 | 882 | Agent.prototype.query = function(name, query, params) { 883 | return this.push(name, query, params); 884 | }; 885 | 886 | Agent.prototype.push = function(name, table, fn) { 887 | var self = this; 888 | 889 | if (typeof(table) !== 'string') { 890 | fn = table; 891 | table = name; 892 | name = self.index++; 893 | } 894 | 895 | self.command.push({ type: 'push', name: name, table: table, fn: fn }); 896 | return self; 897 | }; 898 | 899 | Agent.prototype.validate = function(fn, error, reverse) { 900 | var self = this; 901 | var type = typeof(fn); 902 | 903 | if (typeof(error) === 'boolean') { 904 | reverse = error; 905 | error = undefined; 906 | } 907 | 908 | if (type === 'string' && error === undefined) { 909 | // checks the last result 910 | error = fn; 911 | fn = undefined; 912 | } 913 | 914 | if (type === 'function') { 915 | self.command.push({ type: 'validate', fn: fn, error: error }); 916 | return self; 917 | } 918 | 919 | if (type === 'string' && typeof(error) === 'function' && typeof(reverse) === 'string') 920 | return self.validate2(fn, error, reverse); 921 | 922 | var exec; 923 | 924 | if (reverse) { 925 | exec = function(err, results, next) { 926 | var id = fn == null ? self.last : fn; 927 | if (id == null) 928 | return next(true); 929 | var r = results[id]; 930 | if (r instanceof Array) 931 | return next(r.length === 0); 932 | if (r) 933 | return next(false); 934 | next(true); 935 | }; 936 | } else { 937 | exec = function(err, results, next) { 938 | var id = fn == null ? self.last : fn; 939 | if (id == null) 940 | return next(false); 941 | var r = results[id]; 942 | if (r instanceof Array) 943 | return next(r.length > 0); 944 | if (r) 945 | return next(true); 946 | next(false); 947 | }; 948 | } 949 | 950 | self.command.push({ type: 'validate', fn: exec, error: error }); 951 | return self; 952 | }; 953 | 954 | // validate2('result', n => n.length > 0, 'error'); 955 | Agent.prototype.validate2 = function(name, fn, err) { 956 | var self = this; 957 | var type = typeof(fn); 958 | 959 | if (type === 'string') { 960 | type = err; 961 | err = fn; 962 | fn = type; 963 | } 964 | 965 | var validator = function(err, results, next) { 966 | if (fn(results[name])) 967 | return next(true); 968 | err.push(err || name); 969 | next(false); 970 | }; 971 | 972 | self.command.push({ type: 'validate', fn: validator, error: err }); 973 | return self; 974 | }; 975 | 976 | Agent.prototype.cancel = function(fn) { 977 | return this.validate(fn); 978 | }; 979 | 980 | Agent.prototype.begin = function() { 981 | var self = this; 982 | console.log('Agent.begin() is not supported.'); 983 | return self; 984 | }; 985 | 986 | Agent.prototype.end = function() { 987 | var self = this; 988 | console.log('Agent.end() is not supported.'); 989 | return self; 990 | }; 991 | 992 | Agent.prototype.commit = function() { 993 | console.log('Agent.commit() is not supported.'); 994 | return this; 995 | }; 996 | 997 | Agent.prototype.save = function(name, table, insert, maker) { 998 | 999 | if (typeof(table) === 'boolean') { 1000 | maker = insert; 1001 | insert = table; 1002 | table = name; 1003 | name = undefined; 1004 | } 1005 | 1006 | var self = this; 1007 | if (insert) { 1008 | maker(self.insert(name, table), true); 1009 | return self; 1010 | } 1011 | 1012 | var builder = self.update(name, table); 1013 | builder.first(); 1014 | maker(builder, false); 1015 | 1016 | return self; 1017 | }; 1018 | 1019 | Agent.prototype.insert = function(name, table) { 1020 | 1021 | var self = this; 1022 | 1023 | if (typeof(table) !== 'string') { 1024 | table = name; 1025 | name = self.index++; 1026 | } 1027 | 1028 | var condition = new SqlBuilder(0, 0, self); 1029 | var fn = function(db, builder, helper, callback) { 1030 | 1031 | builder.prepare(); 1032 | 1033 | if (!builder._set && !builder._inc) { 1034 | var err = new Error('No data for inserting.'); 1035 | builder.$callback && builder.$callback(err); 1036 | callback(err, null); 1037 | return; 1038 | } 1039 | 1040 | var data = builder.data; 1041 | 1042 | if (data.$inc) { 1043 | 1044 | if (!data.$set) 1045 | data.$set = {}; 1046 | 1047 | Object.keys(data.$inc).forEach(function(key) { 1048 | data.$set[key] = data.$inc[key]; 1049 | }); 1050 | 1051 | data.$inc = undefined; 1052 | } 1053 | 1054 | self.$events.query && self.emit('query', name, builder.debug('insert')); 1055 | 1056 | var method = db.insertOne || db.insert; 1057 | method.call(db, data.$set, function(err, response) { 1058 | var id = response ? (response.insertedCount ? response.insertedId || (response.insertedIds && response.insertedIds.length > 1 ? response.insertedIds : response.insertedIds[0]) : null) : null; 1059 | self.id = id; 1060 | if (!self.isPut) 1061 | self.$id = self.id; 1062 | var data = id ? { identity: id } : null; 1063 | builder.$callback && builder.$callback(err, data); 1064 | callback(err, data); 1065 | }); 1066 | }; 1067 | 1068 | self.command.push({ type: 'query', table: table, name: name, condition: condition, fn: fn }); 1069 | self.builders[name] = condition; 1070 | return condition; 1071 | }; 1072 | 1073 | Agent.prototype.listing = function(name, table) { 1074 | var self = this; 1075 | 1076 | if (typeof(table) !== 'string') { 1077 | table = name; 1078 | name = self.index++; 1079 | } 1080 | 1081 | var condition = new SqlBuilder(0, 0, self); 1082 | 1083 | var fn = function(db, builder, helper, callback) { 1084 | 1085 | builder.prepare(); 1086 | builder._isfirst && console.warn('You can\'t use "builder.first()" for ".listing()".'); 1087 | 1088 | var cursor = db.find(builder.builder); 1089 | cursor.project(PROJECTION); 1090 | cursor.count(function(err, count) { 1091 | 1092 | if (err) 1093 | return callback(err); 1094 | 1095 | self.$events.query && self.emit('query', name, builder.debug('listing')); 1096 | var output = {}; 1097 | output.count = count; 1098 | cursor = db.find(builder.builder); 1099 | builder._fields && cursor.project(builder._fields); 1100 | builder._order && cursor.sort(builder._order); 1101 | builder._take && cursor.limit(builder._take); 1102 | builder._skip && cursor.skip(builder._skip); 1103 | cursor.toArray(function(err, docs) { 1104 | if (err) 1105 | return callback(err); 1106 | output.items = docs; 1107 | output.page = ((builder._skip || 0) / (builder._take || 0)) + 1; 1108 | output.limit = builder._take || 0; 1109 | output.pages = Math.ceil(output.count / output.limit); 1110 | builder && builder.$callback && builder.$callback(null, output); 1111 | callback(null, output); 1112 | }); 1113 | }); 1114 | }; 1115 | 1116 | self.command.push({ type: 'query', name: name, table: table, condition: condition, fn: fn }); 1117 | self.builders[name] = condition; 1118 | return condition; 1119 | }; 1120 | 1121 | Agent.prototype.select = function(name, table) { 1122 | var self = this; 1123 | 1124 | if (typeof(table) !== 'string') { 1125 | table = name; 1126 | name = self.index++; 1127 | } 1128 | 1129 | var condition = new SqlBuilder(0, 0, self); 1130 | 1131 | var fn = function(db, builder, helper, callback) { 1132 | 1133 | var cb = function(err, data) { 1134 | builder.$callback && builder.$callback(err, data); 1135 | callback(err, data); 1136 | }; 1137 | 1138 | builder.prepare(); 1139 | self.$events.query && self.emit('query', name, builder.debug('select')); 1140 | 1141 | if (builder._isfirst && !builder._order) { 1142 | if (builder._fields) 1143 | db.findOne(builder.builder, { projection: builder._fields }, cb); 1144 | else 1145 | db.findOne(builder.builder, cb); 1146 | } else { 1147 | var cursor = db.find(builder.builder); 1148 | builder._fields && cursor.project(builder._fields); 1149 | builder._order && cursor.sort(builder._order); 1150 | builder._take && cursor.limit(builder._take); 1151 | builder._skip && cursor.skip(builder._skip); 1152 | cursor.toArray(cb); 1153 | } 1154 | }; 1155 | 1156 | self.command.push({ type: 'query', name: name, table: table, condition: condition, fn: fn }); 1157 | self.builders[name] = condition; 1158 | return condition; 1159 | }; 1160 | 1161 | Agent.prototype.compare = function(name, table, obj, keys) { 1162 | 1163 | var self = this; 1164 | 1165 | if (typeof(table) !== 'string') { 1166 | keys = obj; 1167 | obj = table; 1168 | table = name; 1169 | name = self.index++; 1170 | } 1171 | 1172 | var condition = new SqlBuilder(0, 0, self); 1173 | condition.first(); 1174 | 1175 | var fn = function(db, builder, helper, callback) { 1176 | 1177 | var prop = keys ? keys : builder._fields ? Object.keys(builder._fields) : Object.keys(obj); 1178 | 1179 | !builder._fields && builder.fields.apply(builder, prop); 1180 | builder.prepare(); 1181 | self.$events.query && self.emit('query', name, builder.debug('compare')); 1182 | 1183 | db.findOne(builder.builder, builder._fields ? { projection: builder._fields } : null, function(err, doc) { 1184 | 1185 | if (err) 1186 | return callback(err); 1187 | 1188 | var val = doc; 1189 | var diff; 1190 | 1191 | if (val) { 1192 | diff = []; 1193 | for (var i = 0, length = prop.length; i < length; i++) { 1194 | var key = prop[i]; 1195 | var a = val[key]; 1196 | var b = obj[key]; 1197 | a !== b && diff.push(key); 1198 | } 1199 | } else 1200 | diff = prop; 1201 | 1202 | callback(null, diff.length ? { diff: diff, record: val, value: obj } : false); 1203 | }); 1204 | }; 1205 | 1206 | self.command.push({ type: 'query', name: name, table: table, condition: condition, fn: fn }); 1207 | self.builders[name] = condition; 1208 | return condition; 1209 | }; 1210 | 1211 | Agent.prototype.find = Agent.prototype.builder = function(name) { 1212 | return this.builders[name]; 1213 | }; 1214 | 1215 | const EMPTYEXISTS = { projection: { _id: 1 }}; 1216 | 1217 | Agent.prototype.exists = function(name, table) { 1218 | var self = this; 1219 | 1220 | if (typeof(table) !== 'string') { 1221 | table = name; 1222 | name = self.index++; 1223 | } 1224 | 1225 | var condition = new SqlBuilder(0, 0, self); 1226 | condition.fields('_id'); 1227 | 1228 | var fn = function(db, builder, helper, callback) { 1229 | builder.prepare(); 1230 | self.$events.query && self.emit('query', name, builder.debug('exists')); 1231 | db.findOne(builder.builder, EMPTYEXISTS, function(err, doc) { 1232 | builder.$callback && builder.$callback(err, !!doc); 1233 | callback(err, !!doc); 1234 | }); 1235 | }; 1236 | 1237 | self.command.push({ type: 'query', name: name, table: table, condition: condition, fn: fn }); 1238 | self.builders[name] = condition; 1239 | return condition; 1240 | }; 1241 | 1242 | Agent.prototype.count = function(name, table, column) { 1243 | var self = this; 1244 | 1245 | if (typeof(table) !== 'string') { 1246 | table = name; 1247 | name = self.index++; 1248 | } 1249 | 1250 | var condition = new SqlBuilder(0, 0, self); 1251 | condition.fields(column || '_id'); 1252 | 1253 | var fn = function(db, builder, helper, callback) { 1254 | builder.prepare(); 1255 | self.$events.query && self.emit('query', name, builder.debug('count')); 1256 | db.find(builder.builder).count(function(err, count) { 1257 | builder.$callback && builder.$callback(err, count); 1258 | callback(err, count); 1259 | }); 1260 | }; 1261 | 1262 | self.command.push({ type: 'query', table: table, name: name, condition: condition, fn: fn }); 1263 | self.builders[name] = condition; 1264 | return condition; 1265 | }; 1266 | 1267 | Agent.prototype.max = function(name, table, column) { 1268 | 1269 | if (typeof(table) !== 'string') { 1270 | table = name; 1271 | name = self.index++; 1272 | } 1273 | 1274 | var self = this; 1275 | var fn = function(db, builder, helper, callback) { 1276 | 1277 | builder.prepare(); 1278 | builder.first(); 1279 | self.$events.query && self.emit('query', name, builder.debug('max')); 1280 | 1281 | var cursor = db.find(builder.builder); 1282 | cursor.sort(builder._order); 1283 | cursor.project(builder._fields); 1284 | cursor.limit(1); 1285 | cursor.toArray(function(err, response) { 1286 | var data = response && response.length ? response[0][helper] : 0; 1287 | builder.$callback && builder.$callback(err, data); 1288 | callback(err, data); 1289 | }); 1290 | }; 1291 | 1292 | var condition = new SqlBuilder(0, 0, self); 1293 | condition.fields(column); 1294 | condition.sort(column, true); 1295 | 1296 | self.command.push({ type: 'query', table: table, name: name, condition: condition, fn: fn, helper: column }); 1297 | self.builders[name] = condition; 1298 | return condition; 1299 | }; 1300 | 1301 | Agent.prototype.min = function(name, table, column) { 1302 | 1303 | if (typeof(table) !== 'string') { 1304 | table = name; 1305 | name = self.index++; 1306 | } 1307 | 1308 | var self = this; 1309 | var fn = function(db, builder, helper, callback) { 1310 | 1311 | builder.prepare(); 1312 | builder.first(); 1313 | self.$events.query && self.emit('query', name, builder.debug('min')); 1314 | 1315 | var cursor = db.find(builder.builder); 1316 | cursor.sort(builder._order); 1317 | cursor.project(builder._fields); 1318 | cursor.limit(1); 1319 | cursor.toArray(function(err, response) { 1320 | var data = response && response.length ? response[0][helper] : 0; 1321 | builder.$callback && builder.$callback(err, data); 1322 | callback(err, data); 1323 | }); 1324 | }; 1325 | 1326 | var condition = new SqlBuilder(0, 0, self); 1327 | condition.fields(column); 1328 | condition.sort(column, false); 1329 | 1330 | self.command.push({ type: 'query', table: table, name: name, condition: condition, fn: fn, helper: column }); 1331 | self.builders[name] = condition; 1332 | return condition; 1333 | }; 1334 | 1335 | Agent.prototype.avg = function(name) { 1336 | throw new Error('Agent.avg(' + name + ') is not supported now.'); 1337 | }; 1338 | 1339 | Agent.prototype.updateOnly = function(name, table, values) { 1340 | throw new Error('Agent.updateOnly(' + name + ') is not supported now.'); 1341 | }; 1342 | 1343 | Agent.prototype.update = function(name, table) { 1344 | 1345 | var self = this; 1346 | 1347 | if (typeof(table) !== 'string') { 1348 | table = name; 1349 | name = self.index++; 1350 | } 1351 | 1352 | var condition = new SqlBuilder(0, 0, self); 1353 | var fn = function(db, builder, helper, callback) { 1354 | 1355 | builder.prepare(); 1356 | 1357 | if (!builder._set && !builder._inc) { 1358 | var err = new Error('No data for update.'); 1359 | builder.$callback && builder.$callback(err); 1360 | callback(err, null); 1361 | return; 1362 | } 1363 | 1364 | self.$events.query && self.emit('query', name, builder.debug('update')); 1365 | 1366 | if (builder._isfirst) { 1367 | db.updateOne(builder.builder, builder.data, function(err, response) { 1368 | var data = response ? (response.result.nModified || response.result.n) : 0; 1369 | builder.$callback && builder.$callback(err, data); 1370 | callback(err, data); 1371 | }); 1372 | } else { 1373 | var method = db.updateMany || db.update; 1374 | method.call(db, builder.builder, builder.data, { multi: true }, function(err, response) { 1375 | var data = response ? (response.result.nModified || response.result.n) : 0; 1376 | builder.$callback && builder.$callback(err, data); 1377 | callback(err, data); 1378 | }); 1379 | } 1380 | }; 1381 | 1382 | self.command.push({ type: 'query', table: table, name: name, condition: condition, fn: fn }); 1383 | self.builders[name] = condition; 1384 | return condition; 1385 | }; 1386 | 1387 | Agent.prototype.delete = function(name, table) { 1388 | 1389 | var self = this; 1390 | 1391 | if (typeof(table) !== 'string') { 1392 | table = name; 1393 | name = self.index++; 1394 | } 1395 | 1396 | var condition = new SqlBuilder(0, 0, self); 1397 | var fn = function(db, builder, helper, callback) { 1398 | builder.prepare(); 1399 | self.$events.query && self.emit('query', name, builder.debug('delete')); 1400 | var method = db.removeOne || db.remove; 1401 | if (builder._isfirst) { 1402 | method.call(db, builder.builder, { single: true }, function(err, response) { 1403 | var data = response ? (response.result.nRemoved || response.result.n) : 0; 1404 | builder.$callback && builder.$callback(data); 1405 | callback(err, data); 1406 | }); 1407 | } else { 1408 | method.call(db, builder.builder, function(err, response) { 1409 | var data = response ? (response.result.nRemoved || response.result.n) : 0; 1410 | builder.$callback && builder.$callback(data); 1411 | callback(err, data); 1412 | }); 1413 | } 1414 | }; 1415 | 1416 | self.command.push({ type: 'query', table: table, name: name, condition: condition, fn: fn }); 1417 | self.builders[name] = condition; 1418 | return condition; 1419 | }; 1420 | 1421 | Agent.prototype.remove = function(name, table) { 1422 | return this.delete(name, table); 1423 | }; 1424 | 1425 | Agent.prototype.ifnot = function(name, fn) { 1426 | var self = this; 1427 | self.prepare(function(error, response, resume) { 1428 | var value = response[name]; 1429 | if (value instanceof Array) { 1430 | if (value.length) 1431 | return resume(); 1432 | } else if (value) 1433 | return resume(); 1434 | fn.call(self, error, response, value); 1435 | setImmediate(resume); 1436 | }); 1437 | return self; 1438 | }; 1439 | 1440 | Agent.prototype.ifexists = function(name, fn) { 1441 | var self = this; 1442 | self.prepare(function(error, response, resume) { 1443 | 1444 | var value = response[name]; 1445 | if (value instanceof Array) { 1446 | if (!value.length) 1447 | return resume(); 1448 | } else if (!value) 1449 | return resume(); 1450 | 1451 | fn.call(self, error, response, value); 1452 | setImmediate(resume); 1453 | }); 1454 | return self; 1455 | }; 1456 | 1457 | Agent.prototype.destroy = function(name) { 1458 | var self = this; 1459 | for (var i = 0, length = self.command.length; i < length; i++) { 1460 | var item = self.command[i]; 1461 | if (item.name !== name) 1462 | continue; 1463 | self.command.splice(i, 1); 1464 | delete self.builders[name]; 1465 | return true; 1466 | } 1467 | return false; 1468 | }; 1469 | 1470 | Agent.prototype.close = function() { 1471 | var self = this; 1472 | self.done && self.done(); 1473 | self.done = null; 1474 | return self; 1475 | }; 1476 | 1477 | Agent.prototype.rollback = function(where, e, next) { 1478 | var self = this; 1479 | self.errors && self.errors.push(e); 1480 | next(); 1481 | }; 1482 | 1483 | Agent.prototype._prepare = function(callback) { 1484 | 1485 | var self = this; 1486 | 1487 | if (!self.errors) 1488 | self.errors = self.isErrorBuilder ? new global.ErrorBuilder() : []; 1489 | 1490 | self.command.sqlagent(function(item, next) { 1491 | 1492 | if (item.type === 'validate') { 1493 | try { 1494 | var tmp = item.fn(self.errors, self.results, function(output) { 1495 | if (output === true || output === undefined) 1496 | return next(); 1497 | // reason 1498 | if (typeof(output) === 'string') 1499 | self.errors.push(output); 1500 | else if (item.error) 1501 | self.errors.push(item.error); 1502 | next(false); 1503 | }); 1504 | 1505 | var type = typeof(tmp); 1506 | if (type !== 'boolean' && type !== 'string') 1507 | return; 1508 | if (tmp === true || tmp === undefined) 1509 | return next(); 1510 | if (typeof(tmp) === 'string') 1511 | self.errors.push(tmp); 1512 | else if (item.error) 1513 | self.errors.push(item.error); 1514 | next(false); 1515 | } catch (e) { 1516 | self.rollback('validate', e, next); 1517 | } 1518 | return; 1519 | } 1520 | 1521 | if (item.type === 'bookmark') { 1522 | try { 1523 | item.fn(self.errors, self.results); 1524 | return next(); 1525 | } catch (e) { 1526 | self.rollback('bookmark', e, next); 1527 | } 1528 | } 1529 | 1530 | if (item.type === 'primary') { 1531 | self.$primary = item.name; 1532 | next(); 1533 | return; 1534 | } 1535 | 1536 | if (item.type === 'modify') { 1537 | try { 1538 | item.fn(self.results); 1539 | next(); 1540 | } catch (e) { 1541 | self.rollback('modify', e, next); 1542 | } 1543 | return; 1544 | } 1545 | 1546 | if (item.type === 'prepare') { 1547 | try { 1548 | item.fn(self.errors, self.results, () => next()); 1549 | } catch (e) { 1550 | self.rollback('prepare', e, next); 1551 | } 1552 | return; 1553 | } 1554 | 1555 | if (item.type === 'unput') { 1556 | self.isPut = false; 1557 | next(); 1558 | return; 1559 | } 1560 | 1561 | if (item.type === 'put') { 1562 | if (item.disable) 1563 | self.$id = null; 1564 | else 1565 | self.$id = typeof(item.params) === 'function' ? item.params() : item.params; 1566 | self.isPut = !self.disable; 1567 | next(); 1568 | return; 1569 | } 1570 | 1571 | if (self.skipCount) { 1572 | self.skipCount--; 1573 | next(); 1574 | return; 1575 | } 1576 | 1577 | if (typeof(item.name) === 'string') { 1578 | if (self.skips[item.name] === true) { 1579 | next(); 1580 | return; 1581 | } 1582 | } 1583 | 1584 | if (item.type === 'push') { 1585 | item.fn(self.db.collection(item.table), function(err, response) { 1586 | 1587 | self.last = item.name; 1588 | 1589 | if (err) { 1590 | self.errors.push(item.name + ': ' + err.message); 1591 | next(); 1592 | return; 1593 | } 1594 | 1595 | self.results[item.name] = response; 1596 | self.$events.data && self.emit('data', item.name, response); 1597 | next(); 1598 | }); 1599 | return; 1600 | } 1601 | 1602 | if (item.type !== 'query') { 1603 | next(); 1604 | return; 1605 | } 1606 | 1607 | item.fn(self.db.collection(item.table), item.condition, item.helper, function(err, response) { 1608 | 1609 | self.last = item.name; 1610 | 1611 | if (err) { 1612 | self.errors.push(item.name + ': ' + err.message); 1613 | next(); 1614 | return; 1615 | } 1616 | 1617 | var val = item.condition._isfirst && item.condition._order ? (response instanceof Array ? response[0] : response) : response; 1618 | self.results[item.name] = val; 1619 | self.$events.data && self.emit('data', item.name, val); 1620 | 1621 | if (!self.$when) { 1622 | next(); 1623 | return; 1624 | } 1625 | 1626 | var tmp = self.$when[item.name]; 1627 | if (tmp) { 1628 | for (var i = 0, length = tmp.length; i < length; i++) 1629 | tmp[i](self.errors, self.results, self.results[item.name]); 1630 | } 1631 | next(); 1632 | }); 1633 | 1634 | }, function() { 1635 | 1636 | if (Agent.debug || self.debug) { 1637 | self.time = Date.now() - self.debugtime; 1638 | console.log(self.debugname, '----- done (' + self.time + ' ms)'); 1639 | } 1640 | 1641 | self.index = 0; 1642 | self.done && self.done(); 1643 | self.done = null; 1644 | var err = null; 1645 | 1646 | if (self.isErrorBuilder) { 1647 | if (self.errors.hasError()) 1648 | err = self.errors; 1649 | } else if (self.errors.length) 1650 | err = self.errors; 1651 | 1652 | self.$events.end && self.emit('end', err, self.results, self.time); 1653 | callback && callback(err, self.returnIndex !== undefined ? self.results[self.returnIndex] : self.results); 1654 | }); 1655 | 1656 | return self; 1657 | }; 1658 | 1659 | Agent.prototype.exec = function(callback, returnIndex) { 1660 | 1661 | var self = this; 1662 | 1663 | if (Agent.debug || self.debug) { 1664 | self.debugname = 'sqlagent/mongodb (' + Math.floor(Math.random() * 1000) + ')'; 1665 | self.debugtime = Date.now(); 1666 | } 1667 | 1668 | if (returnIndex !== undefined && typeof(returnIndex) !== 'boolean') 1669 | self.returnIndex = returnIndex; 1670 | else 1671 | self.returnIndex = undefined; 1672 | 1673 | if (!self.command.length) { 1674 | callback && callback.call(self, null, {}); 1675 | return self; 1676 | } 1677 | 1678 | (Agent.debug || self.debug) && console.log(self.debugname, '----- exec'); 1679 | 1680 | connect(self.connection, function(err, db) { 1681 | 1682 | if (err) { 1683 | !self.errors && (self.errors = self.isErrorBuilder ? new global.ErrorBuilder() : []); 1684 | self.errors.push(err); 1685 | callback && callback.call(self, self.errors, {}); 1686 | return; 1687 | } 1688 | 1689 | self.db = db; 1690 | self._prepare(callback); 1691 | }); 1692 | 1693 | return self; 1694 | }; 1695 | 1696 | function connect(conn, callback, index) { 1697 | var db = CONNECTIONS[conn]; 1698 | if (db) 1699 | return callback(null, db); 1700 | 1701 | if (index > 60) { 1702 | callback(new Error('SQLAgent: timeout to connect into the database.')); 1703 | return; 1704 | } 1705 | 1706 | if (index === undefined) 1707 | index = 1; 1708 | 1709 | setTimeout(() => connect(conn, callback, index + 1), 100); 1710 | } 1711 | 1712 | Agent.prototype.$$exec = function(returnIndex) { 1713 | var self = this; 1714 | return function(callback) { 1715 | return self.exec(callback, returnIndex); 1716 | }; 1717 | }; 1718 | 1719 | Agent.destroy = function() { 1720 | throw new Error('Not supported.'); 1721 | }; 1722 | 1723 | Agent.prototype.readFile = function(id, options, callback) { 1724 | 1725 | if (typeof(options) === 'function') { 1726 | callback = options; 1727 | options = null; 1728 | } 1729 | 1730 | connect(this.connection, function(err, db) { 1731 | 1732 | if (err) 1733 | return callback(err); 1734 | 1735 | var bucket = new database.GridFSBucket(db, options); 1736 | 1737 | if (bucket.openUploadStream) { 1738 | console.error('SQLAgent error: readFile() is not supported for MongoDB, use readStream().'); 1739 | } else { 1740 | FILEREADERFILTER._id = id; 1741 | bucket.find(FILEREADERFILTER).nextObject(function(err, doc) { 1742 | if (!err && !doc) 1743 | err = new Error('File not found.'); 1744 | if (err) 1745 | return callback(err, null, NOOP); 1746 | callback(null, new GridFSObject(doc._id, doc.metadata, doc.filename, doc.length, doc.contentType, bucket), NOOP, doc.metadata, doc.length, doc.filename); 1747 | }); 1748 | } 1749 | }); 1750 | }; 1751 | 1752 | Agent.prototype.readStream = function(id, options, callback) { 1753 | 1754 | if (typeof(options) === 'function') { 1755 | callback = options; 1756 | options = null; 1757 | } 1758 | 1759 | connect(this.connection, function(err, db) { 1760 | 1761 | if (err) 1762 | return callback(err); 1763 | 1764 | var bucket = new database.GridFSBucket(db, options); 1765 | 1766 | if (bucket.openUploadStream) { 1767 | FILEREADERFILTER._id = id; 1768 | db.collection('fs.files').findOne(FILEREADERFILTER, function(err, doc) { 1769 | if (!err && !doc) 1770 | err = new Error('File not found.'); 1771 | if (err) 1772 | return callback(err, null, NOOP); 1773 | 1774 | callback(null, bucket.openDownloadStream(id), doc.metadata, doc.length, doc.filename); 1775 | }); 1776 | } else { 1777 | FILEREADERFILTER._id = id; 1778 | 1779 | bucket.find(FILEREADERFILTER).nextObject(function(err, doc) { 1780 | if (!err && !doc) 1781 | err = new Error('File not found.'); 1782 | if (err) 1783 | return callback(err); 1784 | callback(null, new GridFSObject(doc._id, doc.metadata, doc.filename, doc.length, doc.contentType, bucket).stream(true), doc.metadata, doc.length, doc.filename); 1785 | }); 1786 | } 1787 | }); 1788 | 1789 | return this; 1790 | }; 1791 | 1792 | Agent.prototype.writeFile = function(id, file, name, meta, options, callback) { 1793 | var self = this; 1794 | 1795 | if (typeof(options) === 'function') { 1796 | callback = options; 1797 | options = null; 1798 | } 1799 | 1800 | if (typeof(meta) === 'function') { 1801 | var tmp = callback; 1802 | callback = meta; 1803 | meta = tmp; 1804 | } 1805 | 1806 | connect(self.connection, function(err, db) { 1807 | 1808 | if (err) { 1809 | self.errors && self.errors.push(err); 1810 | return callback(err); 1811 | } 1812 | 1813 | var bucket = new database.GridFSBucket(db, options); 1814 | var upload = bucket.openUploadStreamWithId(id, name, meta ? { metadata: meta } : undefined); 1815 | var stream = typeof(file.pipe) === 'function' ? file : Fs.createReadStream(file); 1816 | 1817 | stream.pipe(upload).once('finish', function() { 1818 | callback && callback(null); 1819 | callback = null; 1820 | }).once('error', function(err) { 1821 | self.errors && self.errors.push(err); 1822 | callback && callback(err); 1823 | callback = null; 1824 | }); 1825 | }); 1826 | 1827 | return self; 1828 | }; 1829 | 1830 | Agent.prototype.writeStream = function(id, stream, name, meta, options, callback) { 1831 | var self = this; 1832 | 1833 | if (!callback) 1834 | callback = NOOP; 1835 | 1836 | if (typeof(options) === 'function') { 1837 | callback = options; 1838 | options = null; 1839 | } 1840 | 1841 | if (typeof(meta) === 'function') { 1842 | var tmp = callback; 1843 | callback = meta; 1844 | meta = tmp; 1845 | } 1846 | 1847 | connect(self.connection, function(err, db) { 1848 | 1849 | if (err) { 1850 | self.errors && self.errors.push(err); 1851 | callback && callback(err); 1852 | return; 1853 | } 1854 | 1855 | var bucket = new database.GridFSBucket(db, options); 1856 | var upload = bucket.openUploadStreamWithId(id, name, meta ? { metadata: meta } : undefined); 1857 | 1858 | upload.once('finish', function(err) { 1859 | self.errors && self.errors.push(err); 1860 | callback && callback(err); 1861 | callback = null; 1862 | }); 1863 | 1864 | stream.pipe(upload); 1865 | }); 1866 | 1867 | return self; 1868 | }; 1869 | 1870 | Agent.prototype.writeBuffer = function(id, buffer, name, meta, options, callback) { 1871 | var self = this; 1872 | 1873 | if (!callback) 1874 | callback = NOOP; 1875 | 1876 | if (typeof(options) === 'function') { 1877 | callback = options; 1878 | options = null; 1879 | } 1880 | 1881 | if (typeof(meta) === 'function') { 1882 | var tmp = callback; 1883 | callback = meta; 1884 | meta = tmp; 1885 | } 1886 | 1887 | connect(self.connection, function(err, db) { 1888 | 1889 | if (err) { 1890 | self.errors && self.errors.push(err); 1891 | callback && callback(err); 1892 | return; 1893 | } 1894 | 1895 | var bucket = new database.GridFSBucket(db, options); 1896 | var upload = bucket.openUploadStreamWithId(id, name, meta ? { metadata: meta } : undefined); 1897 | 1898 | upload.end(buffer, function(err) { 1899 | self.errors && self.errors.push(err); 1900 | callback && callback(err); 1901 | callback = null; 1902 | }); 1903 | }); 1904 | 1905 | return self; 1906 | }; 1907 | 1908 | Agent.init = function(conn, debug) { 1909 | Agent.debug = debug ? true : false; 1910 | 1911 | F.wait('database'); 1912 | 1913 | database.connect(conn, OPTIONS, function(err, db) { 1914 | if (err) 1915 | throw err; 1916 | 1917 | if (db.db) { 1918 | // new mongodb 1919 | if (conn[conn.length - 1] === '/') 1920 | conn = conn.substring(0, conn.length - 1); 1921 | db = db.db(conn.substring(conn.lastIndexOf('/') + 1)); 1922 | } 1923 | 1924 | CONNECTIONS[conn] = db; 1925 | F.wait('database'); 1926 | EMIT('database', conn); 1927 | }); 1928 | 1929 | F.database = function(errorBuilder) { 1930 | return new Agent(conn, errorBuilder); 1931 | }; 1932 | 1933 | return Agent; 1934 | }; 1935 | 1936 | module.exports = Agent; 1937 | global.SqlBuilder = SqlBuilder; 1938 | global.ObjectID = database.ObjectID; 1939 | global.GridStore = database.GridStore; 1940 | 1941 | ObjectID.parse = function(value, isArray) { 1942 | if (value instanceof ObjectID) 1943 | return value; 1944 | if (isArray || value instanceof Array) 1945 | return ObjectID.parseArray(value); 1946 | try { 1947 | return new ObjectID(value); 1948 | } catch (e) { 1949 | return null; 1950 | } 1951 | }; 1952 | 1953 | ObjectID.parseArray = function(value) { 1954 | 1955 | if (typeof(value) === 'string') 1956 | value = value.split(','); 1957 | 1958 | var arr = []; 1959 | 1960 | if (!(value instanceof Array)) 1961 | return arr; 1962 | 1963 | for (var i = 0, length = value.length; i < length; i++) { 1964 | var id = ObjectID.parse(value[i]); 1965 | id && arr.push(id); 1966 | } 1967 | 1968 | return arr; 1969 | }; 1970 | 1971 | function get(obj, path) { 1972 | 1973 | var cachekey = '=' + path; 1974 | 1975 | if (columns_cache[cachekey]) 1976 | return columns_cache[cachekey](obj); 1977 | 1978 | var arr = path.split('.'); 1979 | var builder = []; 1980 | var p = ''; 1981 | 1982 | for (var i = 0, length = arr.length - 1; i < length; i++) { 1983 | var tmp = arr[i]; 1984 | var index = tmp.lastIndexOf('['); 1985 | if (index !== -1) 1986 | builder.push('if(!w.' + (p ? p + '.' : '') + tmp.substring(0, index) + ')return'); 1987 | p += (p !== '' ? '.' : '') + arr[i]; 1988 | builder.push('if(!w.' + p + ')return'); 1989 | } 1990 | 1991 | var fn = (new Function('w', builder.join(';') + ';return w.' + path.replace(REG_APO, '\''))); 1992 | columns_cache[cachekey] = fn; 1993 | return fn(obj); 1994 | } 1995 | 1996 | function GridFSObject(id, meta, filename, length, type, bucket) { 1997 | this.id = id; 1998 | this.meta = meta; 1999 | this.filename = filename; 2000 | this.length = length; 2001 | this.type = type; 2002 | this.bucket = bucket; 2003 | } 2004 | 2005 | GridFSObject.prototype.stream = function() { 2006 | return this.bucket.openDownloadStream(this.id); 2007 | }; -------------------------------------------------------------------------------- /mysql.js: -------------------------------------------------------------------------------- 1 | const database = require('mysql'); 2 | const Parser = require('url'); 3 | const queries = {}; 4 | const columns_cache = {}; 5 | const pools_cache = {}; 6 | const REG_COLUMN = /^(!{1,}|\s)*/; 7 | const REG_COLUMN_CAST = /`]/g; 8 | const REG_ARGUMENT = /\?/g; 9 | const REG_PARAMS = /#\d+#/g; 10 | const REG_QUERY = /\*/i; 11 | 12 | require('./index'); 13 | 14 | function SqlBuilder(skip, take, agent) { 15 | this.agent = agent; 16 | this.builder = []; 17 | this._order = null; 18 | this._skip = skip >= 0 ? skip : 0; 19 | this._take = take >= 0 ? take : 0; 20 | this._set = null; 21 | this._fn; 22 | this._join; 23 | this._fields; 24 | this._schema; 25 | this._primary; 26 | this._group; 27 | this._having; 28 | this._is = false; 29 | this.hasOperator = false; 30 | } 31 | 32 | SqlBuilder.prototype = { 33 | get data() { 34 | return this._set; 35 | } 36 | }; 37 | 38 | SqlBuilder.prototype.callback = function(fn) { 39 | this.$callback = fn; 40 | return this; 41 | }; 42 | 43 | SqlBuilder.prototype.assign = function(name, key) { 44 | this.$assignname = name; 45 | this.$assignkey = key; 46 | return this; 47 | }; 48 | 49 | SqlBuilder.prototype.replace = function(builder, reference) { 50 | var self = this; 51 | 52 | self.builder = reference ? builder.builder : builder.builder.slice(0); 53 | 54 | if (builder._order) 55 | self._order = reference ? builder._order : builder._order.slice(0); 56 | 57 | self._skip = builder._skip; 58 | self._take = builder._take; 59 | 60 | if (builder._set) 61 | self._set = reference ? builder._set : copy(builder._set); 62 | 63 | if (builder._fn) 64 | self._fn = reference ? builder._fn : copy(builder._fn); 65 | 66 | if (builder._join) 67 | self._join = reference ? builder._join : builder._join.slice(0); 68 | 69 | if (builder._fields) 70 | self._fields = builder._fields; 71 | 72 | if (builder._schema) 73 | self._schema = builder._schema; 74 | 75 | if (builder._primary) 76 | self._primary = builder._primary; 77 | 78 | self._is = builder._is; 79 | self.hasOperator = builder.hasOperator; 80 | return self; 81 | }; 82 | 83 | function copy(source) { 84 | 85 | var keys = Object.keys(source); 86 | var i = keys.length; 87 | var target = {}; 88 | 89 | while (i--) { 90 | var key = keys[i]; 91 | target[key] = source[key]; 92 | } 93 | 94 | return target; 95 | } 96 | 97 | SqlBuilder.prototype.clone = function() { 98 | var builder = new SqlBuilder(0, 0, this); 99 | return builder.replace(this); 100 | }; 101 | 102 | SqlBuilder.prototype.join = function(name, on, type) { 103 | var self = this; 104 | if (!self._join) 105 | self._join = []; 106 | 107 | if (!type) 108 | type = 'left'; 109 | 110 | self._join.push(type + ' join ' + name + ' on ' + on); 111 | return self; 112 | }; 113 | 114 | SqlBuilder.prototype.set = function(name, value) { 115 | var self = this; 116 | if (!self._set) 117 | self._set = {}; 118 | 119 | if (typeof(name) === 'string') { 120 | self._set[name] = value === '$' ? '#00#' : value; 121 | return self; 122 | } 123 | 124 | var keys = Object.keys(name); 125 | 126 | for (var i = 0, length = keys.length; i < length; i++) { 127 | var key = keys[i]; 128 | var val = name[key]; 129 | if (val !== undefined) 130 | self._set[key] = val === '$' ? '#00#' : val; 131 | } 132 | 133 | return self; 134 | }; 135 | 136 | SqlBuilder.prototype.primary = SqlBuilder.prototype.primaryKey = function(name) { 137 | this._primary = name; 138 | return this; 139 | }; 140 | 141 | SqlBuilder.prototype.remove = SqlBuilder.prototype.rem = function(name) { 142 | if (this._set) 143 | this._set[name] = undefined; 144 | return this; 145 | }; 146 | 147 | SqlBuilder.prototype.raw = function(name, value) { 148 | var self = this; 149 | if (!self._set) 150 | self._set = {}; 151 | self._set['!' + name] = value; 152 | return self; 153 | }; 154 | 155 | SqlBuilder.prototype.inc = function(name, type, value) { 156 | 157 | var self = this; 158 | var can = false; 159 | 160 | if (!self._set) 161 | self._set = {}; 162 | 163 | if (value === undefined) { 164 | value = type; 165 | type = '+'; 166 | can = true; 167 | } 168 | 169 | if (typeof(name) === 'string') { 170 | 171 | if (can && typeof(value) === 'string') { 172 | type = value[0]; 173 | switch (type) { 174 | case '+': 175 | case '-': 176 | case '*': 177 | case '/': 178 | value = value.substring(1).parseFloat(); 179 | break; 180 | default: 181 | type = '+'; 182 | value = value.parseFloat(); 183 | break; 184 | } 185 | } else { 186 | type = '+'; 187 | if (value == null) 188 | value = 1; 189 | } 190 | 191 | if (!value) 192 | return self; 193 | 194 | name = type + name; 195 | self._set[name] = value === '$' ? '#00#' : value; 196 | return self; 197 | } 198 | 199 | var keys = Object.keys(name); 200 | 201 | for (var i = 0, length = keys.length; i < length; i++) { 202 | var key = keys[i]; 203 | name[key] && self.inc(key, name[key]); 204 | } 205 | 206 | return self; 207 | }; 208 | 209 | SqlBuilder.prototype.sort = function(name, desc) { 210 | return this.order(name, desc); 211 | }; 212 | 213 | SqlBuilder.prototype.order = function(name, desc) { 214 | 215 | var self = this; 216 | if (!self._order) 217 | self._order = []; 218 | 219 | var key = '<' + name + '.' + self._schema + '.' + (desc || 'false') + '>'; 220 | if (columns_cache[key]) { 221 | self._order.push(columns_cache[key]); 222 | return self; 223 | } 224 | 225 | var lowered = name.toLowerCase(); 226 | if (lowered.lastIndexOf(' desc') !== -1 || lowered.lastIndexOf(' asc') !== -1) { 227 | columns_cache[key] = SqlBuilder.column(name, self._schema); 228 | self._order.push(columns_cache[key]); 229 | return self; 230 | } else if (typeof(desc) === 'boolean') 231 | desc = desc === true ? 'DESC' : 'ASC'; 232 | else 233 | desc = 'ASC'; 234 | 235 | columns_cache[key] = SqlBuilder.column(name, self._schema) + ' ' + desc; 236 | self._order.push(columns_cache[key]); 237 | return self; 238 | }; 239 | 240 | SqlBuilder.prototype.skip = function(value) { 241 | var self = this; 242 | self._skip = self.parseInt(value); 243 | return self; 244 | }; 245 | 246 | SqlBuilder.prototype.limit = function(value) { 247 | return this.take(value); 248 | }; 249 | 250 | SqlBuilder.prototype.page = function(value, max) { 251 | var self = this; 252 | value = self.parseInt(value) - 1; 253 | max = self.parseInt(max); 254 | if (value < 0) 255 | value = 0; 256 | self._skip = value * max; 257 | self._take = max; 258 | return self; 259 | }; 260 | 261 | SqlBuilder.prototype.parseInt = function(num) { 262 | if (typeof(num) === 'number') 263 | return num; 264 | if (!num) 265 | return 0; 266 | num = parseInt(num); 267 | if (isNaN(num)) 268 | num = 0; 269 | return num; 270 | }; 271 | 272 | SqlBuilder.prototype.take = function(value) { 273 | var self = this; 274 | self._take = self.parseInt(value); 275 | return self; 276 | }; 277 | 278 | SqlBuilder.prototype.first = function() { 279 | var self = this; 280 | self._skip = 0; 281 | self._take = 1; 282 | return self; 283 | }; 284 | 285 | SqlBuilder.prototype.where = function(name, operator, value) { 286 | return this.push(name, operator, value); 287 | }; 288 | 289 | SqlBuilder.prototype.push = function(name, operator, value) { 290 | var self = this; 291 | 292 | if (value === undefined) { 293 | value = operator; 294 | operator = '='; 295 | } else if (operator === '!=') 296 | operator = '<>'; 297 | 298 | var is = false; 299 | 300 | // I expect Agent.$$ 301 | if (typeof(value) === 'function') { 302 | if (!self._fn) 303 | self._fn = {}; 304 | var key = Math.floor(Math.random() * 1000000); 305 | self._fn[key] = value; 306 | value = '#' + key + '#'; 307 | is = true; 308 | } 309 | 310 | self.checkOperator(); 311 | self.builder.push(SqlBuilder.column(name, self._schema) + operator + (is ? value : SqlBuilder.escape(value))); 312 | self._is = true; 313 | return self; 314 | }; 315 | 316 | SqlBuilder.prototype.random = function() { 317 | var self = this; 318 | if (!self._order) 319 | self._order = []; 320 | self._order.push('RAND()'); 321 | return self; 322 | }; 323 | 324 | SqlBuilder.prototype.checkOperator = function() { 325 | var self = this; 326 | !self.hasOperator && self.and(); 327 | self.hasOperator = false; 328 | return self; 329 | }; 330 | 331 | SqlBuilder.prototype.clear = function() { 332 | this._take = 0; 333 | this._skip = 0; 334 | this._order = null; 335 | this._set = null; 336 | this.builder = []; 337 | this.hasOperator = false; 338 | return this; 339 | }; 340 | 341 | SqlBuilder.prototype.schema = function(name) { 342 | this._schema = name; 343 | return this; 344 | }; 345 | 346 | SqlBuilder.prototype.fields = function() { 347 | var self = this; 348 | if (!self._fields) 349 | self._fields = ''; 350 | 351 | if (arguments[0] instanceof Array) { 352 | var arr = arguments[0]; 353 | for (var i = 0, length = arr.length; i < length; i++) 354 | self._fields += (self._fields ? ',' : '') + SqlBuilder.column(arr[i], self._schema); 355 | } else { 356 | for (var i = 0; i < arguments.length; i++) 357 | self._fields += (self._fields ? ',' : '') + SqlBuilder.column(arguments[i], self._schema); 358 | } 359 | 360 | return self; 361 | }; 362 | 363 | SqlBuilder.prototype.field = function(name) { 364 | var self = this; 365 | if (!self._fields) 366 | self._fields = ''; 367 | self._fields += (self._fields ? ',' : '') + SqlBuilder.column(name, self._schema); 368 | return self; 369 | }; 370 | 371 | SqlBuilder.column = function(name, schema) { 372 | 373 | var cachekey = (schema ? schema + '.' : '') + name; 374 | var val = columns_cache[cachekey]; 375 | if (val) 376 | return val; 377 | 378 | var raw = false; 379 | 380 | if (name[0] === '!') { 381 | raw = true; 382 | name = name.replace(REG_COLUMN, ''); 383 | } 384 | 385 | var index = name.lastIndexOf('-->'); 386 | var cast = ''; 387 | 388 | if (index !== -1) { 389 | cast = name.substring(index).replace('-->', '').trim(); 390 | name = name.substring(0, index).trim(); 391 | } 392 | 393 | var indexAS = name.toLowerCase().indexOf(' as'); 394 | var plus = ''; 395 | 396 | if (indexAS !== -1) { 397 | plus = name.substring(indexAS); 398 | name = name.substring(0, indexAS); 399 | } else if (cast) 400 | plus = ' as `' + name + '`'; 401 | 402 | var casting = function(value) { 403 | if (!cast) 404 | return value; 405 | return 'CAST(' + value + cast + ')'; 406 | }; 407 | 408 | if (cast) { 409 | switch (cast) { 410 | case 'integer': 411 | case 'int': 412 | case 'byte': 413 | case 'smallint': 414 | case 'number': 415 | cast = 'UNSIGNED'; 416 | break; 417 | } 418 | cast = ' AS ' + cast; 419 | } 420 | 421 | if (raw) 422 | return columns_cache[cachekey] = casting(name) + plus; 423 | 424 | name = name.replace(REG_COLUMN_CAST, ''); 425 | var index = name.indexOf('.'); 426 | if (index === -1) 427 | return columns_cache[cachekey] = casting((schema ? schema + '.' : '') + '`' + name + '`') + plus; 428 | return columns_cache[cachekey] = casting(name.substring(0, index) + '.`' + name.substring(index + 1) + '`') + plus; 429 | }; 430 | 431 | SqlBuilder.prototype.group = function(names) { 432 | var self = this; 433 | 434 | if (names instanceof Array) { 435 | for (var i = 0, length = names.length; i < length; i++) 436 | names[i] = SqlBuilder.column(names[i], self._schema); 437 | self._group = 'GROUP BY ' + names.join(','); 438 | } else if (names) { 439 | var arr = new Array(arguments.length); 440 | for (var i = 0; i < arguments.length; i++) 441 | arr[i] = SqlBuilder.column(arguments[i.toString()], self._schema); 442 | self._group = 'GROUP BY ' + arr.join(','); 443 | } else 444 | self._group = undefined; 445 | 446 | return self; 447 | }; 448 | 449 | SqlBuilder.prototype.having = function(condition) { 450 | var self = this; 451 | 452 | if (condition) 453 | self._having = 'HAVING ' + condition; 454 | else 455 | self._having = undefined; 456 | 457 | return self; 458 | }; 459 | 460 | SqlBuilder.prototype.and = function() { 461 | var self = this; 462 | if (!self.builder.length) 463 | return self; 464 | self.hasOperator = true; 465 | self.builder.push('AND'); 466 | return self; 467 | }; 468 | 469 | SqlBuilder.prototype.or = function() { 470 | var self = this; 471 | if (!self.builder.length) 472 | return self; 473 | self.hasOperator = true; 474 | self.builder.push('OR'); 475 | return self; 476 | }; 477 | 478 | SqlBuilder.prototype.scope = function(fn) { 479 | var self = this; 480 | self.checkOperator(); 481 | self.builder.push('('); 482 | self.hasOperator = true; 483 | fn.call(self); 484 | self.builder.push(')'); 485 | return self; 486 | }; 487 | 488 | SqlBuilder.prototype.in = function(name, value) { 489 | var self = this; 490 | if (!(value instanceof Array)) { 491 | self.where(name, value); 492 | return self; 493 | } 494 | self.checkOperator(); 495 | var values = []; 496 | for (var i = 0, length = value.length; i < length; i++) 497 | values.push(SqlBuilder.escape(value[i])); 498 | self.builder.push(SqlBuilder.column(name, self._schema) + ' IN (' + values.join(',') + ')'); 499 | self._is = true; 500 | return self; 501 | }; 502 | 503 | SqlBuilder.prototype.like = function(name, value, where) { 504 | var self = this; 505 | var search; 506 | 507 | self.checkOperator(); 508 | 509 | switch (where) { 510 | case 'beg': 511 | case 'begin': 512 | search = SqlBuilder.escape('%' + value); 513 | break; 514 | case '*': 515 | search = SqlBuilder.escape('%' + value + '%'); 516 | break; 517 | case 'end': 518 | search = SqlBuilder.escape(value + '%'); 519 | break; 520 | default: 521 | search = SqlBuilder.escape(value); 522 | break; 523 | } 524 | 525 | self.builder.push(SqlBuilder.column(name, self._schema) + ' LIKE ' + search); 526 | self._is = true; 527 | return self; 528 | }; 529 | 530 | SqlBuilder.prototype.between = function(name, valueA, valueB) { 531 | var self = this; 532 | self.checkOperator(); 533 | self.builder.push(SqlBuilder.column(name, self._schema) + ' BETWEEN ' + SqlBuilder.escape(valueA) + ' AND ' + SqlBuilder.escape(valueB)); 534 | self._is = true; 535 | return self; 536 | }; 537 | 538 | SqlBuilder.prototype.query = SqlBuilder.prototype.sql = function(sql) { 539 | var self = this; 540 | self.checkOperator(); 541 | 542 | if (arguments.length > 1) { 543 | var indexer = 1; 544 | var argv = arguments; 545 | sql = sql.replace(REG_ARGUMENT, () => SqlBuilder.escape(argv[indexer++])); 546 | } 547 | 548 | self.builder.push(sql); 549 | self._is = true; 550 | return self; 551 | }; 552 | 553 | SqlBuilder.prototype.toString = function(id, isCounter) { 554 | 555 | var self = this; 556 | var plus = ''; 557 | var order = ''; 558 | var join = ''; 559 | 560 | if (self._join) 561 | join = self._join.join(' '); 562 | 563 | if (!isCounter) { 564 | if (self._order) 565 | order = ' ORDER BY ' + self._order.join(','); 566 | if (self._skip && self._take) 567 | plus = ' LIMIT ' + self._skip + ',' + self._take; 568 | else if (self._take) 569 | plus = ' LIMIT ' + self._take; 570 | else if (self._skip) 571 | plus = ' LIMIT ' + self._skip + ',row_count'; 572 | } 573 | 574 | if (!self.builder.length) 575 | return (join ? ' ' + join : '') + (self._group ? ' ' + self._group : '') + (self._having ? ' ' + self._having : '') + order + plus; 576 | 577 | var where = self.builder.join(' '); 578 | 579 | if (id === undefined) 580 | id = null; 581 | 582 | if (self._fn) 583 | where = where.replace(REG_PARAMS, text => text === '#00#' ? SqlBuilder.escape(id) : SqlBuilder.escape(self._fn[parseInt(text.substring(1, text.length - 1))])); 584 | 585 | return (join ? ' ' + join : '') + (self._is ? ' WHERE ' : ' ') + where + (self._group ? ' ' + self._group : '') + (self._having ? ' ' + self._having : '') + order + plus; 586 | }; 587 | 588 | SqlBuilder.prototype.make = function(fn) { 589 | var self = this; 590 | fn.call(self, self); 591 | return self.agent || self; 592 | }; 593 | 594 | SqlBuilder.prototype.toQuery = function(query) { 595 | var self = this; 596 | return self._fields ? query.replace(REG_QUERY, self._fields) : query; 597 | }; 598 | 599 | function Agent(options, error, id) { 600 | this.$conn = id === undefined ? JSON.stringify(options) : id; 601 | this.options = options; 602 | this.isErrorBuilder = typeof(global.ErrorBuilder) !== 'undefined' ? true : false; 603 | this.errors = this.isErrorBuilder ? error : null; 604 | this.clear(); 605 | this.$events = {}; 606 | 607 | // Hidden: 608 | // this.time 609 | // this.$when; 610 | } 611 | 612 | Agent.prototype = { 613 | get $() { 614 | return new SqlBuilder(0, 0, this); 615 | }, 616 | get $$() { 617 | var self = this; 618 | return function() { 619 | return self.$id; 620 | }; 621 | } 622 | }; 623 | 624 | // Debug mode (output to console) 625 | Agent.debug = false; 626 | 627 | Agent.connect = function(conn, callback) { 628 | callback && callback(null); 629 | var id = (Math.random() * 1000000) >> 0; 630 | return function(error) { 631 | return new Agent(conn, error, id); 632 | }; 633 | }; 634 | 635 | Agent.prototype.promise = function(index, fn) { 636 | var self = this; 637 | 638 | if (typeof(index) === 'function') { 639 | fn = index; 640 | index = undefined; 641 | } 642 | 643 | return new Promise(function(resolve, reject) { 644 | self.exec(function(err, result) { 645 | if (err) 646 | reject(err); 647 | else 648 | resolve(fn ? fn(result) : result); 649 | }, index); 650 | }); 651 | }; 652 | 653 | Agent.prototype.emit = function(name, a, b, c, d, e, f, g) { 654 | var evt = this.$events[name]; 655 | if (evt) { 656 | var clean = false; 657 | for (var i = 0, length = evt.length; i < length; i++) { 658 | if (evt[i].$once) 659 | clean = true; 660 | evt[i].call(this, a, b, c, d, e, f, g); 661 | } 662 | if (clean) { 663 | evt = evt.remove(n => n.$once); 664 | if (evt.length) 665 | this.$events[name] = evt; 666 | else 667 | this.$events[name] = undefined; 668 | } 669 | } 670 | return this; 671 | }; 672 | 673 | Agent.prototype.on = function(name, fn) { 674 | 675 | if (!fn.$once) 676 | this.$free = false; 677 | 678 | if (this.$events[name]) 679 | this.$events[name].push(fn); 680 | else 681 | this.$events[name] = [fn]; 682 | return this; 683 | }; 684 | 685 | Agent.prototype.once = function(name, fn) { 686 | fn.$once = true; 687 | return this.on(name, fn); 688 | }; 689 | 690 | Agent.prototype.removeListener = function(name, fn) { 691 | var evt = this.$events[name]; 692 | if (evt) { 693 | evt = evt.remove(n => n === fn); 694 | if (evt.length) 695 | this.$events[name] = evt; 696 | else 697 | this.$events[name] = undefined; 698 | } 699 | return this; 700 | }; 701 | 702 | Agent.prototype.removeAllListeners = function(name) { 703 | if (name === true) 704 | this.$events = EMPTYOBJECT; 705 | else if (name) 706 | this.$events[name] = undefined; 707 | else 708 | this.$events[name] = {}; 709 | return this; 710 | }; 711 | 712 | Agent.prototype.clear = function() { 713 | this.db = null; 714 | this.command = []; 715 | this.done = null; 716 | this.last = null; 717 | this.id = null; 718 | this.$id = null; 719 | this.isCanceled = false; 720 | this.index = 0; 721 | this.isPut = false; 722 | this.skipCount = 0; 723 | this.skips = {}; 724 | this.results = {}; 725 | this.builders = {}; 726 | 727 | if (this.$when) 728 | this.$when = undefined; 729 | 730 | if (this.errors && this.isErrorBuilder) 731 | this.errors.clear(); 732 | else if (this.errors) 733 | this.errors = null; 734 | 735 | return this; 736 | }; 737 | 738 | Agent.prototype.when = function(name, fn) { 739 | 740 | if (!this.$when) 741 | this.$when = {}; 742 | 743 | if (!this.$when[name]) 744 | this.$when[name] = [fn]; 745 | else 746 | this.$when[name].push(fn); 747 | 748 | return this; 749 | }; 750 | 751 | Agent.prototype.priority = function() { 752 | var self = this; 753 | var length = self.command.length - 1; 754 | 755 | if (!length) 756 | return self; 757 | 758 | var last = self.command[length]; 759 | 760 | for (var i = length; i > -1; i--) 761 | self.command[i] = self.command[i - 1]; 762 | 763 | self.command[0] = last; 764 | return self; 765 | }; 766 | 767 | Agent.prototype.default = function(fn) { 768 | fn.call(this.results, this.results); 769 | return this; 770 | }; 771 | 772 | Agent.query = function(name, query) { 773 | queries[name] = query; 774 | return Agent; 775 | }; 776 | 777 | Agent.prototype.skip = function(name) { 778 | var self = this; 779 | if (name) 780 | self.skips[name] = true; 781 | else 782 | self.skipCount++; 783 | return self; 784 | }; 785 | 786 | Agent.prototype.primaryKey = Agent.prototype.primary = function() { 787 | // compatibility with PG 788 | return this; 789 | }; 790 | 791 | Agent.prototype.prepare = function(fn) { 792 | var self = this; 793 | self.command.push({ type: 'prepare', fn: fn }); 794 | return self; 795 | }; 796 | 797 | Agent.prototype.ifnot = function(name, fn) { 798 | var self = this; 799 | self.prepare(function(error, response, resume) { 800 | var value = response[name]; 801 | if (value instanceof Array) { 802 | if (value.length) 803 | return resume(); 804 | } else if (value) 805 | return resume(); 806 | fn.call(self, error, response, value); 807 | setImmediate(resume); 808 | }); 809 | return self; 810 | }; 811 | 812 | Agent.prototype.ifexists = function(name, fn) { 813 | var self = this; 814 | self.prepare(function(error, response, resume) { 815 | 816 | var value = response[name]; 817 | if (value instanceof Array) { 818 | if (!value.length) 819 | return resume(); 820 | } else if (!value) 821 | return resume(); 822 | 823 | fn.call(self, error, response, value); 824 | setImmediate(resume); 825 | }); 826 | return self; 827 | }; 828 | 829 | Agent.prototype.modify = function(fn) { 830 | var self = this; 831 | self.command.push({ type: 'modify', fn: fn }); 832 | return self; 833 | }; 834 | 835 | Agent.prototype.bookmark = function(fn) { 836 | var self = this; 837 | self.command.push({ type: 'bookmark', fn: fn }); 838 | return self; 839 | }; 840 | 841 | Agent.prototype.put = function(value) { 842 | var self = this; 843 | self.command.push({ type: 'put', value: value, disable: value == null }); 844 | return self; 845 | }; 846 | 847 | Agent.prototype.lock = function() { 848 | return this.put(this.$$); 849 | }; 850 | 851 | Agent.prototype.unlock = function() { 852 | this.command.push({ 'type': 'unput' }); 853 | return this; 854 | }; 855 | 856 | Agent.prototype.query = function(name, query, params) { 857 | return this.push(name, query, params); 858 | }; 859 | 860 | Agent.prototype.push = function(name, query, params) { 861 | var self = this; 862 | 863 | if (typeof(query) !== 'string') { 864 | params = query; 865 | query = name; 866 | name = self.index++; 867 | } 868 | 869 | var is = false; 870 | 871 | if (!params) { 872 | is = true; 873 | params = new SqlBuilder(0, 0, self); 874 | } 875 | 876 | if (queries[query]) 877 | query = queries[query]; 878 | 879 | self.command.push({ type: 'push', name: name, query: query, condition: params, first: isFIRST(query) }); 880 | self.builders[name] = params; 881 | return is ? params : self; 882 | }; 883 | 884 | Agent.prototype.validate = function(fn, error, reverse) { 885 | var self = this; 886 | var type = typeof(fn); 887 | 888 | if (typeof(error) === 'boolean') { 889 | reverse = error; 890 | error = undefined; 891 | } 892 | 893 | if (type === 'string' && error === undefined) { 894 | // checks the last result 895 | error = fn; 896 | fn = undefined; 897 | } 898 | 899 | if (type === 'function') { 900 | self.command.push({ type: 'validate', fn: fn, error: error }); 901 | return self; 902 | } 903 | 904 | if (type === 'string' && typeof(error) === 'function' && typeof(reverse) === 'string') 905 | return self.validate2(fn, error, reverse); 906 | 907 | var exec; 908 | 909 | if (reverse) { 910 | exec = function(err, results, next) { 911 | var id = fn == null ? self.last : fn; 912 | if (id == null) 913 | return next(true); 914 | var r = results[id]; 915 | if (r instanceof Array) 916 | return next(r.length === 0); 917 | if (r) 918 | return next(false); 919 | next(true); 920 | }; 921 | } else { 922 | exec = function(err, results, next) { 923 | var id = fn == null ? self.last : fn; 924 | if (id == null) 925 | return next(false); 926 | var r = results[id]; 927 | if (r instanceof Array) 928 | return next(r.length > 0); 929 | if (r) 930 | return next(true); 931 | next(false); 932 | }; 933 | } 934 | 935 | self.command.push({ type: 'validate', fn: exec, error: error }); 936 | return self; 937 | }; 938 | 939 | // validate2('result', n => n.length > 0, 'error'); 940 | Agent.prototype.validate2 = function(name, fn, err) { 941 | var self = this; 942 | var type = typeof(fn); 943 | 944 | if (type === 'string') { 945 | type = err; 946 | err = fn; 947 | fn = type; 948 | } 949 | 950 | var validator = function(err, results, next) { 951 | if (fn(results[name])) 952 | return next(true); 953 | err.push(err || name); 954 | next(false); 955 | }; 956 | 957 | self.command.push({ type: 'validate', fn: validator, error: err }); 958 | return self; 959 | }; 960 | 961 | Agent.prototype.cancel = function(fn) { 962 | return this.validate(fn); 963 | }; 964 | 965 | Agent.prototype.begin = function() { 966 | var self = this; 967 | self.command.push({ type: 'begin' }); 968 | return self; 969 | }; 970 | 971 | Agent.prototype.end = function() { 972 | var self = this; 973 | self.command.push({ type: 'end' }); 974 | return self; 975 | }; 976 | 977 | Agent.prototype.commit = function() { 978 | return this.end(); 979 | }; 980 | 981 | Agent.prototype._insert = function(item) { 982 | 983 | var values = item.condition._set; 984 | var keys = Object.keys(values); 985 | 986 | var columns = []; 987 | var columns_values = []; 988 | var params = []; 989 | 990 | for (var i = 0, length = keys.length; i < length; i++) { 991 | var key = keys[i]; 992 | var value = values[key]; 993 | 994 | var isRAW = key[0] === '!'; 995 | if (isRAW) 996 | key = key.substring(1); 997 | 998 | if (key[0] === '$' || value === undefined) 999 | continue; 1000 | 1001 | switch (key[0]) { 1002 | case '+': 1003 | case '-': 1004 | case '*': 1005 | case '/': 1006 | key = key.substring(1); 1007 | if (!value) 1008 | value = 1; 1009 | break; 1010 | default: 1011 | break; 1012 | } 1013 | 1014 | columns.push('`' + key + '`'); 1015 | 1016 | if (isRAW) { 1017 | columns_values.push(value); 1018 | continue; 1019 | } 1020 | 1021 | columns_values.push('?'); 1022 | 1023 | var type = typeof(value); 1024 | if (type === 'function') 1025 | value = value(); 1026 | 1027 | if (type === 'string') 1028 | value = value.trim(); 1029 | 1030 | params.push(value === undefined ? null : value); 1031 | } 1032 | 1033 | item.$query = 'INSERT INTO ' + item.table + ' (' + columns.join(',') + ') VALUES(' + columns_values.join(',') + ')'; 1034 | item.$params = params; 1035 | item.first = true; 1036 | 1037 | return item; 1038 | }; 1039 | 1040 | Agent.prototype._update = function(item) { 1041 | 1042 | var values = item.condition._set; 1043 | var keys = Object.keys(values); 1044 | 1045 | var columns = []; 1046 | var params = []; 1047 | 1048 | for (var i = 0, length = keys.length; i < length; i++) { 1049 | var key = keys[i]; 1050 | var value = values[key]; 1051 | 1052 | var isRAW = key[0] === '!'; 1053 | if (isRAW) 1054 | key = key.substring(1); 1055 | 1056 | if (key[0] === '$' || value === undefined) 1057 | continue; 1058 | 1059 | var type = typeof(value); 1060 | if (type === 'function') 1061 | value = value(); 1062 | 1063 | if (type === 'string') 1064 | value = value.trim(); 1065 | 1066 | switch (key[0]) { 1067 | case '+': 1068 | key = key.substring(1); 1069 | columns.push('`' + key + '`=COALESCE(`' + key + '`,0)+?'); 1070 | if (!value) 1071 | value = 1; 1072 | break; 1073 | case '-': 1074 | key = key.substring(1); 1075 | columns.push('`' + key + '`=COALESCE(`' + key + '`,0)-?'); 1076 | if (!value) 1077 | value = 1; 1078 | break; 1079 | case '*': 1080 | key = key.substring(1); 1081 | columns.push('`' + key + '`=COALESCE(`' + key + '`,0)*?'); 1082 | if (!value) 1083 | value = 1; 1084 | break; 1085 | case '/': 1086 | key = key.substring(1); 1087 | columns.push('`' + key + '`=COALESCE(`' + key + '`,0)/?'); 1088 | if (!value) 1089 | value = 1; 1090 | break; 1091 | default: 1092 | if (isRAW) 1093 | columns.push('`' + key + '`=' + value); 1094 | else 1095 | columns.push('`' + key + '`=?'); 1096 | break; 1097 | } 1098 | 1099 | !isRAW && params.push(value === undefined ? null : value); 1100 | } 1101 | 1102 | item.$query = 'UPDATE ' + item.table + ' SET ' + columns.join(',') + item.condition.toString(this.id); 1103 | item.$params = params; 1104 | item.column = 'affectedRows'; 1105 | item.first = true; 1106 | 1107 | return item; 1108 | }; 1109 | 1110 | Agent.prototype._select = function(item) { 1111 | item.query = 'SELECT * FROM ' + item.table; 1112 | item.$query = item.condition.toQuery(item.query) + item.condition.toString(this.id); 1113 | item.first = item.condition._take === 1; 1114 | return item; 1115 | }; 1116 | 1117 | Agent.prototype._compare = function(item) { 1118 | var keys = item.keys ? item.keys : item.condition._fields ? item.condition._fields.split(',') : Object.keys(item.value); 1119 | 1120 | if (!item.condition._fields) 1121 | item.condition.fields.apply(item.condition, keys); 1122 | 1123 | item.query = 'SELECT * FROM ' + item.table; 1124 | item.$query = item.condition.toQuery(item.query) + item.condition.toString(this.id); 1125 | item.first = item.condition._take === 1; 1126 | return item; 1127 | }; 1128 | 1129 | Agent.prototype._delete = function(item) { 1130 | item.$query = item.query + item.condition.toString(this.id); 1131 | item.first = true; 1132 | item.column = 'affectedRows'; 1133 | return item; 1134 | }; 1135 | 1136 | Agent.prototype._query = function(item) { 1137 | if (item.condition instanceof SqlBuilder) { 1138 | item.$query = (item.scalar ? item.query : item.condition.toQuery(item.query)) + item.condition.toString(this.id, item.scalar); 1139 | return item; 1140 | } 1141 | item.$query = item.query; 1142 | item.$params = item.condition; 1143 | return item; 1144 | }; 1145 | 1146 | Agent.prototype.save = function(name, table, insert, maker) { 1147 | 1148 | if (typeof(table) === 'boolean') { 1149 | maker = insert; 1150 | insert = table; 1151 | table = name; 1152 | name = undefined; 1153 | } 1154 | 1155 | var self = this; 1156 | if (insert) 1157 | maker(self.insert(name, table), true); 1158 | else 1159 | maker(self.update(name, table), false); 1160 | 1161 | return self; 1162 | }; 1163 | 1164 | Agent.prototype.insert = function(name, table) { 1165 | 1166 | var self = this; 1167 | 1168 | if (typeof(table) !== 'string') { 1169 | table = name; 1170 | name = self.index++; 1171 | } 1172 | 1173 | var condition = new SqlBuilder(0, 0, self); 1174 | self.command.push({ type: 'insert', table: table, name: name, condition: condition }); 1175 | self.builders[name] = condition; 1176 | return condition; 1177 | }; 1178 | 1179 | Agent.prototype.select = function(name, table) { 1180 | 1181 | var self = this; 1182 | 1183 | if (typeof(table) !== 'string') { 1184 | table = name; 1185 | name = self.index++; 1186 | } 1187 | 1188 | var condition = new SqlBuilder(0, 0, self); 1189 | self.command.push({ type: 'select', name: name, table: table, condition: condition }); 1190 | self.builders[name] = condition; 1191 | return condition; 1192 | }; 1193 | 1194 | Agent.prototype.compare = function(name, table, obj, keys) { 1195 | 1196 | var self = this; 1197 | 1198 | if (typeof(table) !== 'string') { 1199 | keys = obj; 1200 | obj = table; 1201 | table = name; 1202 | name = self.index++; 1203 | } 1204 | 1205 | var condition = new SqlBuilder(0, 0, self); 1206 | condition.first(); 1207 | self.command.push({ type: 'compare', name: name, table: table, condition: condition, value: obj, keys: keys }); 1208 | self.builders[name] = condition; 1209 | return condition; 1210 | }; 1211 | 1212 | Agent.prototype.listing = function(name, table, column) { 1213 | 1214 | var self = this; 1215 | if (typeof(table) !== 'string') { 1216 | table = name; 1217 | name = self.index++; 1218 | } 1219 | 1220 | var key ='$listing_' + name; 1221 | var condition = new SqlBuilder(0, 0, self); 1222 | self.command.push({ type: 'query', query: 'SELECT COUNT(' + (column || '*') + ') as sqlagentcolumn FROM ' + table, name: key + '_count', condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true, nocallback: true }); 1223 | self.command.push({ type: 'select', name: key + '_items', table: table, condition: condition, listing: key, target: name }); 1224 | self.builders[name] = condition; 1225 | return condition; 1226 | }; 1227 | 1228 | Agent.prototype.find = Agent.prototype.builder = function(name) { 1229 | return this.builders[name]; 1230 | }; 1231 | 1232 | Agent.prototype.exists = function(name, table) { 1233 | var self = this; 1234 | 1235 | if (typeof(table) !== 'string') { 1236 | table = name; 1237 | name = self.index++; 1238 | } 1239 | 1240 | var condition = new SqlBuilder(0, 0, self); 1241 | condition.first(); 1242 | self.command.push({ type: 'query', query: 'SELECT 1 as sqlagentcolumn_e FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn_e', scalar: true }); 1243 | self.builders[name] = condition; 1244 | return condition; 1245 | }; 1246 | 1247 | Agent.prototype.count = function(name, table, column) { 1248 | var self = this; 1249 | 1250 | if (typeof(table) !== 'string') { 1251 | table = name; 1252 | name = self.index++; 1253 | } 1254 | 1255 | var condition = new SqlBuilder(0, 0, self); 1256 | self.command.push({ type: 'query', query: 'SELECT COUNT(' + (column || '*') + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1257 | self.builders[name] = condition; 1258 | return condition; 1259 | }; 1260 | 1261 | Agent.prototype.max = function(name, table, column) { 1262 | var self = this; 1263 | 1264 | if (typeof(table) !== 'string') { 1265 | table = name; 1266 | name = self.index++; 1267 | } 1268 | 1269 | var condition = new SqlBuilder(0, 0, self); 1270 | self.command.push({ type: 'query', query: 'SELECT MAX(' + column + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1271 | self.builders[name] = condition; 1272 | return condition; 1273 | }; 1274 | 1275 | Agent.prototype.min = function(name, table, column) { 1276 | var self = this; 1277 | if (typeof(table) !== 'string') { 1278 | table = name; 1279 | name = self.index++; 1280 | } 1281 | 1282 | var condition = new SqlBuilder(0, 0, self); 1283 | self.command.push({ type: 'query', query: 'SELECT MIN(' + column + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1284 | self.builders[name] = condition; 1285 | return condition; 1286 | }; 1287 | 1288 | Agent.prototype.avg = function(name, table, column) { 1289 | var self = this; 1290 | if (typeof(table) !== 'string') { 1291 | table = name; 1292 | name = self.index++; 1293 | } 1294 | 1295 | var condition = new SqlBuilder(0, 0, self); 1296 | self.command.push({ type: 'query', query: 'SELECT AVG(' + column + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1297 | self.builders[name] = condition; 1298 | return condition; 1299 | }; 1300 | 1301 | Agent.prototype.update = function(name, table) { 1302 | 1303 | var self = this; 1304 | 1305 | if (typeof(table) !== 'string') { 1306 | table = name; 1307 | name = self.index++; 1308 | } 1309 | 1310 | var condition = new SqlBuilder(0, 0, self); 1311 | self.command.push({ type: 'update', table: table, name: name, condition: condition }); 1312 | self.builders[name] = condition; 1313 | return condition; 1314 | }; 1315 | 1316 | Agent.prototype.delete = function(name, table) { 1317 | 1318 | var self = this; 1319 | 1320 | if (typeof(table) !== 'string') { 1321 | table = name; 1322 | name = self.index++; 1323 | } 1324 | 1325 | var condition = new SqlBuilder(0, 0, self); 1326 | self.command.push({ type: 'delete', query: 'DELETE FROM ' + table, name: name, condition: condition }); 1327 | self.builders[name] = condition; 1328 | return condition; 1329 | }; 1330 | 1331 | Agent.prototype.remove = function(name, table) { 1332 | return this.delete(name, table); 1333 | }; 1334 | 1335 | Agent.prototype.destroy = function(name) { 1336 | var self = this; 1337 | for (var i = 0, length = self.command.length; i < length; i++) { 1338 | var item = self.command[i]; 1339 | if (item.name !== name) 1340 | continue; 1341 | self.command.splice(i, 1); 1342 | delete self.builders[name]; 1343 | return true; 1344 | } 1345 | return false; 1346 | }; 1347 | 1348 | Agent.prototype.expected = function(name, index, property) { 1349 | 1350 | var self = this; 1351 | 1352 | if (typeof(index) === 'string') { 1353 | property = index; 1354 | index = undefined; 1355 | } 1356 | 1357 | return function() { 1358 | var output = self.results[name]; 1359 | if (!output) 1360 | return null; 1361 | if (index === undefined) 1362 | return property === undefined ? output : output[property]; 1363 | output = output[index]; 1364 | return output ? output[property] : null; 1365 | }; 1366 | }; 1367 | 1368 | Agent.prototype.close = function() { 1369 | var self = this; 1370 | self.done && self.done(); 1371 | self.done = null; 1372 | return self; 1373 | }; 1374 | 1375 | Agent.prototype.rollback = function(where, e, next) { 1376 | var self = this; 1377 | self.errors && self.errors.push(e); 1378 | self.command.length = 0; 1379 | if (!self.isTransaction) 1380 | return next(); 1381 | self.isRollback = true; 1382 | self.end(); 1383 | next(); 1384 | }; 1385 | 1386 | Agent.prototype._prepare = function(callback) { 1387 | 1388 | var self = this; 1389 | 1390 | self.isRollback = false; 1391 | self.isTransaction = false; 1392 | 1393 | if (!self.errors) 1394 | self.errors = self.isErrorBuilder ? new global.ErrorBuilder() : []; 1395 | 1396 | self.command.sqlagent(function(item, next) { 1397 | 1398 | if (item.type === 'validate') { 1399 | try { 1400 | var tmp = item.fn(self.errors, self.results, function(output) { 1401 | if (output === true || output === undefined) 1402 | return next(); 1403 | // reason 1404 | if (typeof(output) === 'string') 1405 | self.errors.push(output); 1406 | else if (item.error) 1407 | self.errors.push(item.error); 1408 | // we have error 1409 | if (self.isTransaction) { 1410 | self.command.length = 0; 1411 | self.isRollback = true; 1412 | self.end(); 1413 | next(); 1414 | } else 1415 | next(false); 1416 | }); 1417 | 1418 | var type = typeof(tmp); 1419 | if (type !== 'boolean' && type !== 'string') 1420 | return; 1421 | if (tmp === true || tmp === undefined) 1422 | return next(); 1423 | // reason 1424 | if (typeof(tmp) === 'string') 1425 | self.errors.push(tmp); 1426 | else if (item.error) 1427 | self.errors.push(item.error); 1428 | // we have error 1429 | if (self.isTransaction) { 1430 | self.command.length = 0; 1431 | self.isRollback = true; 1432 | self.end(); 1433 | next(); 1434 | } else 1435 | next(false); 1436 | return; 1437 | } catch (e) { 1438 | self.rollback('validate', e, next); 1439 | } 1440 | return; 1441 | } 1442 | 1443 | if (item.type === 'bookmark') { 1444 | try { 1445 | item.fn(self.errors, self.results); 1446 | return next(); 1447 | } catch (e) { 1448 | self.rollback('bookmark', e, next); 1449 | } 1450 | } 1451 | 1452 | if (item.type === 'modify') { 1453 | try { 1454 | item.fn(self.results); 1455 | next(); 1456 | } catch (e) { 1457 | self.rollback('modify', e, next); 1458 | } 1459 | return; 1460 | } 1461 | 1462 | if (item.type === 'prepare') { 1463 | try { 1464 | item.fn(self.errors, self.results, function() { 1465 | next(); 1466 | }); 1467 | } catch (e) { 1468 | self.rollback('prepare', e, next); 1469 | } 1470 | return; 1471 | } 1472 | 1473 | if (item.type === 'unput') { 1474 | self.isPut = false; 1475 | next(); 1476 | return; 1477 | } 1478 | 1479 | if (item.type === 'put') { 1480 | if (item.disable) 1481 | self.$id = null; 1482 | else 1483 | self.$id = typeof(item.value) === 'function' ? item.value() : item.value; 1484 | self.isPut = !self.disable; 1485 | next(); 1486 | return; 1487 | } 1488 | 1489 | if (self.skipCount) { 1490 | self.skipCount--; 1491 | next(); 1492 | return; 1493 | } 1494 | 1495 | if (typeof(item.name) === 'string') { 1496 | if (self.skips[item.name] === true) { 1497 | next(); 1498 | return; 1499 | } 1500 | } 1501 | 1502 | switch (item.type) { 1503 | case 'select': 1504 | self._select(item); 1505 | break; 1506 | case 'update': 1507 | self._update(item); 1508 | break; 1509 | case 'insert': 1510 | self._insert(item); 1511 | break; 1512 | case 'delete': 1513 | self._delete(item); 1514 | break; 1515 | case 'compare': 1516 | self._compare(item); 1517 | break; 1518 | default: 1519 | self._query(item); 1520 | break; 1521 | } 1522 | 1523 | if (item.type !== 'begin' && item.type !== 'end') { 1524 | 1525 | if (!item.first) 1526 | item.first = isFIRST(item.$query); 1527 | 1528 | (Agent.debug || self.debug) && console.log(self.debugname, item.name, item.$query); 1529 | self.$events.query && self.emit('query', item.name, item.$query, item.$params); 1530 | 1531 | self.db.query(item.$query, item.$params, function(err, rows) { 1532 | self.$bind(item, err, rows); 1533 | next(); 1534 | }); 1535 | 1536 | return; 1537 | } 1538 | 1539 | if (item.type === 'begin') { 1540 | (Agent.debug || self.debug) && console.log(self.debugname, 'begin transaction'); 1541 | self.db.query('BEGIN', function(err) { 1542 | if (err) { 1543 | self.errors.push(err.message); 1544 | self.command.length = 0; 1545 | next(false); 1546 | return; 1547 | } 1548 | self.isTransaction = true; 1549 | self.isRollback = false; 1550 | next(); 1551 | }); 1552 | return; 1553 | } 1554 | 1555 | if (item.type === 'end') { 1556 | self.isTransaction = false; 1557 | if (self.isRollback) { 1558 | (Agent.debug || self.debug) && console.log(self.debugname, 'rollback transaction'); 1559 | self.db.query('ROLLBACK', function(err) { 1560 | if (!err) 1561 | return next(); 1562 | self.command.length = 0; 1563 | self.push(err.message); 1564 | next(false); 1565 | }); 1566 | return; 1567 | } 1568 | 1569 | (Agent.debug || self.debug) && console.log(self.debugname, 'commit transaction'); 1570 | 1571 | self.db.query('COMMIT', function(err) { 1572 | if (!err) 1573 | return next(); 1574 | self.errors.push(err.message); 1575 | self.command.length = 0; 1576 | self.db.query('ROLLBACK', function(err) { 1577 | err && self.errors.push(err.message); 1578 | next(); 1579 | }); 1580 | }); 1581 | return; 1582 | } 1583 | 1584 | }, function() { 1585 | 1586 | if (Agent.debug || self.debug) { 1587 | self.time = Date.now() - self.debugtime; 1588 | console.log(self.debugname, '----- done (' + self.time + ' ms)'); 1589 | } 1590 | 1591 | self.index = 0; 1592 | self.done && self.done(); 1593 | self.done = null; 1594 | var err = null; 1595 | 1596 | if (self.isErrorBuilder) { 1597 | if (self.errors.hasError()) 1598 | err = self.errors; 1599 | } else if (self.errors.length) 1600 | err = self.errors; 1601 | 1602 | self.$events.end && self.emit('end', err, self.results, self.time); 1603 | callback && callback(err, self.returnIndex !== undefined ? self.results[self.returnIndex] : self.results); 1604 | }); 1605 | 1606 | return self; 1607 | }; 1608 | 1609 | Agent.prototype.$bindwhen = function(name) { 1610 | var self = this; 1611 | if (!self.$when) 1612 | return self; 1613 | var tmp = self.$when[name]; 1614 | if (!tmp) 1615 | return self; 1616 | for (var i = 0, length = tmp.length; i < length; i++) 1617 | tmp[i](self.errors, self.results, self.results[name]); 1618 | return self; 1619 | }; 1620 | 1621 | Agent.prototype.$bind = function(item, err, rows) { 1622 | 1623 | var self = this; 1624 | 1625 | if (err) { 1626 | item.condition && item.condition.$callback && item.condition.$callback(err); 1627 | self.errors.push(item.name + ': ' + err.message); 1628 | if (self.isTransaction) 1629 | self.isRollback = true; 1630 | self.last = item.name; 1631 | return; 1632 | } 1633 | 1634 | if (item.type === 'insert') { 1635 | self.id = rows.insertId; 1636 | if (!self.isPut) 1637 | self.$id = self.id; 1638 | self.results[item.name] = { identity: self.id }; 1639 | } else if (item.first && item.column) { 1640 | var tmp = rows instanceof Array ? rows.length ? rows[0] : null : rows; 1641 | if (tmp) 1642 | self.results[item.name] = item.column === 'sqlagentcolumn_e' ? true : item.datatype === 1 ? item.condition && item.condition._group ? rows.length : parseFloat(tmp[item.column] || 0) : tmp[item.column]; 1643 | } else if (item.first) 1644 | self.results[item.name] = rows instanceof Array ? rows[0] : rows; 1645 | else 1646 | self.results[item.name] = rows; 1647 | 1648 | if (item.listing) { 1649 | var obj = {}; 1650 | obj.count = self.results[item.listing + '_count']; 1651 | obj.items = self.results[item.listing + '_items']; 1652 | obj.page = ((item.condition._skip || 0) / (item.condition._take || 0)) + 1; 1653 | obj.limit = item.condition._take || 0; 1654 | obj.pages = Math.ceil(obj.count / obj.limit); 1655 | self.results[item.target] = obj; 1656 | self.results[item.listing + '_count'] = null; 1657 | self.results[item.listing + '_items'] = null; 1658 | item.condition && item.condition.$callback && item.condition.$callback(null, obj); 1659 | item.condition.$assignname && self.results[item.condition.$assignname] && (self.results[item.condition.$assignname][item.condition.$assignkey] = obj); 1660 | } else if (item.type === 'compare') { 1661 | 1662 | var keys = item.keys; 1663 | var val = self.results[item.name]; 1664 | var diff; 1665 | 1666 | if (val) { 1667 | diff = []; 1668 | for (var i = 0, length = keys.length; i < length; i++) { 1669 | var key = keys[i]; 1670 | var a = val[key]; 1671 | var b = item.value[key]; 1672 | a != b && diff.push(key); 1673 | } 1674 | } else 1675 | diff = keys; 1676 | 1677 | self.results[item.name] = diff.length ? { diff: diff, record: val, value: item.value } : false; 1678 | } 1679 | 1680 | !item.listing && item.condition && !item.nocallback && item.condition.$callback && item.condition.$callback(null, self.results[item.name]); 1681 | item.condition.$assignname && self.results[item.condition.$assignname] && (self.results[item.condition.$assignname][item.condition.$assignkey] = obj); 1682 | self.$events.data && self.emit('data', item.target || item.name, self.results); 1683 | self.last = item.name; 1684 | self.$bindwhen(item.name); 1685 | }; 1686 | 1687 | Agent.prototype.exec = function(callback, returnIndex) { 1688 | 1689 | var self = this; 1690 | 1691 | if (Agent.debug || self.debug) { 1692 | self.debugname = 'sqlagent/mysql (' + Math.floor(Math.random() * 1000) + ')'; 1693 | self.debugtime = Date.now(); 1694 | } 1695 | 1696 | if (returnIndex !== undefined && typeof(returnIndex) !== 'boolean') 1697 | self.returnIndex = returnIndex; 1698 | else 1699 | self.returnIndex = undefined; 1700 | 1701 | if (!self.command.length) { 1702 | callback && callback.call(self, null, {}); 1703 | return self; 1704 | } 1705 | 1706 | (Agent.debug || self.debug) && console.log(self.debugname, '----- exec'); 1707 | 1708 | if (!pools_cache[self.$conn]) { 1709 | if (typeof(self.options) === 'string') { 1710 | var options = Parser.parse(self.options); 1711 | self.options = {}; 1712 | self.options.host = options.host.split(':')[0]; 1713 | if (options.pathname && options.pathname.length > 1) 1714 | self.options.database = options.pathname.substring(1); 1715 | if (options.port) 1716 | self.options.port = options.port; 1717 | var auth = options.auth; 1718 | if (auth) { 1719 | auth = auth.split(':'); 1720 | self.options.user = decodeURIComponent(auth[0]); 1721 | self.options.password = decodeURIComponent(auth[1]); 1722 | } 1723 | console.log(self.options); 1724 | } 1725 | pools_cache[self.$conn] = database.createPool(self.options); 1726 | } 1727 | 1728 | pools_cache[self.$conn].getConnection(function(err, connection) { 1729 | 1730 | if (err) { 1731 | if (!self.errors) 1732 | self.errors = self.isErrorBuilder ? new global.ErrorBuilder() : []; 1733 | self.errors.push(err); 1734 | callback && callback.call(self, self.errors, {}); 1735 | return; 1736 | } 1737 | 1738 | self.done = () => connection.release(); 1739 | self.db = connection; 1740 | self._prepare(callback); 1741 | }); 1742 | 1743 | return self; 1744 | }; 1745 | 1746 | Agent.destroy = function() { 1747 | var keys = Object.keys(pools_cache); 1748 | for (var i = 0, length = keys.length; i < length; i++) 1749 | pools_cache[keys[i]].end(function(){}); 1750 | }; 1751 | 1752 | Agent.prototype.$$exec = function(returnIndex) { 1753 | var self = this; 1754 | return function(callback) { 1755 | return self.exec(callback, returnIndex); 1756 | }; 1757 | }; 1758 | 1759 | Agent.escape = Agent.prototype.escape = SqlBuilder.escape = SqlBuilder.prototype.escape = function(value) { 1760 | 1761 | if (value == null) 1762 | return 'null'; 1763 | 1764 | var type = typeof(value); 1765 | 1766 | if (type === 'function') { 1767 | value = value(); 1768 | 1769 | if (value == null) 1770 | return 'null'; 1771 | 1772 | type = typeof(value); 1773 | } 1774 | 1775 | if (type === 'boolean') 1776 | return value ? '1' : '0'; 1777 | 1778 | if (type === 'number') 1779 | return value.toString(); 1780 | 1781 | if (type === 'string') 1782 | return database.escape(value); 1783 | 1784 | if (value instanceof Array) 1785 | return database.escape(value.join(',')); 1786 | 1787 | if (value instanceof Date) 1788 | return database.escape(dateToString(value)); 1789 | 1790 | return database.escape(value.toString()); 1791 | }; 1792 | 1793 | function dateToString(dt) { 1794 | var arr = []; 1795 | arr.push(dt.getFullYear().toString()); 1796 | arr.push((dt.getMonth() + 1).toString()); 1797 | arr.push(dt.getDate().toString()); 1798 | arr.push(dt.getHours().toString()); 1799 | arr.push(dt.getMinutes().toString()); 1800 | arr.push(dt.getSeconds().toString()); 1801 | for (var i = 1, length = arr.length; i < length; i++) { 1802 | if (arr[i].length === 1) 1803 | arr[i] = '0' + arr[i]; 1804 | } 1805 | return arr[0] + '-' + arr[1] + '-' + arr[2] + ' ' + arr[3] + ':' + arr[4] + ':' + arr[5]; 1806 | } 1807 | 1808 | function isFIRST(query) { 1809 | return query ? query.substring(query.length - 7).toLowerCase() === 'limit 1' : false; 1810 | } 1811 | 1812 | Agent.init = function(conn, debug) { 1813 | Agent.debug = debug ? true : false; 1814 | var id = (Math.random() * 100000) >> 0; 1815 | framework.database = function(errorBuilder) { 1816 | return new Agent(conn, errorBuilder, id); 1817 | }; 1818 | EMIT('database'); 1819 | }; 1820 | 1821 | module.exports = Agent; 1822 | global.SqlBuilder = SqlBuilder; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Peter Sirka" 4 | }, 5 | "engines": { 6 | "node": ">=0.8.0" 7 | }, 8 | "contributors": [{ 9 | "name": "Peter Širka", 10 | "email": "petersirka@gmail.com" 11 | }, { 12 | "name": "Jay Kelkar", 13 | "email": "jkelkar@gmail.com" 14 | }, { 15 | "name": "Martin Smola", 16 | "email": "smola.martin@gmail.com" 17 | }, { 18 | "name": "Aidan Dunn", 19 | "email": "aidancheyd@gmail.com" 20 | }], 21 | 22 | "license": "MIT", 23 | "name": "sqlagent", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/totaljs/node-sqlagent.git" 27 | }, 28 | "version": "12.1.1" 29 | } -------------------------------------------------------------------------------- /pg-lo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A custom implementation of pg-large-object 3 | * @author Joris van der Wel 4 | */ 5 | 6 | const NOOP = function(){}; 7 | const Stream = require('stream'); 8 | const BUFFERSIZE = 16384; 9 | const WRITESTREAM = { highWaterMark: BUFFERSIZE, decodeStrings: true, objectMode: false }; 10 | const READSTREAM = { highWaterMark: BUFFERSIZE, encoding: null, objectMode: false }; 11 | 12 | var LargeObject = function(client, oid, fd) { 13 | this._client = client; 14 | this.oid = oid; 15 | this._fd = fd; 16 | }; 17 | 18 | LargeObject.SEEK_SET = 0; 19 | LargeObject.SEEK_CUR = 1; 20 | LargeObject.SEEK_END = 2; 21 | 22 | LargeObject.prototype.close = function(callback) { 23 | var self = this; 24 | self._client.query({ name: 'npg_lo_close', text: 'SELECT lo_close($1) as ok', values: [self._fd] }, callback); 25 | return self; 26 | }; 27 | 28 | LargeObject.prototype.read = function(length, callback) { 29 | var self = this; 30 | self._client.query({ name: 'npg_loread', text:'SELECT loread($1, $2) as data', values: [self._fd, length] }, function(err, response) { 31 | if (err) 32 | return callback(err); 33 | var data = response.rows[0].data; 34 | callback(null, data); 35 | }); 36 | return self; 37 | }; 38 | 39 | LargeObject.prototype.write = function(buffer, callback) { 40 | var self = this; 41 | self._client.query({ name: 'npg_lowrite', text:'SELECT lowrite($1, $2)', values: [self._fd, buffer] }, callback); 42 | return self; 43 | }; 44 | 45 | LargeObject.prototype.seek = function(position, ref, callback) { 46 | var self = this; 47 | self._client.query({ name: 'npg_lo_lseek64', text: 'SELECT lo_lseek' + self.plusql + '($1, $2, $3) as location', values: [self._fd, position, ref] }, function(err, response) { 48 | if (err) 49 | return callback(err); 50 | var location = response.rows[0].location; 51 | callback(null, location); 52 | }); 53 | return self; 54 | }; 55 | 56 | LargeObject.prototype.tell = function(callback) { 57 | var self = this; 58 | self._client.query({ name: 'npg_lo_tell64', text: 'SELECT lo_tell' + self.plusql + '($1) as location', values: [self._fd] }, function(err, response) { 59 | if (err) 60 | return callback(err); 61 | var location = response.rows[0].location; 62 | callback(null, location); 63 | }); 64 | return self; 65 | }; 66 | 67 | LargeObject.prototype.size = function(callback) { 68 | var self = this; 69 | self._client.query({ name: 'npg_size', text: 'SELECT lo_lseek' + self.plusql + '($1, location, 0), seek.size FROM (SELECT lo_lseek' + self.plusql + '($1, 0, 2) AS SIZE, tell.location FROM (SELECT lo_tell' + self.plusql + '($1) AS location) tell) seek', values: [self._fd] }, function(err, response) { 70 | if (err) 71 | return callback(err); 72 | var size = response.rows[0].size; 73 | callback(null, size); 74 | }); 75 | return self; 76 | }; 77 | 78 | LargeObject.prototype.truncate = function(length, callback) { 79 | var self = this; 80 | self._client.query({ name: 'npg_lo_truncate' + self.plusql, text:'SELECT lo_truncate' + self.plusql + '($1, $2)', values: [self._fd, length]}, callback); 81 | return self; 82 | }; 83 | 84 | LargeObject.prototype.getReadableStream = function(bufferSize) { 85 | return new ReadStream(this, bufferSize); 86 | }; 87 | 88 | LargeObject.prototype.getWritableStream = function(bufferSize) { 89 | return new WriteStream(this, bufferSize); 90 | }; 91 | 92 | var LargeObjectManager = function(client) { 93 | this._client = client; 94 | }; 95 | 96 | LargeObjectManager.WRITE = 0x00020000; 97 | LargeObjectManager.READ = 0x00040000; 98 | LargeObjectManager.READWRITE = 0x00020000 | 0x00040000; 99 | 100 | LargeObjectManager.prototype.open = function(oid, mode, callback) { 101 | 102 | if (!oid) 103 | throw 'Illegal Argument'; 104 | 105 | var self = this; 106 | self._client.query({ name: 'npg_lo_open', text:'SELECT lo_open($1, $2) AS fd, current_setting(\'server_version_num\') as version', values: [oid, mode]}, function(err, response) { 107 | if (err) 108 | return callback(err); 109 | var lo = new LargeObject(self._client, oid, response.rows[0].fd); 110 | lo.oldversion = response.rows[0].version < 90300; 111 | lo.plusql = lo.oldversion ? '' : '64'; 112 | callback(null, lo); 113 | }); 114 | 115 | return self; 116 | }; 117 | 118 | LargeObjectManager.prototype.create = function(callback) { 119 | this._client.query({ name: 'npg_lo_creat', text:'SELECT lo_creat($1) AS oid', values: [LargeObjectManager.READWRITE]}, function(err, response) { 120 | if (err) 121 | return callback(err); 122 | var oid = response.rows[0].oid; 123 | callback(null, oid); 124 | }); 125 | return this; 126 | }; 127 | 128 | LargeObjectManager.prototype.unlink = function(oid, callback) { 129 | if (!oid) 130 | throw 'Illegal Argument'; 131 | this._client.query({ name: 'npg_lo_unlink', text:'SELECT lo_unlink($1) as ok', values: [oid]}, callback); 132 | return this; 133 | }; 134 | 135 | 136 | LargeObjectManager.prototype.readStream = function(oid, bufferSize, callback) { 137 | 138 | if (typeof(bufferSize) === 'function') { 139 | callback = bufferSize; 140 | bufferSize = undefined; 141 | } 142 | 143 | var self = this; 144 | 145 | self.open(oid, LargeObjectManager.READ, function(err, obj) { 146 | 147 | if (err) 148 | return callback(err); 149 | 150 | obj.size(function(err, size) { 151 | 152 | if (err) 153 | return callback(err); 154 | 155 | if (size === '0') 156 | return callback(new Error('Stream is empty.'), size, null); 157 | 158 | var stream = obj.getReadableStream(bufferSize); 159 | 160 | stream.on('error', function() { 161 | obj.close(NOOP); 162 | }); 163 | 164 | stream.on('end', function() { 165 | obj.close(NOOP); 166 | }); 167 | 168 | callback(null, size, stream); 169 | }); 170 | }); 171 | 172 | return self; 173 | }; 174 | 175 | LargeObjectManager.prototype.writeStream = function(bufferSize, callback) { 176 | 177 | if (typeof(bufferSize) === 'function') { 178 | callback = bufferSize; 179 | bufferSize = undefined; 180 | } 181 | 182 | var self = this; 183 | self.create(function(err, oid) { 184 | if (err) 185 | return callback(err); 186 | 187 | self.open(oid, LargeObjectManager.WRITE, function(err, obj) { 188 | if (err) 189 | return callback(err); 190 | 191 | var stream = obj.getWritableStream(bufferSize); 192 | 193 | stream.on('error', function() { 194 | obj.close(NOOP); 195 | }); 196 | 197 | stream.on('finish', function() { 198 | obj.close(NOOP); 199 | }); 200 | 201 | callback(null, oid, stream); 202 | }); 203 | 204 | }); 205 | return self; 206 | }; 207 | 208 | 209 | var WriteStream = function(largeObject, bufferSize) { 210 | 211 | if (bufferSize && bufferSize !== BUFFERSIZE) 212 | WRITESTREAM.bufferSize = bufferSize; 213 | else if (WRITESTREAM.bufferSize !== BUFFERSIZE) 214 | WRITESTREAM.bufferSize = BUFFERSIZE; 215 | 216 | Stream.Writable.call(this, WRITESTREAM); 217 | this._largeObject = largeObject; 218 | }; 219 | 220 | WriteStream.prototype = Object.create(Stream.Writable.prototype); 221 | WriteStream.prototype._write = function(chunk, encoding, callback) { 222 | if (!Buffer.isBuffer(chunk)) 223 | throw 'Illegal Argument'; 224 | this._largeObject.write(chunk, callback); 225 | }; 226 | 227 | var ReadStream = function(largeObject, bufferSize) { 228 | if (bufferSize && bufferSize !== BUFFERSIZE) 229 | READSTREAM.bufferSize = bufferSize; 230 | else if (READSTREAM.bufferSize !== BUFFERSIZE) 231 | READSTREAM.bufferSize = BUFFERSIZE; 232 | Stream.Readable.call(this, READSTREAM); 233 | this._largeObject = largeObject; 234 | }; 235 | 236 | ReadStream.prototype = Object.create(Stream.Readable.prototype); 237 | ReadStream.prototype._read = function(length) { 238 | 239 | if (length <= 0) 240 | throw 'Illegal Argument'; 241 | 242 | var self = this; 243 | self._largeObject.read(length, function(error, data) { 244 | 245 | if (error) 246 | return self.emit('error', error); 247 | 248 | self.push(data); 249 | 250 | if (data.length < length) 251 | self.push(null); // the large object has no more data left 252 | }); 253 | }; 254 | 255 | exports.create = function(client) { 256 | return new LargeObjectManager(client); 257 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # A very helpful ORM for node.js 2 | 3 | [![Professional Support](https://www.totaljs.com/img/badge-support.svg)](https://www.totaljs.com/support/) [![Chat with contributors](https://www.totaljs.com/img/badge-chat.svg)](https://messenger.totaljs.com) [![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![MIT License][license-image]][license-url] 4 | 5 | - installation `$ npm install sqlagent` 6 | 7 | --- 8 | 9 | - for PostgreSQL `$ npm install pg` 10 | - for MySQL `$ npm install mysql` 11 | - for MS SQL Server `$ npm install mssql` 12 | - for MongoDB `$ npm install mongodb` 13 | 14 | --- 15 | 16 | - Currently supports __PostgreSQL__, __MySQL__, __SQL Server__ and __MongoDB__ 17 | - Simple and powerful 18 | - Best use with [Total.js - web framework for Node.js](https://www.totaljs.com) 19 | 20 | __IMPORTANT__: 21 | 22 | - the code is executed as is added 23 | - `rollback` is executed automatically when is the transaction enabled 24 | - SQL Server: pagination works only in `SQL SERVER >=2012` 25 | - `SqlBuilder` is a global object 26 | - `undefined` values are skipped 27 | 28 | ## Initialization 29 | 30 | ### Basic initialization 31 | 32 | #### PostgreSQL 33 | 34 | ```javascript 35 | // Example: postgresql://user:password@127.0.0.1/database 36 | var Agent = require('sqlagent/pg').connect('connetion-string-to-postgresql'); 37 | 38 | /* 39 | // It's executed when the datbase returns an unexpected error 40 | Agent.error = function(err, type, query) { 41 | 42 | }; 43 | */ 44 | 45 | // Agent() returns new instance of SQL Agent 46 | var sql = Agent(); 47 | ``` 48 | 49 | __Additional configuration__: 50 | 51 | ``` 52 | postgresql://user:password@127.0.0.1/database?native=true&ssl=true 53 | ``` 54 | 55 | - `native` {Boolean} enables PG C native binding (faster than JavaScript binding, default: `false`) 56 | - `ssl` {Boolean} enables SSL (default: `false`) 57 | - `max` {Number} max. pools (default: `20`) 58 | - `min` {Number} min. pools (default: `4`) 59 | - `idleTimeoutMillis` {Number} idle timeout (default: `1000`) 60 | 61 | #### MySQL 62 | 63 | ```javascript 64 | // Example: mysql://user:password@127.0.0.1/database 65 | var Agent = require('sqlagent/mysql').connect('connetion-string-to-mysql'); 66 | var sql = new Agent(); 67 | ``` 68 | 69 | #### SQL Server (MSSQL) 70 | 71 | ```javascript 72 | // Example: mssql://user:password@127.0.0.1/database 73 | // Example with name of instance: mssql://user:password@localhost_SQLEXPRESS/database 74 | var Agent = require('sqlagent/sqlserver').connect('connetion-string-to-mssql'); 75 | var sql = new Agent(); 76 | ``` 77 | 78 | #### MongoDB 79 | 80 | ```javascript 81 | // Example: mongodb://user:password@127.0.0.1/database 82 | var Agent = require('sqlagent/mongodb').connect('connetion-string-to-mongodb'); 83 | var nosql = new Agent(); 84 | ``` 85 | 86 | ### Initialization for Total.js 87 | 88 | Create a definition file: 89 | 90 | ```javascript 91 | // Below code rewrites total.js database prototype 92 | require('sqlagent/pg').init('connetion-string-to-postgresql', [debug]); // debug is by default: false 93 | require('sqlagent/mysql').init('connetion-string-to-mysql', [debug]); // debug is by default: false 94 | require('sqlagent/sqlserver').init('connetion-string-to-sqlserver', [debug]); // debug is by default: false 95 | require('sqlagent/mongodb').init('connetion-string-to-mongodb', [debug]); // debug is by default: false 96 | ``` 97 | 98 | Usage: 99 | 100 | ```javascript 101 | // When you use RDMBS: 102 | // var sql = DATABASE([ErrorBuilder]); 103 | var sql = DATABASE(); 104 | // sql === SqlAgent 105 | 106 | // +v9.9.6 enable debugging 107 | sql.debug = true; 108 | 109 | // When you use MongoDB: 110 | // var nosql = DATABASE([ErrorBuilder]); 111 | var nosql = DATABASE(); 112 | // nosql === SqlAgent 113 | ``` 114 | 115 | ### IMPORTANT 116 | 117 | In order for mysql to return Boolean values please set the data type in db to BIT(1) and use bellow code for initialization. 118 | ```javascript 119 | var Agent = require('sqlagent/mysql').connect({ 120 | host: "localhost", 121 | user: "root", 122 | password: "", 123 | database: "test", 124 | typeCast: function castField( field, useDefaultTypeCasting ) { 125 | if ( ( field.type === "BIT" ) && ( field.length === 1 ) ) { 126 | var bytes = field.buffer(); 127 | return( bytes[ 0 ] === 1 ); 128 | } 129 | return( useDefaultTypeCasting() ); 130 | } 131 | }); 132 | var sql = new Agent(); 133 | ``` 134 | 135 | ## Usage 136 | 137 | ### Select 138 | 139 | ```plain 140 | instance.select([name], table) 141 | ``` 142 | 143 | - `name` (String) is an identificator for results, optional (default: internal indexer) 144 | - `table` (String) table name, the library automatically creates SQL query 145 | - __returns__ SqlBuilder 146 | 147 | ```javascript 148 | sql.select('users', 'tbl_user').make(function(builder) { 149 | builder.where('id', '>', 5); 150 | builder.page(10, 10); 151 | }); 152 | 153 | sql.select('orders', 'tbl_order').make(function(builder) { 154 | builder.where('isremoved', false); 155 | builder.page(10, 10); 156 | builder.fields('amount', 'datecreated'); 157 | }); 158 | 159 | sql.select('products', 'tbl_products').make(function(builder) { 160 | builder.between('price', 30, 50); 161 | builder.and(); 162 | builder.where('isremoved', false); 163 | builder.limit(20); 164 | builder.fields('id', 'name'); 165 | }); 166 | 167 | sql.exec(function(err, response) { 168 | console.log(response.users); 169 | console.log(response.products); 170 | console.log(response.admin); 171 | }); 172 | ``` 173 | 174 | ### Push (only for MongoDB) 175 | 176 | ```plain 177 | instance.push([name], collection, fn(collection, callback(err, response)) 178 | ``` 179 | 180 | ```javascript 181 | sql.push('users', 'users', function(collection, callback) { 182 | 183 | var $group = {}; 184 | $group._id = {}; 185 | $group._id = '$category'; 186 | $group.count = { $sum: 1 }; 187 | 188 | var $match = {}; 189 | $match.isremoved = false; 190 | 191 | var pipeline = []; 192 | pipeline.push({ $match: $match }); 193 | pipeline.push({ $group: $group }); 194 | 195 | collection.aggregate(pipeline, callback); 196 | }); 197 | 198 | // OR 199 | 200 | sql.push('users', 'users', function(collection, callback) { 201 | collection.findOne({ name: 'Peter' }, { name: 1, age: 1 }).toArray(callback); 202 | }); 203 | ``` 204 | 205 | ### Listing 206 | 207 | ```plain 208 | instance.listing([name], table) 209 | ``` 210 | 211 | - `name` (String) is an identificator for results, optional (default: internal indexer) 212 | - `table` (String) table name, the library automatically creates SQL query 213 | - __returns__ SqlBuilder 214 | 215 | ```javascript 216 | sql.listing('users', 'tbl_user').make(function(builder) { 217 | builder.where('id', '>', 5); 218 | builder.page(10, 10); 219 | }); 220 | 221 | sql.exec(function(err, response) { 222 | 223 | // users will contain: 224 | // .count --> count of all users according to the filter 225 | // .items --> selected items 226 | // .page --> a page number (+v11.0.0) 227 | // .pages --> page count (+v11.0.0) 228 | // .limit --> items limit per page (+v11.0.0) 229 | 230 | console.log(response.users.count); 231 | console.log(response.users.items); 232 | }); 233 | ``` 234 | 235 | 236 | ### Save 237 | 238 | ```plain 239 | instance.save([name], table, isINSERT, prepare(builder, isINSERT)); 240 | ``` 241 | 242 | ```javascript 243 | sql.save('user', 'tbl_user', somemodel.id === 0, function(builder, isINSERT) { 244 | 245 | builder.set('name', somemodel.name); 246 | 247 | if (isINSERT) { 248 | builder.set('datecreated', new Date()); 249 | return; 250 | } 251 | 252 | builder.inc('countupdate', 1); 253 | builder.where('id', somemodel.id); 254 | }); 255 | ``` 256 | 257 | ### Insert 258 | 259 | ```plain 260 | instance.insert([name], table) 261 | ``` 262 | 263 | - `name` (String) is an identificator for results, optional (default: internal indexer) 264 | - `table` (String) table name, the library automatically creates SQL query 265 | - __returns__ if value is undefined then __SqlBuilder__ otherwise __SqlAgent__ 266 | 267 | ```javascript 268 | sql.insert('user', 'tbl_user').make(function(builder) { 269 | builder.set({ name: 'Peter', age: 30 }); 270 | }); 271 | 272 | sql.insert('log', 'tbl_logs').make(function(builder) { 273 | builder.set('message', 'Some log message.'); 274 | builder.set('created', new Date()); 275 | }); 276 | 277 | sql.exec(function(err, response) { 278 | console.log(response.user); // response.user.identity (INSERTED IDENTITY) 279 | console.log(response.log); // response.log.identity (INSERTED IDENTITY) 280 | }); 281 | ``` 282 | 283 | __IMPORTANT__: `identity` works only with auto-increment in MS SQL SERVER. 284 | 285 | ### Update 286 | 287 | ```plain 288 | instance.update([name], table) 289 | ``` 290 | 291 | - `name` (String) is an identificator for results, optional (default: internal indexer) 292 | - `table` (String) table name, the library automatically creates SQL query 293 | - __returns__ if value is undefined then __SqlBuilder__ otherwise __SqlAgent__ 294 | 295 | ```javascript 296 | sql.update('user1', 'tbl_user').make(function(builder) { 297 | builder.set({ name: 'Peter', age: 30 }); 298 | builder.where('id', 1); 299 | }); 300 | 301 | // is same as 302 | sql.update('user2', 'tbl_user').make(function(builder) { 303 | builder.where('id', 1); 304 | builder.set('name', 'Peter'); 305 | builder.set('age', 30); 306 | }); 307 | 308 | sql.exec(function(err, response) { 309 | console.log(response.user1); // returns {Number} (count of changed rows) 310 | console.log(response.user2); // returns {Number} (count of changed rows) 311 | }); 312 | ``` 313 | 314 | ### Delete 315 | 316 | ```plain 317 | instance.delete([name], table) 318 | instance.remove([name], table) 319 | ``` 320 | 321 | - `name` (String) is an identificator for results, optional (default: internal indexer) 322 | - `table` (String) table name, the library automatically creates SQL query 323 | - __returns__ SqlBuilder 324 | 325 | ```javascript 326 | sql.remove('user', 'tbl_user').make(function(builder) { 327 | builder.where('id', 1); 328 | }); 329 | 330 | sql.exec(function(err, response) { 331 | console.log(response.user); // returns {Number} (count of deleted rows) 332 | }); 333 | ``` 334 | 335 | ### Query 336 | 337 | ```plain 338 | instance.query([name], query) 339 | ``` 340 | 341 | - `name` (String) is an identificator for results, optional (default: internal indexer) 342 | - `query` (String) SQL query 343 | - `params` (Array) SQL additional params (each DB has own SQL implementation e.g. PG `WHERE id=$1`, MySQL `WHERE id=?`, etc.) 344 | - __returns__ if params is undefined then __SqlBuilder__ otherwise __SqlAgent__ 345 | 346 | ```javascript 347 | sql.query('user', 'SELECT * FROM tbl_user').make(function(builder) { 348 | builder.where('id', 1); 349 | }); 350 | 351 | sql.exec(function(err, response) { 352 | console.log(response.user); 353 | }); 354 | ``` 355 | 356 | ### Aggregation 357 | 358 | ```plain 359 | instance.count([name], table) 360 | ``` 361 | 362 | - __returns__ SqlBuilder 363 | 364 | ```javascript 365 | var count = sql.count('users', 'tbl_user'); 366 | count.between('age', 20, 40); 367 | 368 | sql.exec(function(err, response) { 369 | console.log(response.users); // response.users === number 370 | }); 371 | ``` 372 | 373 | --- 374 | 375 | ```plain 376 | instance.max([name], table, column) 377 | instance.min([name], table, column) 378 | instance.avg([name], table, column) 379 | ``` 380 | 381 | - __returns__ SqlBuilder 382 | 383 | ```javascript 384 | var max = sql.max('users', 'tbl_user', 'age'); 385 | max.where('isremoved', false); 386 | 387 | sql.exec(function(err, response) { 388 | console.log(response.users); // response.users === number 389 | }); 390 | ``` 391 | 392 | ### Exists 393 | 394 | ```plain 395 | instance.exists([name], table) 396 | ``` 397 | 398 | - __returns__ SqlBuilder 399 | 400 | ```javascript 401 | var exists = sql.exists('user', 'tbl_user'); 402 | exists.where('id', 35); 403 | 404 | sql.exec(function(err, response) { 405 | console.log(response.user); // response.user === Boolean (in correct case otherwise undefined) 406 | }); 407 | ``` 408 | 409 | ### Compare 410 | 411 | ```plain 412 | instance.compare([name], table, value, [keys]) 413 | ``` 414 | 415 | - the module compares values between DB and `value` 416 | - the response can be `false` or `{ diff: ['name'], record: Object, value: Object }` 417 | - works with `sql.ifexists()` and `sql.ifnot()` 418 | - __returns__ SqlBuilder 419 | 420 | ```javascript 421 | var compare = sql.compare('user', 'tbl_user', { name: 'Peter', age: 33 }); 422 | // OR: var compare = sql.compare('user', 'tbl_user', { name: 'Peter', age: 33 }, ['name']); --> compares only name field 423 | // OR: compare.fields('name', 'age'); --> compares these fields (if aren't defined "keys") 424 | 425 | compare.where('id', 35); 426 | 427 | sql.exec(function(err, response) { 428 | 429 | if (response.user) { 430 | // shows the property names which were changed 431 | console.log(response.user.diff); 432 | } 433 | 434 | }); 435 | ``` 436 | 437 | --- 438 | 439 | ```plain 440 | instance.max([name], table, column) 441 | instance.min([name], table, column) 442 | instance.avg([name], table, column) // doesn't work with Mongo 443 | ``` 444 | 445 | - __returns__ SqlBuilder 446 | 447 | ```javascript 448 | var max = sql.max('users', 'tbl_user', 'age'); 449 | max.where('isremoved', false); 450 | 451 | sql.exec(function(err, response) { 452 | console.log(response.users); // response.users === number 453 | }); 454 | ``` 455 | 456 | ### Transactions 457 | 458 | - doesn't work with MongoDB 459 | - rollback is performed automatically 460 | 461 | ```javascript 462 | sql.begin(); 463 | sql.insert('tbl_user', { name: 'Peter' }); 464 | sql.commit(); 465 | ``` 466 | 467 | ## Special cases 468 | 469 | ### How to set the primary key? 470 | 471 | - doesn't work with MongoDB 472 | 473 | ```javascript 474 | // instance.primary('column name') is same as instance.primaryKey('column name') 475 | 476 | instance.primary('userid'); 477 | instance.insert('tbl_user', ...); 478 | 479 | instance.primary('productid'); 480 | instance.insert('tbl_product', ...); 481 | 482 | instance.primary(); // back to default "id" 483 | ``` 484 | 485 | - default `primary key name` is `id` 486 | - works only in PostgreSQL because INSERT ... RETURNING __must have specific column name__ 487 | 488 | ### How to use latest primary id value for relations? 489 | 490 | ```javascript 491 | // primary key is id + autoincrement 492 | var user = sql.insert('user', 'tbl_user'); 493 | user.set('name', 'Peter'); 494 | 495 | var address = sql.insert('tbl_user_address'); 496 | address.set('id', sql.$$); 497 | address.set('country', 'Slovakia'); 498 | 499 | sql.exec(); 500 | ``` 501 | 502 | ### How to use latest primary id value for multiple relations? 503 | 504 | ```javascript 505 | // primary key is id + autoincrement 506 | var user = sql.insert('user', 'tbl_user'); 507 | user.set('name', 'Peter'); 508 | 509 | // Lock latest inserted identificator 510 | sql.lock(); 511 | // is same as 512 | // sql.put(sql.$$); 513 | 514 | var address = sql.insert('tbl_user_address'); 515 | address.set('iduser', sql.$$); // adds latest primary id value 516 | address.set('country', 'Slovakia'); 517 | 518 | var email = sql.insert('tbl_user_email'); 519 | email.set('iduser', sql.$$); // adds locked value 520 | email.set('email', 'petersirka@gmail.com'); 521 | sql.unlock(); 522 | 523 | sql.exec(); 524 | ``` 525 | 526 | ### If not or If exists 527 | 528 | ```javascript 529 | instance.ifnot('user', function(error, response, value) { 530 | // error === ErrorBuilder 531 | // It will be executed when the results `user` contains a negative value or array.length === 0 532 | // Is executed in order 533 | }); 534 | 535 | instance.ifexists('user', function(error, response, value) { 536 | // error === ErrorBuilder 537 | // It will be executed when the results `user` contains a positive value or array.length > 0 538 | // Is executed in order 539 | }); 540 | ``` 541 | 542 | ### Default values 543 | 544 | - you can set default values 545 | - values are bonded immediately (not in order) 546 | 547 | ```javascript 548 | sql.default(function(response) { 549 | response.count = 0; 550 | response.user = {}; 551 | response.user.id = 1; 552 | }); 553 | 554 | // ... 555 | // ... 556 | 557 | sql.exec(function(err, response) { 558 | console.log(response); 559 | }); 560 | ``` 561 | 562 | ### Modify results 563 | 564 | - values are bonded in an order 565 | 566 | ```javascript 567 | sql.select(...); 568 | sql.insert(...); 569 | 570 | sql.modify(function(response) { 571 | response.user = {}; 572 | response.user.identity = 10; 573 | }); 574 | 575 | // ... 576 | // ... 577 | 578 | // Calling: 579 | // 1. select 580 | // 2. insert 581 | // 3. modify 582 | // 4. other commands 583 | sql.exec(function(err, response) { 584 | console.log(response); 585 | }); 586 | ``` 587 | 588 | ### Preparing (dependencies) 589 | 590 | - you can use multiple `sql.prepare()` 591 | 592 | ```javascript 593 | var user = sql.update('user', 'tbl_user'); 594 | user.where('id', 20); 595 | user.set('name', 'Peter'); 596 | 597 | var select = sql.select('address', 'tbl_address'); 598 | select.where('isremoved', false); 599 | select.and(); 600 | select.where('city', 'Bratislava'); 601 | select.limit(1); 602 | 603 | // IMPORTANT: 604 | sql.prepare(function(error, response, resume) { 605 | // error === ErrorBuilder 606 | sql.builder('address').set('idaddress', response.address.id); 607 | resume(); 608 | }); 609 | 610 | var address = sql.update('address', 'tbl_user_address'); 611 | address.where('iduser', 20); 612 | 613 | sql.exec(); 614 | ``` 615 | 616 | ### Validation 617 | 618 | - you can use multiple `sql.validate()` 619 | 620 | ```plain 621 | sql.validate(fn) 622 | ``` 623 | 624 | ```javascript 625 | var select = sql.select('address', 'tbl_address'); 626 | select.where('isremoved', false); 627 | select.and(); 628 | select.where('city', 'Bratislava'); 629 | select.limit(1); 630 | 631 | // IMPORTANT: 632 | sql.validate(function(error, response, resume) { 633 | 634 | // error === ErrorBuilder 635 | 636 | if (!response.address) { 637 | error.push('Sorry, address not found'); 638 | // cancel pending queries 639 | return resume(false); 640 | } 641 | 642 | sql.builder('user').set('idaddress', response.id); 643 | 644 | // continue 645 | resume(); 646 | }); 647 | 648 | var user = sql.update('user', 'tbl_user'); 649 | user.where('id', 20); 650 | user.set('name', 'Peter'); 651 | 652 | sql.exec(); 653 | ``` 654 | 655 | __Validation alternative (+v4.0.0)__ 656 | 657 | ```javascript 658 | // IMPORTANT: 659 | sql.validate(function(error, response) { 660 | 661 | // error === ErrorBuilder 662 | 663 | if (!response.address) { 664 | error.push('Sorry, address not found'); 665 | return false; 666 | } 667 | 668 | sql.builder('user').set('idaddress', response.id); 669 | return true; 670 | }); 671 | ``` 672 | 673 | --- 674 | 675 | ```plain 676 | sql.validate([result_name_for_validation], error_message, [reverse]); 677 | ``` 678 | 679 | - `result_name_for_validation` (String) a result to compare. 680 | - `error_message` (String) an error message 681 | - `reverse` (Boolean) a reverse comparison (false: result must exist (default), true: result must be empty) 682 | __ 683 | 684 | If the function throw error then SqlAgent cancel all pending queris (perform Rollback if the agent is in transaction mode) and executes callback with error. 685 | 686 | ```javascript 687 | var select = sql.select('address', 'tbl_address'); 688 | select.where('isremoved', false); 689 | select.and(); 690 | select.where('city', 'Bratislava'); 691 | select.limit(1); 692 | 693 | // IMPORTANT: 694 | sql.validate('Sorry, address not found'); 695 | 696 | var user = sql.select('user', 'tbl_user'); 697 | user.where('id', 20); 698 | 699 | sql.validate('Sorry, user not found'); 700 | sql.validate('Sorry, address not found for the current user', 'address'); 701 | 702 | sql.exec(); 703 | ``` 704 | 705 | __Validation alternative (+v8.0.0)__ 706 | 707 | ```javascript 708 | sql.validate('products', n => n.length > 0, 'error-products'); 709 | sql.validate('detail', n => !n, 'error-detail'); 710 | ``` 711 | 712 | 713 | ## Global 714 | 715 | ### Stored procedures 716 | 717 | ```javascript 718 | sql.query('myresult', 'exec myprocedure'); 719 | 720 | // with params 721 | // sql.query('myresult', 'exec myprocedure $1', [3403]); 722 | 723 | sql.exec(function(err, response) { 724 | console.log(response.myresult); 725 | }); 726 | ``` 727 | 728 | ### Skipper 729 | 730 | ```javascript 731 | sql.select('users', 'tbl_users'); 732 | sql.skip(); // skip orders 733 | sql.select('orders', 'tbl_orders'); 734 | 735 | sql.bookmark(function(error, response) { 736 | // error === ErrorBuilder 737 | // skip logs 738 | sql.skip('logs'); 739 | }); 740 | 741 | sql.select('logs', 'tbl_logs'); 742 | 743 | sql.exec(function(err, response) { 744 | console.log(response); // --- response will be contain only { users: [] } 745 | }); 746 | ``` 747 | 748 | ### Bookmarks 749 | 750 | Bookmark is same as `sql.prepare()` function but without `resume` argument. 751 | 752 | ```javascript 753 | sql.select('users', 'tbl_users'); 754 | 755 | sql.bookmark(function(error, response) { 756 | // error === ErrorBuilder 757 | console.log(response); 758 | response['custom'] = 'Peter'; 759 | }); 760 | 761 | sql.select('orders', 'tbl_orders'); 762 | 763 | sql.exec(function(err, response) { 764 | response.users; 765 | response.orders; 766 | response.custom; // === Peter 767 | }); 768 | ``` 769 | 770 | ### Error handling 771 | 772 | ```javascript 773 | sql.select('users', 'tbl_users'); 774 | 775 | sql.validate(function(error, response, resume) { 776 | 777 | // error === ErrorBuilder 778 | 779 | if (!response.users || respone.users.length === 0) 780 | error.push(new Error('This is error')); 781 | 782 | // total.js: 783 | // error.push('error-users-empty'); 784 | 785 | resume(); 786 | }); 787 | 788 | sql.select('orders', 'tbl_orders'); 789 | 790 | // sql.validate([error message], [result name for validation]) 791 | sql.validate('error-orders-empty'); 792 | // is same as: 793 | // sql.validate('error-orders-empty', 'orders'); 794 | 795 | sql.validate('error-users-empty', 'users'); 796 | ``` 797 | 798 | ### Escaping values 799 | 800 | - doesn't work with MongoDB 801 | 802 | ```javascript 803 | var escaped1 = Agent.escape(value); 804 | 805 | // or ... 806 | 807 | var sql = new Agent(); 808 | var escaped2 = sql.escape(value); 809 | ``` 810 | 811 | ### Predefined queries 812 | 813 | - doesn't work with MongoDB 814 | 815 | ```plain 816 | Agent.query(name, query); 817 | ``` 818 | 819 | ```javascript 820 | Agent.query('users', 'SELECT * FROM tbl_users'); 821 | Agent.query('allorders', 'SELECT * FROM view_orders'); 822 | 823 | sql.query('users').where('id', '>', 20); 824 | sql.query('orders', 'allorders').limit(5); 825 | 826 | sql.exec(function(err, response) { 827 | console.log(response[0]); // users 828 | console.log(response.orders); // orders 829 | }); 830 | ``` 831 | 832 | ### Waiting for specified values 833 | 834 | - `+3.1.0` 835 | 836 | ```javascript 837 | sql.when('users', function(error, response, value) { 838 | console.log(value); 839 | }); 840 | 841 | sql.when('orders', function(error, response, value) { 842 | console.log(value); 843 | }); 844 | 845 | sql.select('users', 'tbl_users'); 846 | sql.select('orders', 'tbl_orders'); 847 | sql.exec(); 848 | ``` 849 | 850 | ## Bonus 851 | 852 | ### How to get latest inserted ID? 853 | 854 | - doesn't work with MongoDB 855 | 856 | ```javascript 857 | sql.insert('user', 'tbl_user').set('name', 'Peter'); 858 | 859 | sql.bookmark(function() { 860 | console.log(sql.id); 861 | }); 862 | 863 | sql.exec(); 864 | ``` 865 | 866 | ### Expected values? No problem 867 | 868 | - __MongoDB__ supports expected values only in conditions. 869 | 870 | ```plain 871 | sql.expected(name, index, property); // gets a specific value from the array 872 | sql.expected(name, property); 873 | ``` 874 | 875 | ```javascript 876 | sql.select('user', 'tbl_user').where('id', 1).first(); 877 | sql.select('products', 'tbl_product').where('iduser', sql.expected('user', 'id')); 878 | 879 | sql.exec(); 880 | ``` 881 | 882 | ### Measuring time 883 | 884 | ```javascript 885 | sql.exec(function(err, response) { 886 | console.log(sql.time + ' ms'); 887 | // or 888 | // console.log(this.time) 889 | }); 890 | ``` 891 | 892 | ### Events 893 | 894 | ```javascript 895 | sql.on('query', function(name, query, params){}); 896 | sql.on('data', function(name, response){}); 897 | sql.on('end', function(err, response, time){}); 898 | ``` 899 | 900 | ### Generators in total.js 901 | 902 | ```javascript 903 | function *some_action() { 904 | var sql = DB(); 905 | 906 | sql.select('users', 'tbl_user').make(function(select) { 907 | select.where('id', '>', 100); 908 | select.and(); 909 | select.where('id', '<', 1000); 910 | select.limit(10); 911 | }); 912 | 913 | sql.select('products', 'tbl_product').make(function(select) { 914 | select.where('price', '<>', 10); 915 | select.limit(10); 916 | }); 917 | 918 | // get all results 919 | var results = yield sync(sql.$$exec())(); 920 | console.log(results); 921 | 922 | // or get a specific result: 923 | var result = yield sync(sql.$$exec('users'))(); 924 | console.log(result); 925 | } 926 | ``` 927 | 928 | ### Priority 929 | 930 | Set a command priority, so the command will be processed next round. 931 | 932 | ```javascript 933 | sql.select('... processed as second') 934 | sql.select('... processed as first'); 935 | sql.priority(); // --> takes last item in queue and inserts it as first (sorts it immediately). 936 | ``` 937 | 938 | 939 | ### Debug mode 940 | 941 | Debug mode writes each query to console. 942 | 943 | ```javascript 944 | sql.debug = true; 945 | ``` 946 | 947 | ### We need to return into the callback only one value from the response object 948 | 949 | ```javascript 950 | sql.exec(callback, 0); // --> returns first value from response (if isn't error) 951 | sql.exec(callback, 'users'); // --> returns response.users (if is isn't error) 952 | 953 | sql.exec(function(err, response) { 954 | if (err) 955 | throw err; 956 | console.log(response); // response will contain only orders 957 | }, 'orders'); 958 | ``` 959 | 960 | ## SqlBuilder 961 | 962 | - automatically adds `and` if is not added between e.g. 2x where 963 | 964 | ```javascript 965 | // Creates SqlBuilder 966 | var builder = sql.$; 967 | 968 | builder.where('id', '<>', 20); 969 | builder.set('isconfirmed', true); 970 | 971 | // e.g.: 972 | sql.update('users', 'tbl_users', builder); 973 | sql.exec(function(err, response) { 974 | console.log(response.users); 975 | }) 976 | ``` 977 | 978 | --- 979 | 980 | #### builder.callback(fn) 981 | 982 | ```plain 983 | builder.callback(function(err, response) { 984 | 985 | }); 986 | ``` 987 | 988 | `+v11.0.0` returns a value from DB 989 | 990 | 991 | --- 992 | 993 | #### builder.set() 994 | 995 | ```plain 996 | builder.set(name, value) 997 | ``` 998 | 999 | adds a value for update or insert 1000 | 1001 | - `name` (String) column name 1002 | - `value` (Object) value 1003 | 1004 | --- 1005 | 1006 | #### builder.raw() 1007 | 1008 | ```plain 1009 | builder.raw(name, value) 1010 | ``` 1011 | 1012 | adds a raw value for update or insert without SQL encoding 1013 | 1014 | - `name` (String) column name 1015 | - `value` (Object) value 1016 | 1017 | --- 1018 | 1019 | ```plain 1020 | builder.set(obj) 1021 | ``` 1022 | adds an object for update or insert value collection 1023 | 1024 | ```javascript 1025 | builder.set({ name: 'Peter', age: 30 }); 1026 | // is same as 1027 | // builder.set('name', 'Peter'); 1028 | // builder.set('age', 30); 1029 | ``` 1030 | 1031 | --- 1032 | 1033 | #### builder.inc() 1034 | 1035 | ```plain 1036 | builder.set(name, [type], value) 1037 | ``` 1038 | 1039 | adds a value for update or insert 1040 | 1041 | - `name` (String) column name 1042 | - `type` (String) increment type (`+` (default), `-`, `*`, `/`) 1043 | - `value` (Number) value 1044 | 1045 | ```javascript 1046 | builder.inc('countupdate', 1); 1047 | builder.inc('countview', '+', 1); 1048 | builder.inc('credits', '-', 1); 1049 | 1050 | // Short write 1051 | builder.inc('countupdate', '+1'); 1052 | builder.inc('credits', '-1'); 1053 | ``` 1054 | 1055 | --- 1056 | 1057 | #### builder.rem() 1058 | 1059 | ```plain 1060 | builder.rem(name) 1061 | ``` 1062 | removes an value for inserting or updating. 1063 | 1064 | ```javascript 1065 | builder.set('name', 'Peter'); 1066 | builder.rem('name'); 1067 | ``` 1068 | 1069 | --- 1070 | 1071 | #### builder.sort() 1072 | 1073 | ```plain 1074 | builder.sort(name, [desc]) 1075 | builder.order(name, [desc]) 1076 | ``` 1077 | adds sorting 1078 | 1079 | - `name` (String) column name 1080 | - `desc` (Boolean), default: false 1081 | 1082 | #### builder.random() 1083 | 1084 | ```plain 1085 | builder.random() 1086 | ``` 1087 | 1088 | Reads random rows. __IMPORTANT__: MongoDB doesn't support this feature. 1089 | 1090 | --- 1091 | 1092 | #### builder.skip() 1093 | 1094 | ```plain 1095 | builder.skip(value) 1096 | ``` 1097 | skips records 1098 | 1099 | - `value` (Number or String), string is automatically converted into number 1100 | 1101 | --- 1102 | 1103 | #### builder.take() 1104 | 1105 | ```plain 1106 | builder.take(value) 1107 | builder.limit(value) 1108 | ``` 1109 | takes records 1110 | 1111 | - `value` (Number or String), string is automatically converted into number 1112 | 1113 | --- 1114 | 1115 | #### builder.page() 1116 | 1117 | ```plain 1118 | builder.page(page, maxItemsPerPage) 1119 | ``` 1120 | sets automatically sql.skip() and sql.take() 1121 | 1122 | - `page` (Number or String), string is automatically converted into number 1123 | - `maxItemsPerPage` (Number or String), string is automatically converted into number 1124 | 1125 | --- 1126 | 1127 | #### builder.first() 1128 | 1129 | ```plain 1130 | builder.first() 1131 | ``` 1132 | sets sql.take(1) 1133 | 1134 | --- 1135 | 1136 | #### builder.join() 1137 | 1138 | - doesn't work with MongoDB 1139 | 1140 | ```plain 1141 | builder.join(name, on, [type]) 1142 | ``` 1143 | 1144 | adds a value for update or insert 1145 | 1146 | - `name` (String) table name 1147 | - `on` (String) condition 1148 | - `type` (String) optional, inner type `inner`, `left` (default), `right` 1149 | 1150 | ```javascript 1151 | builder.join('address', 'address.id=user.idaddress'); 1152 | ``` 1153 | 1154 | --- 1155 | 1156 | #### builder.where() 1157 | 1158 | ```plain 1159 | builder.where(name, [operator], value) 1160 | builder.push(name, [operator], value) 1161 | ``` 1162 | add a condition after SQL WHERE 1163 | 1164 | - `name` (String) column name 1165 | - `operator` (String), optional `>`, `<`, `<>`, `=` (default) 1166 | - `value` (Object) 1167 | 1168 | --- 1169 | 1170 | #### builder.group() 1171 | 1172 | - doesn't work with MongoDB 1173 | 1174 | ```plain 1175 | builder.group(name) 1176 | builder.group(name1, name2, name3); // +v2.9.1 1177 | ``` 1178 | creates a group by in SQL query 1179 | 1180 | - `name` (String or String Array) 1181 | 1182 | --- 1183 | 1184 | #### builder.having() 1185 | 1186 | - doesn't work with MongoDB 1187 | 1188 | ```plain 1189 | builder.having(condition) 1190 | ``` 1191 | adds having in SQL query 1192 | 1193 | - `condition` (String), e.g. `MAX(Id)>0` 1194 | 1195 | --- 1196 | 1197 | #### builder.and() 1198 | 1199 | ```plain 1200 | builder.and() 1201 | ``` 1202 | adds AND to SQL query. __IMPORTANT__: In MongoDB has to be this operator used before all queries. 1203 | 1204 | --- 1205 | 1206 | #### builder.or() 1207 | 1208 | ```plain 1209 | builder.or() 1210 | ``` 1211 | adds OR to SQL query. __IMPORTANT__: In MongoDB has to be this operator used before all queries. 1212 | 1213 | --- 1214 | 1215 | #### builder.in() 1216 | 1217 | ```plain 1218 | builder.in(name, value) 1219 | ``` 1220 | adds IN to SQL query 1221 | 1222 | - `name` (String), column name 1223 | - `value` (String, Number or String Array, Number Array) 1224 | 1225 | --- 1226 | 1227 | #### builder.between() 1228 | 1229 | ```plain 1230 | builder.between(name, a, b) 1231 | ``` 1232 | adds between to SQL query 1233 | 1234 | - `name` (String), column name 1235 | - `a` (Number) 1236 | - `b` (Number) 1237 | 1238 | --- 1239 | 1240 | #### builder.overlaps() 1241 | 1242 | ```plain 1243 | builder.overlaps(valueA, valueB, columnA, columnB) 1244 | ``` 1245 | - __only for PostgreSQL__ 1246 | 1247 | adds overlaps to SQL query 1248 | 1249 | - `valueA` (String, Number, Date) 1250 | - `valueB` (String, Number, Date) 1251 | - `columnA` (String), column A name 1252 | - `columnB` (String), column B name 1253 | 1254 | --- 1255 | 1256 | #### builder.like() 1257 | 1258 | ```plain 1259 | builder.like(name, value, [where]) 1260 | ``` 1261 | adds like command 1262 | 1263 | - `name` (String) column name 1264 | - `value` (String) value to search 1265 | - `where` (String) optional, e.g. `beg`, `end`, `*` ==> %search (beg), search% (end), %search% (*) 1266 | 1267 | --- 1268 | 1269 | #### builder.sql() 1270 | 1271 | - doesn't work with MongoDB 1272 | 1273 | ```plain 1274 | builder.sql(query, [param1], [param2], [param..n]) 1275 | ``` 1276 | adds a custom SQL to SQL query 1277 | 1278 | - `query` (String) 1279 | 1280 | ```javascript 1281 | builder.sql('age=? AND name=?', 20, 'Peter'); 1282 | ``` 1283 | 1284 | #### builder.query() 1285 | 1286 | - works with MongoDB 1287 | 1288 | ```plain 1289 | builder.query(fieldname, filter) 1290 | ``` 1291 | adds a custom QUERY to filter. 1292 | 1293 | ```javascript 1294 | builder.query('tags', { $size: 0 }); 1295 | ``` 1296 | 1297 | --- 1298 | 1299 | #### builder.scope() 1300 | 1301 | ```plain 1302 | builder.scope(fn); 1303 | ``` 1304 | adds a scope `()` 1305 | 1306 | ```javascript 1307 | builder.where('user', 'person'); 1308 | builder.and(); 1309 | 1310 | // RDMBS: 1311 | builder.scope(function() { 1312 | builder.where('type', 20); 1313 | builder.or(); 1314 | builder.where('age', '<', 20); 1315 | }); 1316 | 1317 | // MongoDB: 1318 | builder.scope(function() { 1319 | builder.or(); 1320 | builder.where('type', 20); 1321 | builder.where('age', '<', 20); 1322 | }); 1323 | 1324 | // creates: user='person' AND (type=20 OR age<20) 1325 | ``` 1326 | 1327 | #### builder.define() 1328 | 1329 | ```plain 1330 | builder.define(name, SQL_TYPE_LOWERCASE); 1331 | ``` 1332 | - __only for SQL SERVER__ 1333 | - change the param type 1334 | 1335 | ```javascript 1336 | var insert = sql.insert('user', 'tbl_user'); 1337 | 1338 | insert.set('name', 'Peter Širka'); 1339 | insert.define('name', 'varchar'); 1340 | insert.set('credit', 340.34); 1341 | insert.define('credit', 'money'); 1342 | sql.exec(); 1343 | ``` 1344 | 1345 | --- 1346 | 1347 | #### builder.schema() 1348 | 1349 | - doesn't work with MongoDB 1350 | 1351 | ```plain 1352 | builder.schema() 1353 | ``` 1354 | sets current schema for `where`, `in`, `between`, `field`, `fields`, `like` 1355 | 1356 | ```javascript 1357 | builder.schema('b'); 1358 | builder.fields('name', 'age'); // --> b."name", b."age" 1359 | builder.schema('a'); 1360 | builder.fields('name', 'age'); // --> a."name", a."age" 1361 | builder.fields('!COUNT(id) as count') // --> a.COUNT() 1362 | ``` 1363 | 1364 | #### builder.escape() 1365 | 1366 | - doesn't work with MongoDB 1367 | 1368 | ```plain 1369 | builder.escape(string) 1370 | ``` 1371 | escapes value as prevention for SQL injection 1372 | 1373 | #### builder.fields() 1374 | 1375 | ```plain 1376 | builder.fields() 1377 | ``` 1378 | sets fields for data selecting. 1379 | 1380 | ```javascript 1381 | builder.fields('name', 'age'); // "name", "age" 1382 | builder.fields('!COUNT(id)'); // Raw field: COUNT(id) 1383 | builder.fields('!COUNT(id) --> number'); // Raw field with casting: COUNT(id)::int (in PG), CAST(COUNT(id) as INT) (in SQL SERVER), etc. 1384 | ``` 1385 | 1386 | #### builder.replace() 1387 | 1388 | ```plain 1389 | builder.replace(builder, [reference]) 1390 | ``` 1391 | replaces current instance of SqlBuilder with new. The argument `reference` (default: `false`) when is `true` creates a reference to `builder` (it doesn't clone it). Better performance with lower memory. 1392 | 1393 | - `builder` (SqlBuilder) Another instance of SqlBuilder. 1394 | 1395 | 1396 | --- 1397 | 1398 | #### builder.toString() 1399 | 1400 | - doesn't work with MongoDB 1401 | 1402 | ```plain 1403 | builder.toString() 1404 | ``` 1405 | creates escaped SQL query (internal) 1406 | 1407 | ## Blob 1408 | 1409 | ### PostgreSQL 1410 | 1411 | - all file operations are executed just-in-time (you don't need to call `sql.exec()`) 1412 | - all file operations aren't executed in queue 1413 | 1414 | ```javascript 1415 | // sql.writeStream(filestream, [buffersize](default: 16384), callback(err, loid)) 1416 | sql.writeStream(Fs.createReadStream('/file.png'), function(err, loid) { 1417 | // Now is the file inserted 1418 | // Where is the file stored? 1419 | 1420 | // loid === NUMBER 1421 | // SELECT * FROM pg_largeobject WHERE loid=loid 1422 | }); 1423 | 1424 | // sql.writeBuffer(buffer, callback(err, loid)) 1425 | sql.writeBuffer(Buffer.from('Peter Širka', 'utf8'), function(err, loid) { 1426 | // Now is the buffer inserted 1427 | // Where is the buffer stored? 1428 | 1429 | // loid === NUMBER 1430 | // SELECT * FROM pg_largeobject WHERE loid=loid 1431 | }); 1432 | 1433 | // sql.readStream(loid, [buffersize](default: 16384), callback(err, stream, size)) 1434 | sql.readStream(loid, function(err, stream, size) { 1435 | // stream is created 1436 | }); 1437 | ``` 1438 | 1439 | ### MongoDB 1440 | 1441 | - all file operations are executed immediately, there's no need to call sql.exec() 1442 | 1443 | ```javascript 1444 | // nosql.writeStream(id, stream, filename, [metadata], [options], callback) 1445 | nosql.writeStream(new ObjectID(), Fs.createReadStream('logo.png'), 'logo.png', function(err) { 1446 | // Now is the stream inserted 1447 | }); 1448 | 1449 | // nosql.readStream(id, [options], callback(err, stream, metadata, size, filename)) 1450 | nosql.readStream(id, function(err, stream, metadata, size, filename) { 1451 | stream.pipe(Fs.createWriteStream('myfile.png')); 1452 | }); 1453 | 1454 | // get file info 1455 | nosql.select('fs.files').make(function(builder){ 1456 | // available fields - _id,filename,contentType,length,chunkSize,uploadDate,aliases,metadata,md5 1457 | builder.fields('filename', 'metadata'); 1458 | }); 1459 | 1460 | nosql.exec(function(err, results){ 1461 | console.log(results); 1462 | }); 1463 | ``` 1464 | 1465 | ## Global events 1466 | 1467 | __Global events__: 1468 | 1469 | ```javascript 1470 | ON('database', function() { 1471 | // Database is ready 1472 | }); 1473 | ``` 1474 | 1475 | ## Async/Await 1476 | 1477 | `+v12.0.0` supports `sql.promise([name], [callback(response)])` for using of async/await. 1478 | 1479 | - `sql.promise()` performs `sql.exec()` 1480 | - look to example below: 1481 | 1482 | ```javascript 1483 | var Agent = require('sqlagent/pg').connect('...'); 1484 | 1485 | async function data() { 1486 | var b = new Agent(); 1487 | b.select('users', 'tbl_users'); 1488 | var users = await b.promise('users'); 1489 | console.log(users); 1490 | } 1491 | 1492 | data(); 1493 | ``` 1494 | 1495 | ## Contributors 1496 | 1497 | | Contributor | Type | E-mail | 1498 | |-------------|------|--------| 1499 | | [Peter Širka](https://github.com/JozefGula) | author + support | | 1500 | | [Martin Smola](https://github.com/molda) | contributor + support | | 1501 | | [Jay Kelkar](https://github.com/jkelkar) | contributor | | 1502 | | [Aidan Dunn](https://github.com/Aidan-Chey) | contributor | | 1503 | 1504 | ## Contact 1505 | 1506 | Do you have any questions? Contact us 1507 | 1508 | [![Professional Support](https://www.totaljs.com/img/badge-support.svg)](https://www.totaljs.com/support/) [![Chat with contributors](https://www.totaljs.com/img/badge-chat.svg)](https://messenger.totaljs.com) 1509 | 1510 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat 1511 | [license-url]: license.txt 1512 | 1513 | [npm-url]: https://npmjs.org/package/sqlagent 1514 | [npm-version-image]: https://img.shields.io/npm/v/sqlagent.svg?style=flat 1515 | [npm-downloads-image]: https://img.shields.io/npm/dm/sqlagent.svg?style=flat 1516 | -------------------------------------------------------------------------------- /sqlserver.js: -------------------------------------------------------------------------------- 1 | const database = require('mssql'); 2 | const Parser = require('url'); 3 | const queries = {}; 4 | const columns_cache = {}; 5 | const pools_cache = {}; 6 | const REG_SELECT = /select/i; 7 | const REG_CUSTOM = /#\d+#/g; 8 | const REG_WILDCARD = /\*/i; 9 | const REG_APO = /'/g; 10 | const REG_COLUMN = /^(!{1,}|\s)*/; 11 | const REG_COLUMN_CAST = /\[|\]/g; 12 | const REG_ARGUMENT = /\?/g; 13 | 14 | require('./index'); 15 | 16 | function SqlBuilder(skip, take, agent) { 17 | this.agent = agent; 18 | this.builder = []; 19 | this._order = null; 20 | this._skip = skip >= 0 ? skip : 0; 21 | this._take = take >= 0 ? take : 0; 22 | this._set = null; 23 | this._define; 24 | this._fn; 25 | this._join; 26 | this._fields; 27 | this._schema; 28 | this._primary; 29 | this._group; 30 | this._having; 31 | this._is = false; 32 | this.hasOperator = false; 33 | } 34 | 35 | SqlBuilder.prototype = { 36 | get data() { 37 | return this._set; 38 | } 39 | }; 40 | 41 | SqlBuilder.prototype.callback = function(fn) { 42 | this.$callback = fn; 43 | return this; 44 | }; 45 | 46 | SqlBuilder.prototype.assign = function(name, key) { 47 | this.$assignname = name; 48 | this.$assignkey = key; 49 | return this; 50 | }; 51 | 52 | SqlBuilder.prototype.replace = function(builder, reference) { 53 | var self = this; 54 | 55 | self.builder = reference ? builder.builder : builder.builder.slice(0); 56 | 57 | if (builder._order) 58 | self._order = reference ? builder._order : builder._order.slice(0); 59 | 60 | self._skip = builder._skip; 61 | self._take = builder._take; 62 | 63 | if (builder._set) 64 | self._set = reference ? builder._set : copy(builder._set); 65 | 66 | if (builder._fn) 67 | self._fn = reference ? builder._fn : copy(builder._fn); 68 | 69 | if (builder._join) 70 | self._join = reference ? builder._join : builder._join.slice(0); 71 | 72 | if (builder._fields) 73 | self._fields = builder._fields; 74 | 75 | if (builder._schema) 76 | self._schema = builder._schema; 77 | 78 | if (builder._primary) 79 | self._primary = builder._primary; 80 | 81 | self._is = builder._is; 82 | self.hasOperator = builder.hasOperator; 83 | return self; 84 | }; 85 | 86 | function copy(source) { 87 | 88 | var keys = Object.keys(source); 89 | var i = keys.length; 90 | var target = {}; 91 | 92 | while (i--) { 93 | var key = keys[i]; 94 | target[key] = source[key]; 95 | } 96 | 97 | return target; 98 | } 99 | 100 | SqlBuilder.prototype.clone = function() { 101 | var builder = new SqlBuilder(0, 0, this.agent); 102 | return builder.replace(this); 103 | }; 104 | 105 | SqlBuilder.prototype.join = function(name, on, type) { 106 | var self = this; 107 | if (!self._join) 108 | self._join = []; 109 | 110 | if (!type) 111 | type = 'left'; 112 | 113 | self._join.push(type + ' join ' + name + ' on ' + on); 114 | return self; 115 | }; 116 | 117 | SqlBuilder.prototype.schema = function(name) { 118 | this._schema = name; 119 | return this; 120 | }; 121 | 122 | SqlBuilder.prototype.remove = SqlBuilder.prototype.rem = function(name) { 123 | if (this._set) 124 | this._set[name] = undefined; 125 | return this; 126 | }; 127 | 128 | SqlBuilder.prototype.prepare = function(query) { 129 | if (!this._skip && this._take) 130 | return query.replace(REG_SELECT, 'SELECT TOP ' + this._take); 131 | return query; 132 | }; 133 | 134 | SqlBuilder.prototype.define = function(name, type) { 135 | var self = this; 136 | if (!self._define) 137 | self._define = {}; 138 | self._define[name] = type; 139 | return self; 140 | }; 141 | 142 | SqlBuilder.prototype.set = function(name, value) { 143 | var self = this; 144 | if (!self._set) 145 | self._set = {}; 146 | 147 | if (typeof(name) === 'string') { 148 | self._set[name] = value === '$' ? '#00#' : value; 149 | return self; 150 | } 151 | 152 | var keys = Object.keys(name); 153 | 154 | for (var i = 0, length = keys.length; i < length; i++) { 155 | var key = keys[i]; 156 | var val = name[key]; 157 | if (val !== undefined) 158 | self._set[key] = val === '$' ? '#00#' : val; 159 | } 160 | 161 | return self; 162 | }; 163 | 164 | SqlBuilder.prototype.inc = function(name, type, value) { 165 | 166 | var self = this; 167 | var can = false; 168 | 169 | if (!self._set) 170 | self._set = {}; 171 | 172 | if (value === undefined) { 173 | value = type; 174 | type = '+'; 175 | can = true; 176 | } 177 | 178 | if (typeof(name) === 'string') { 179 | 180 | if (can && typeof(value) === 'string') { 181 | type = value[0]; 182 | switch (type) { 183 | case '+': 184 | case '-': 185 | case '*': 186 | case '/': 187 | value = value.substring(1).parseFloat(); 188 | break; 189 | default: 190 | type = '+'; 191 | value = value.parseFloat(); 192 | break; 193 | } 194 | } else { 195 | type = '+'; 196 | if (value == null) 197 | value = 1; 198 | } 199 | 200 | if (!value) 201 | return self; 202 | 203 | name = type + name; 204 | self._set[name] = value === '$' ? '#00#' : value; 205 | return self; 206 | } 207 | 208 | var keys = Object.keys(name); 209 | 210 | for (var i = 0, length = keys.length; i < length; i++) { 211 | var key = keys[i]; 212 | name[key] && self.inc(key, name[key]); 213 | } 214 | 215 | return self; 216 | }; 217 | 218 | SqlBuilder.prototype.sort = function(name, desc) { 219 | return this.order(name, desc); 220 | }; 221 | 222 | SqlBuilder.prototype.random = function() { 223 | var self = this; 224 | if (!self._order) 225 | self._order = []; 226 | self._order.push('NEWID()'); 227 | return self; 228 | }; 229 | 230 | SqlBuilder.prototype.order = function(name, desc) { 231 | 232 | var self = this; 233 | if (!self._order) 234 | self._order = []; 235 | 236 | var key = '<' + name + '.' + self._schema + '.' + (desc || 'false') + '>'; 237 | if (columns_cache[key]) { 238 | self._order.push(columns_cache[key]); 239 | return self; 240 | } 241 | 242 | var lowered = name.toLowerCase(); 243 | 244 | if (lowered.lastIndexOf(' desc') !== -1 || lowered.lastIndexOf(' asc') !== -1) { 245 | columns_cache[key] = SqlBuilder.column(name, self._schema); 246 | self._order.push(columns_cache[key]); 247 | return self; 248 | } else if (typeof(desc) === 'boolean') 249 | desc = desc === true ? 'DESC' : 'ASC'; 250 | else 251 | desc = 'ASC'; 252 | 253 | columns_cache[key] = SqlBuilder.column(name, self._schema) + ' ' + desc; 254 | self._order.push(columns_cache[key]); 255 | return self; 256 | }; 257 | 258 | SqlBuilder.prototype.random = function() { 259 | var self = this; 260 | if (!self._order) 261 | self._order = []; 262 | self._order.push('RAND()'); 263 | return self; 264 | }; 265 | 266 | SqlBuilder.prototype.skip = function(value) { 267 | var self = this; 268 | self._skip = self.parseInt(value); 269 | return self; 270 | }; 271 | 272 | SqlBuilder.prototype.limit = function(value) { 273 | return this.take(value); 274 | }; 275 | 276 | SqlBuilder.prototype.page = function(value, max) { 277 | var self = this; 278 | value = self.parseInt(value) - 1; 279 | max = self.parseInt(max); 280 | if (value < 0) 281 | value = 0; 282 | self._skip = value * max; 283 | self._take = max; 284 | return self; 285 | }; 286 | 287 | SqlBuilder.prototype.parseInt = function(num) { 288 | if (typeof(num) === 'number') 289 | return num; 290 | if (!num) 291 | return 0; 292 | num = parseInt(num); 293 | if (isNaN(num)) 294 | num = 0; 295 | return num; 296 | }; 297 | 298 | SqlBuilder.prototype.take = function(value) { 299 | var self = this; 300 | self._take = self.parseInt(value); 301 | return self; 302 | }; 303 | 304 | SqlBuilder.prototype.first = function() { 305 | var self = this; 306 | self._skip = 0; 307 | self._take = 1; 308 | return self; 309 | }; 310 | 311 | SqlBuilder.prototype.where = function(name, operator, value) { 312 | return this.push(name, operator, value); 313 | }; 314 | 315 | SqlBuilder.prototype.push = function(name, operator, value) { 316 | var self = this; 317 | 318 | if (value === undefined) { 319 | value = operator; 320 | operator = '='; 321 | } else if (operator === '!=') 322 | operator = '<>'; 323 | 324 | var is = false; 325 | 326 | // I expect Agent.$$ 327 | if (typeof(value) === 'function') { 328 | if (!self._fn) 329 | self._fn = {}; 330 | var key = Math.floor(Math.random() * 1000000); 331 | self._fn[key] = value; 332 | value = '#' + key + '#'; 333 | is = true; 334 | } 335 | 336 | self.checkOperator(); 337 | self.builder.push(SqlBuilder.column(name, self._schema) + operator + (is ? value : SqlBuilder.escape(value))); 338 | self._is = true; 339 | return self; 340 | }; 341 | 342 | SqlBuilder.prototype.checkOperator = function() { 343 | var self = this; 344 | !self.hasOperator && self.and(); 345 | self.hasOperator = false; 346 | return self; 347 | }; 348 | 349 | SqlBuilder.prototype.clear = function() { 350 | this._take = 0; 351 | this._skip = 0; 352 | this._order = null; 353 | this._set = null; 354 | this.builder = []; 355 | this.hasOperator = false; 356 | return this; 357 | }; 358 | 359 | SqlBuilder.prototype.fields = function() { 360 | var self = this; 361 | if (!self._fields) 362 | self._fields = ''; 363 | 364 | if (arguments[0] instanceof Array) { 365 | var arr = arguments[0]; 366 | for (var i = 0, length = arr.length; i < length; i++) 367 | self._fields += (self._fields ? ',' : '') + SqlBuilder.column(arr[i], self._schema); 368 | } else { 369 | for (var i = 0; i < arguments.length; i++) 370 | self._fields += (self._fields ? ',' : '') + SqlBuilder.column(arguments[i], self._schema); 371 | } 372 | 373 | return self; 374 | }; 375 | 376 | SqlBuilder.prototype.field = function(name) { 377 | var self = this; 378 | if (!self._fields) 379 | self._fields = ''; 380 | self._fields += (self._fields ? ',' : '') + SqlBuilder.column(name, self._schema); 381 | return self; 382 | }; 383 | 384 | SqlBuilder.escape = SqlBuilder.prototype.escape = function(value) { 385 | 386 | if (value == null) 387 | return 'null'; 388 | 389 | var type = typeof(value); 390 | 391 | if (type === 'function') { 392 | value = value(); 393 | if (value == null) 394 | return 'null'; 395 | type = typeof(value); 396 | } 397 | 398 | if (type === 'boolean') 399 | return value ? '1' : '0'; 400 | 401 | if (type === 'number') 402 | return value.toString(); 403 | 404 | if (type === 'string') 405 | return SqlBuilder.escaper(value); 406 | 407 | if (value instanceof Array) 408 | return SqlBuilder.escaper(value.join(',')); 409 | 410 | if (value instanceof Date) 411 | return dateToString(value); 412 | 413 | return SqlBuilder.escaper(value.toString()); 414 | }; 415 | 416 | SqlBuilder.escaper = function(value) { 417 | return "'" + value.replace(REG_APO, '\'\'') + "'"; 418 | }; 419 | 420 | SqlBuilder.prototype.raw = function(name, value) { 421 | var self = this; 422 | if (!self._set) 423 | self._set = {}; 424 | self._set['!' + name] = value; 425 | return self; 426 | }; 427 | 428 | SqlBuilder.column = function(name, schema) { 429 | 430 | var cachekey = (schema ? schema + '.' : '') + name; 431 | var val = columns_cache[cachekey]; 432 | if (val) 433 | return val; 434 | 435 | var raw = false; 436 | 437 | if (name[0] === '!') { 438 | raw = true; 439 | name = name.replace(REG_COLUMN, ''); 440 | } 441 | 442 | var index = name.lastIndexOf('-->'); 443 | var cast = ''; 444 | 445 | if (index !== -1) { 446 | cast = name.substring(index).replace('-->', '').trim(); 447 | name = name.substring(0, index).trim(); 448 | } 449 | 450 | var indexAS = name.toLowerCase().indexOf(' as'); 451 | var plus = ''; 452 | 453 | if (indexAS !== -1) { 454 | plus = name.substring(indexAS); 455 | name = name.substring(0, indexAS); 456 | } else if (cast) 457 | plus = ' as [' + name + ']'; 458 | 459 | var casting = function(value) { 460 | return cast ? 'CAST(' + value + cast + ')' : value; 461 | }; 462 | 463 | if (cast) { 464 | switch (cast) { 465 | case 'integer': 466 | case 'int': 467 | case 'byte': 468 | case 'smallint': 469 | case 'number': 470 | cast = 'INT'; 471 | break; 472 | case 'float': 473 | case 'real': 474 | case 'double': 475 | case 'decimal': 476 | case 'currency': 477 | cast = 'REAL'; 478 | break; 479 | case 'boolean': 480 | case 'bool': 481 | cast = 'BIT'; 482 | break; 483 | } 484 | cast = ' AS ' + cast; 485 | } 486 | 487 | if (raw) 488 | return columns_cache[cachekey] = casting(name) + plus; 489 | 490 | name = name.replace(REG_COLUMN_CAST, ''); 491 | index = name.indexOf('.'); 492 | 493 | if (index === -1) 494 | return columns_cache[cachekey] = casting((schema ? schema + '.' : '') + '[' + name + ']') + plus; 495 | return columns_cache[cachekey] = casting(name.substring(0, index) + '.[' + name.substring(index + 1) + ']') + plus; 496 | }; 497 | 498 | SqlBuilder.prototype.group = function(names) { 499 | var self = this; 500 | 501 | if (names instanceof Array) { 502 | for (var i = 0, length = names.length; i < length; i++) 503 | names[i] = SqlBuilder.column(names[i], self._schema); 504 | self._group = 'GROUP BY ' + names.join(','); 505 | } else if (names) { 506 | var arr = new Array(arguments.length); 507 | for (var i = 0; i < arguments.length; i++) 508 | arr[i] = SqlBuilder.column(arguments[i.toString()], self._schema); 509 | self._group = 'GROUP BY ' + arr.join(','); 510 | } else 511 | self._group = undefined; 512 | 513 | return self; 514 | }; 515 | 516 | SqlBuilder.prototype.having = function(condition) { 517 | var self = this; 518 | 519 | if (condition) 520 | self._having = 'HAVING ' + condition; 521 | else 522 | self._having = undefined; 523 | 524 | return self; 525 | }; 526 | 527 | SqlBuilder.prototype.and = function() { 528 | var self = this; 529 | if (!self.builder.length) 530 | return self; 531 | self.hasOperator = true; 532 | self.builder.push('AND'); 533 | return self; 534 | }; 535 | 536 | SqlBuilder.prototype.or = function() { 537 | var self = this; 538 | if (!self.builder.length) 539 | return self; 540 | self.hasOperator = true; 541 | self.builder.push('OR'); 542 | return self; 543 | }; 544 | 545 | SqlBuilder.prototype.scope = function(fn) { 546 | var self = this; 547 | self.checkOperator(); 548 | self.builder.push('('); 549 | self.hasOperator = true; 550 | fn.call(self); 551 | self.builder.push(')'); 552 | return self; 553 | }; 554 | 555 | SqlBuilder.prototype.in = function(name, value) { 556 | var self = this; 557 | if (!(value instanceof Array)) { 558 | self.where(name, value); 559 | return self; 560 | } 561 | self.checkOperator(); 562 | var values = []; 563 | for (var i = 0, length = value.length; i < length; i++) 564 | values.push(SqlBuilder.escape(value[i])); 565 | self.builder.push(SqlBuilder.column(name, self._schema) + ' IN (' + values.join(',') + ')'); 566 | self._is = true; 567 | return self; 568 | }; 569 | 570 | SqlBuilder.prototype.like = function(name, value, where) { 571 | var self = this; 572 | var search; 573 | 574 | self.checkOperator(); 575 | 576 | switch (where) { 577 | case 'beg': 578 | case 'begin': 579 | search = SqlBuilder.escape('%' + value); 580 | break; 581 | case '*': 582 | search = SqlBuilder.escape('%' + value + '%'); 583 | break; 584 | case 'end': 585 | search = SqlBuilder.escape(value + '%'); 586 | break; 587 | default: 588 | search = SqlBuilder.escape(value); 589 | break; 590 | } 591 | 592 | self.builder.push(SqlBuilder.column(name, self._schema) + ' LIKE ' + search); 593 | self._is = true; 594 | return self; 595 | }; 596 | 597 | SqlBuilder.prototype.between = function(name, valueA, valueB) { 598 | var self = this; 599 | self.checkOperator(); 600 | self.builder.push(SqlBuilder.column(name, self._schema) + ' BETWEEN ' + SqlBuilder.escape(valueA) + ' AND ' + SqlBuilder.escape(valueB)); 601 | self._is = true; 602 | return self; 603 | }; 604 | 605 | SqlBuilder.prototype.query = SqlBuilder.prototype.sql = function(sql) { 606 | var self = this; 607 | self.checkOperator(); 608 | 609 | if (arguments.length > 1) { 610 | var indexer = 1; 611 | var argv = arguments; 612 | sql = sql.replace(REG_ARGUMENT, () => SqlBuilder.escape(argv[indexer++])); 613 | } 614 | 615 | self.builder.push(sql); 616 | self._is = true; 617 | return self; 618 | }; 619 | 620 | SqlBuilder.prototype.toString = function(id, isCounter) { 621 | 622 | var self = this; 623 | var plus = ''; 624 | var order = ''; 625 | var join = ''; 626 | 627 | if (self._join) 628 | join = self._join.join(' ') + ' '; 629 | 630 | if (!isCounter) { 631 | if (self._order) 632 | order = ' ORDER BY ' + self._order.join(','); 633 | if (self._skip && self._take) 634 | plus = ' OFFSET ' + self._skip + ' ROWS FETCH NEXT ' + self._take + ' ROWS ONLY'; 635 | else if (self._take) 636 | plus = ' OFFSET 0 ROWS FETCH NEXT ' + self._take + ' ROWS ONLY'; 637 | else if (self._skip) 638 | plus = ' OFFSET ' + self._skip + ' ROWS'; 639 | if (!self._order && plus.length) 640 | order = ' ORDER BY 1'; 641 | } 642 | 643 | if (!self.builder.length) 644 | return (join ? ' ' + join : '') + (self._group ? ' ' + self._group : '') + (self._having ? ' ' + self._having : '') + order + plus; 645 | 646 | var where = self.builder.join(' '); 647 | 648 | if (id === undefined) 649 | id = null; 650 | 651 | if (self._fn) 652 | where = where.replace(REG_CUSTOM, text => text === '#00#' ? SqlBuilder.escape(id) : SqlBuilder.escape(self._fn[parseInt(text.substring(1, text.length - 1))])); 653 | 654 | return (join ? ' ' + join : '') + (self._is ? ' WHERE ' : ' ') + where + (self._group ? ' ' + self._group : '') + (self._having ? ' ' + self._having : '') + order + plus; 655 | }; 656 | 657 | SqlBuilder.prototype.make = function(fn) { 658 | var self = this; 659 | fn.call(self, self); 660 | return self.agent || self; 661 | }; 662 | 663 | SqlBuilder.prototype.toQuery = function(query) { 664 | var self = this; 665 | return self._fields ? query.replace(REG_WILDCARD, self._fields) : query; 666 | }; 667 | 668 | function Agent(options, error, id) { 669 | this.$conn = id === undefined ? JSON.stringify(options) : id; 670 | this.isErrorBuilder = global.ErrorBuilder ? true : false; 671 | this.errors = this.isErrorBuilder ? error : null; 672 | this.options = options; 673 | this.db = null; 674 | this.clear(); 675 | this.$events = {}; 676 | 677 | // Hidden: 678 | // this.time; 679 | // this.$when; 680 | } 681 | 682 | Agent.prototype = { 683 | get $() { 684 | return new SqlBuilder(0, 0, this); 685 | }, 686 | get $$() { 687 | var self = this; 688 | return function() { 689 | return self.$id; 690 | }; 691 | } 692 | }; 693 | 694 | Agent.connect = function(conn, callback) { 695 | callback && callback(null); 696 | var id = (Math.random() * 1000000) >> 0; 697 | return function(error) { 698 | return new Agent(conn, error, id); 699 | }; 700 | }; 701 | 702 | Agent.prototype.emit = function(name, a, b, c, d, e, f, g) { 703 | var evt = this.$events[name]; 704 | if (evt) { 705 | var clean = false; 706 | for (var i = 0, length = evt.length; i < length; i++) { 707 | if (evt[i].$once) 708 | clean = true; 709 | evt[i].call(this, a, b, c, d, e, f, g); 710 | } 711 | if (clean) { 712 | evt = evt.remove(n => n.$once); 713 | if (evt.length) 714 | this.$events[name] = evt; 715 | else 716 | this.$events[name] = undefined; 717 | } 718 | } 719 | return this; 720 | }; 721 | 722 | Agent.prototype.on = function(name, fn) { 723 | 724 | if (!fn.$once) 725 | this.$free = false; 726 | 727 | if (this.$events[name]) 728 | this.$events[name].push(fn); 729 | else 730 | this.$events[name] = [fn]; 731 | return this; 732 | }; 733 | 734 | Agent.prototype.once = function(name, fn) { 735 | fn.$once = true; 736 | return this.on(name, fn); 737 | }; 738 | 739 | Agent.prototype.removeListener = function(name, fn) { 740 | var evt = this.$events[name]; 741 | if (evt) { 742 | evt = evt.remove(n => n === fn); 743 | if (evt.length) 744 | this.$events[name] = evt; 745 | else 746 | this.$events[name] = undefined; 747 | } 748 | return this; 749 | }; 750 | 751 | Agent.prototype.removeAllListeners = function(name) { 752 | if (name === true) 753 | this.$events = EMPTYOBJECT; 754 | else if (name) 755 | this.$events[name] = undefined; 756 | else 757 | this.$events[name] = {}; 758 | return this; 759 | }; 760 | 761 | // Debug mode (output to console) 762 | Agent.debug = false; 763 | 764 | Agent.prototype.clear = function() { 765 | 766 | this.command = []; 767 | this.done = null; 768 | this.last = null; 769 | this.id = null; 770 | this.$id = null; 771 | this.isCanceled = false; 772 | this.index = 0; 773 | this.isPut = false; 774 | this.skipCount = 0; 775 | this.skips = {}; 776 | this.$transaction; 777 | this.$fast = false; 778 | this.results = {}; 779 | this.builders = {}; 780 | 781 | if (this.$when) 782 | this.$when = undefined; 783 | 784 | if (this.errors && this.isErrorBuilder) 785 | this.errors.clear(); 786 | else if (this.errors) 787 | this.errors = null; 788 | 789 | return this; 790 | }; 791 | 792 | 793 | Agent.prototype.when = function(name, fn) { 794 | 795 | if (!this.$when) 796 | this.$when = {}; 797 | 798 | if (this.$when[name]) 799 | this.$when[name].push(fn); 800 | else 801 | this.$when[name] = [fn]; 802 | 803 | return this; 804 | }; 805 | 806 | Agent.prototype.priority = function() { 807 | var self = this; 808 | var length = self.command.length - 1; 809 | 810 | if (!length) 811 | return self; 812 | 813 | var last = self.command[length]; 814 | for (var i = length; i > -1; i--) 815 | self.command[i] = self.command[i - 1]; 816 | 817 | self.command[0] = last; 818 | return self; 819 | }; 820 | 821 | Agent.prototype.default = function(fn) { 822 | fn.call(this.results, this.results); 823 | return this; 824 | }; 825 | 826 | Agent.query = function(name, query) { 827 | queries[name] = query; 828 | return Agent; 829 | }; 830 | 831 | Agent.prototype.nolock = function(enable) { 832 | if (enable === undefined) 833 | this.$fast = true; 834 | else 835 | this.$fast = false; 836 | return this; 837 | }; 838 | 839 | Agent.prototype.primaryKey = Agent.prototype.primary = function() { 840 | // compatibility with PG 841 | return this; 842 | }; 843 | 844 | Agent.prototype.skip = function(name) { 845 | var self = this; 846 | if (name) 847 | self.skips[name] = true; 848 | else 849 | self.skipCount++; 850 | return self; 851 | }; 852 | 853 | Agent.prototype.prepare = function(fn) { 854 | var self = this; 855 | self.command.push({ type: 'prepare', fn: fn }); 856 | return self; 857 | }; 858 | 859 | Agent.prototype.ifnot = function(name, fn) { 860 | var self = this; 861 | self.prepare(function(error, response, resume) { 862 | var value = response[name]; 863 | if (value instanceof Array) { 864 | if (value.length) 865 | return resume(); 866 | } else if (value) 867 | return resume(); 868 | fn.call(self, error, response, value); 869 | setImmediate(resume); 870 | }); 871 | return self; 872 | }; 873 | 874 | Agent.prototype.ifexists = function(name, fn) { 875 | var self = this; 876 | self.prepare(function(error, response, resume) { 877 | 878 | var value = response[name]; 879 | if (value instanceof Array) { 880 | if (!value.length) 881 | return resume(); 882 | } else if (!value) 883 | return resume(); 884 | 885 | fn.call(self, error, response, value); 886 | setImmediate(resume); 887 | }); 888 | return self; 889 | }; 890 | 891 | Agent.prototype.modify = function(fn) { 892 | var self = this; 893 | self.command.push({ type: 'modify', fn: fn }); 894 | return self; 895 | }; 896 | 897 | Agent.prototype.bookmark = function(fn) { 898 | var self = this; 899 | self.command.push({ type: 'bookmark', fn: fn }); 900 | return self; 901 | }; 902 | 903 | Agent.prototype.put = function(value) { 904 | var self = this; 905 | self.command.push({ type: 'put', value: value, disable: value == null }); 906 | return self; 907 | }; 908 | 909 | Agent.prototype.lock = function() { 910 | return this.put(this.$$); 911 | }; 912 | 913 | Agent.prototype.unlock = function() { 914 | this.command.push({ 'type': 'unput' }); 915 | return this; 916 | }; 917 | 918 | Agent.prototype.query = function(name, query, params) { 919 | return this.push(name, query, params); 920 | }; 921 | 922 | Agent.prototype.push = function(name, query, params) { 923 | var self = this; 924 | 925 | if (typeof(query) !== 'string') { 926 | params = query; 927 | query = name; 928 | name = self.index++; 929 | } 930 | 931 | var is = false; 932 | 933 | if (!params) { 934 | is = true; 935 | params = new SqlBuilder(0, 0, self); 936 | } 937 | 938 | if (queries[query]) 939 | query = queries[query]; 940 | 941 | self.command.push({ name: name, query: query, params: params, first: isFIRST(query) }); 942 | self.builders[name] = params; 943 | return is ? params : self; 944 | }; 945 | 946 | Agent.prototype.validate = function(fn, error, reverse) { 947 | var self = this; 948 | var type = typeof(fn); 949 | 950 | if (typeof(error) === 'boolean') { 951 | reverse = error; 952 | error = undefined; 953 | } 954 | 955 | if (type === 'string' && error === undefined) { 956 | // checks the last result 957 | error = fn; 958 | fn = undefined; 959 | } 960 | 961 | if (type === 'function') { 962 | self.command.push({ type: 'validate', fn: fn, error: error }); 963 | return self; 964 | } 965 | 966 | if (type === 'string' && typeof(error) === 'function' && typeof(reverse) === 'string') 967 | return self.validate2(fn, error, reverse); 968 | 969 | var exec; 970 | 971 | if (reverse) { 972 | exec = function(err, results, next) { 973 | var id = fn == null ? self.last : fn; 974 | if (id == null) 975 | return next(true); 976 | var r = results[id]; 977 | if (r instanceof Array) 978 | return next(r.length === 0); 979 | if (r) 980 | return next(false); 981 | next(true); 982 | }; 983 | } else { 984 | exec = function(err, results, next) { 985 | var id = fn == null ? self.last : fn; 986 | if (id == null) 987 | return next(false); 988 | var r = results[id]; 989 | if (r instanceof Array) 990 | return next(r.length > 0); 991 | if (r) 992 | return next(true); 993 | next(false); 994 | }; 995 | } 996 | 997 | self.command.push({ type: 'validate', fn: exec, error: error }); 998 | return self; 999 | }; 1000 | 1001 | // validate2('result', n => n.length > 0, 'error'); 1002 | Agent.prototype.validate2 = function(name, fn, err) { 1003 | var self = this; 1004 | var type = typeof(fn); 1005 | 1006 | if (type === 'string') { 1007 | type = err; 1008 | err = fn; 1009 | fn = type; 1010 | } 1011 | 1012 | var validator = function(err, results, next) { 1013 | if (fn(results[name])) 1014 | return next(true); 1015 | err.push(err || name); 1016 | next(false); 1017 | }; 1018 | 1019 | self.command.push({ type: 'validate', fn: validator, error: err }); 1020 | return self; 1021 | }; 1022 | 1023 | Agent.prototype.cancel = function(fn) { 1024 | return this.validate(fn); 1025 | }; 1026 | 1027 | Agent.prototype.begin = function() { 1028 | var self = this; 1029 | self.command.push({ type: 'begin' }); 1030 | return self; 1031 | }; 1032 | 1033 | Agent.prototype.end = function() { 1034 | var self = this; 1035 | self.command.push({ type: 'end' }); 1036 | return self; 1037 | }; 1038 | 1039 | Agent.prototype.commit = function() { 1040 | return this.end(); 1041 | }; 1042 | 1043 | Agent.prototype._insert = function(item) { 1044 | 1045 | var values = item.condition._set; 1046 | var isPrepare = item.condition._define; 1047 | 1048 | var keys = Object.keys(values); 1049 | var columns = []; 1050 | var columns_values = []; 1051 | var params = []; 1052 | 1053 | for (var i = 0, length = keys.length; i < length; i++) { 1054 | var key = keys[i]; 1055 | var value = values[key]; 1056 | 1057 | var isRAW = key[0] === '!'; 1058 | if (isRAW) 1059 | key = key.substring(1); 1060 | 1061 | if (key[0] === '$' || value === undefined) 1062 | continue; 1063 | 1064 | switch (key[0]) { 1065 | case '+': 1066 | case '-': 1067 | case '*': 1068 | case '/': 1069 | key = key.substring(1); 1070 | if (!value) 1071 | value = 1; 1072 | break; 1073 | } 1074 | 1075 | columns.push('[' + key + ']'); 1076 | 1077 | if (isRAW) { 1078 | columns_values.push(value); 1079 | continue; 1080 | } 1081 | 1082 | columns_values.push('@' + key); 1083 | 1084 | var type = typeof(value); 1085 | var isFN = false; 1086 | 1087 | if (type === 'function') 1088 | value = value(); 1089 | 1090 | if (type === 'string') 1091 | value = value.trim(); 1092 | 1093 | if (type === 'function') 1094 | isFN = true; 1095 | 1096 | if (type === 'object') { 1097 | if (Buffer.isBuffer(value)) 1098 | type = 'varbinary'; 1099 | else if (typeof(value.getTime) === 'function') 1100 | type = 'datetime'; 1101 | } 1102 | 1103 | if (isPrepare && item.condition._define[key]) 1104 | type = item.condition._define[key]; 1105 | 1106 | params.push({ name: key, type: type, value: value === undefined ? null : value, isFN: isFN }); 1107 | } 1108 | 1109 | item.$query = 'INSERT INTO ' + item.table + ' (' + columns.join(',') + ') VALUES(' + columns_values.join(',') + '); SELECT @@identity AS [identity]'; 1110 | item.$params = params; 1111 | item.first = true; 1112 | return item; 1113 | }; 1114 | 1115 | Agent.prototype._update = function(item) { 1116 | 1117 | var values = item.condition._set; 1118 | var keys = Object.keys(values); 1119 | 1120 | var columns = []; 1121 | var params = []; 1122 | 1123 | for (var i = 0, length = keys.length; i < length; i++) { 1124 | var key = keys[i]; 1125 | var value = values[key]; 1126 | 1127 | var isRAW = key[0] === '!'; 1128 | if (isRAW) 1129 | key = key.substring(1); 1130 | 1131 | if (key[0] === '$' || value === undefined) 1132 | continue; 1133 | 1134 | var type = typeof(value); 1135 | if (type === 'function') 1136 | value = value(); 1137 | 1138 | if (type === 'string') 1139 | value = value.trim(); 1140 | 1141 | switch (key[0]) { 1142 | case '+': 1143 | key = key.substring(1); 1144 | columns.push('[' + key + ']=ISNULL([' + key + '],0)+@' + key); 1145 | if (!value) 1146 | value = 1; 1147 | break; 1148 | case '-': 1149 | key = key.substring(1); 1150 | columns.push('[' + key + ']=ISNULL([' + key + '],0)-@' + key); 1151 | if (!value) 1152 | value = 1; 1153 | break; 1154 | case '*': 1155 | key = key.substring(1); 1156 | columns.push('[' + key + ']=ISNULL([' + key + '],0)*@' + key); 1157 | if (!value) 1158 | value = 1; 1159 | break; 1160 | case '/': 1161 | key = key.substring(1); 1162 | columns.push('[' + key + ']=ISNULL([' + key + '],0)/@' + key); 1163 | if (!value) 1164 | value = 1; 1165 | break; 1166 | default: 1167 | if (isRAW) 1168 | columns.push('[' + key + ']=' + value); 1169 | else 1170 | columns.push('[' + key + ']=@' + key); 1171 | break; 1172 | } 1173 | 1174 | !isRAW && params.push({ name: key, type: type, value: value === undefined ? null : value }); 1175 | } 1176 | 1177 | item.$query = 'UPDATE ' + item.table + ' SET ' + columns.join(',') + item.condition.toString(this.id) + '; SELECT @@rowcount As affectedRows'; 1178 | item.$params = params; 1179 | item.column = 'affectedRows'; 1180 | item.first = true; 1181 | return item; 1182 | }; 1183 | 1184 | Agent.prototype._query = function(item) { 1185 | if (item.condition instanceof SqlBuilder) { 1186 | item.$query = (item.scalar ? item.query : item.condition.toQuery(item.query)) + item.condition.toString(this.id, item.scalar); 1187 | return item; 1188 | } 1189 | item.$query = item.query; 1190 | item.$params = item.condition; 1191 | return item; 1192 | }; 1193 | 1194 | Agent.prototype._select = function(item) { 1195 | item.query = 'SELECT * FROM ' + item.table; 1196 | item.$query = item.condition.toQuery(item.query) + item.condition.toString(this.id); 1197 | item.first = item.condition._take === 1; 1198 | return item; 1199 | }; 1200 | 1201 | Agent.prototype._compare = function(item) { 1202 | var keys = item.keys ? item.keys : item.condition._fields ? item.condition._fields.split(',') : Object.keys(item.value); 1203 | !item.condition._fields && item.condition.fields.apply(item.condition, keys); 1204 | item.query = 'SELECT * FROM ' + item.table; 1205 | item.$query = item.condition.toQuery(item.query) + item.condition.toString(this.id); 1206 | item.first = item.condition._take === 1; 1207 | return item; 1208 | }; 1209 | 1210 | Agent.prototype._delete = function(item) { 1211 | item.$query = item.query + item.condition.toString(this.id) + '; SELECT @@rowcount As affectedRows'; 1212 | item.column = 'affectedRows'; 1213 | item.first = true; 1214 | return item; 1215 | }; 1216 | 1217 | Agent.prototype.save = function(name, table, insert, maker) { 1218 | 1219 | if (typeof(table) === 'boolean') { 1220 | maker = insert; 1221 | insert = table; 1222 | table = name; 1223 | name = undefined; 1224 | } 1225 | 1226 | var self = this; 1227 | if (insert) 1228 | maker(self.insert(name, table), true); 1229 | else 1230 | maker(self.update(name, table), false); 1231 | 1232 | return self; 1233 | }; 1234 | 1235 | Agent.prototype.insert = function(name, table) { 1236 | 1237 | var self = this; 1238 | 1239 | if (typeof(table) !== 'string') { 1240 | table = name; 1241 | name = self.index++; 1242 | } 1243 | 1244 | var condition = new SqlBuilder(0, 0, self); 1245 | self.command.push({ type: 'insert', table: table, name: name, condition: condition }); 1246 | self.builders[name] = condition; 1247 | return condition; 1248 | }; 1249 | 1250 | Agent.prototype.select = function(name, table) { 1251 | 1252 | var self = this; 1253 | 1254 | if (typeof(table) !== 'string') { 1255 | table = name; 1256 | name = self.index++; 1257 | } 1258 | 1259 | var condition = new SqlBuilder(0, 0, self); 1260 | self.command.push({ type: 'select', name: name, table: table, condition: condition }); 1261 | self.builders[name] = condition; 1262 | return condition; 1263 | }; 1264 | 1265 | Agent.prototype.compare = function(name, table, obj, keys) { 1266 | 1267 | var self = this; 1268 | 1269 | if (typeof(table) !== 'string') { 1270 | keys = obj; 1271 | obj = table; 1272 | table = name; 1273 | name = self.index++; 1274 | } 1275 | 1276 | var condition = new SqlBuilder(0, 0, self); 1277 | condition.first(); 1278 | self.command.push({ type: 'compare', name: name, table: table, condition: condition, value: obj, keys: keys }); 1279 | self.builders[name] = condition; 1280 | return condition; 1281 | }; 1282 | 1283 | Agent.prototype.listing = function(name, table, column) { 1284 | 1285 | var self = this; 1286 | if (typeof(table) !== 'string') { 1287 | table = name; 1288 | name = self.index++; 1289 | } 1290 | 1291 | var key ='$listing_' + name; 1292 | var condition = new SqlBuilder(0, 0, self); 1293 | self.command.push({ type: 'query', query: 'SELECT COUNT(' + (column || '*') + ') as sqlagentcolumn FROM ' + table, name: key + '_count', condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true, nocallback: true }); 1294 | self.command.push({ type: 'select', name: key + '_items', table: table, condition: condition, listing: key, target: name }); 1295 | self.builders[name] = condition; 1296 | return condition; 1297 | }; 1298 | 1299 | Agent.prototype.find = Agent.prototype.builder = function(name) { 1300 | return this.builders[name]; 1301 | }; 1302 | 1303 | Agent.prototype.exists = function(name, table) { 1304 | var self = this; 1305 | 1306 | if (typeof(table) !== 'string') { 1307 | table = name; 1308 | name = self.index++; 1309 | } 1310 | 1311 | var condition = new SqlBuilder(0, 0, self); 1312 | condition.first(); 1313 | self.command.push({ type: 'query', query: 'SELECT 1 as sqlagentcolumn_e FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn_e', scalar: true }); 1314 | self.builders[name] = condition; 1315 | return condition; 1316 | }; 1317 | 1318 | Agent.prototype.count = function(name, table, column) { 1319 | var self = this; 1320 | 1321 | if (typeof(table) !== 'string') { 1322 | table = name; 1323 | name = self.index++; 1324 | } 1325 | 1326 | var condition = new SqlBuilder(0, 0, self); 1327 | self.command.push({ type: 'query', query: 'SELECT COUNT(' + (column || '*') + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1328 | self.builders[name] = condition; 1329 | return condition; 1330 | }; 1331 | 1332 | Agent.prototype.max = function(name, table, column) { 1333 | var self = this; 1334 | if (typeof(table) !== 'string') { 1335 | table = name; 1336 | name = self.index++; 1337 | } 1338 | 1339 | var condition = new SqlBuilder(0, 0, self); 1340 | self.command.push({ type: 'query', query: 'SELECT MAX(' + column + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1341 | self.builders[name] = condition; 1342 | return condition; 1343 | }; 1344 | 1345 | Agent.prototype.min = function(name, table, column) { 1346 | var self = this; 1347 | if (typeof(table) !== 'string') { 1348 | table = name; 1349 | name = self.index++; 1350 | } 1351 | 1352 | var condition = new SqlBuilder(0, 0, self); 1353 | self.command.push({ type: 'query', query: 'SELECT MIN(' + column + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1354 | self.builders[name] = condition; 1355 | return condition; 1356 | }; 1357 | 1358 | Agent.prototype.avg = function(name, table, column) { 1359 | var self = this; 1360 | if (typeof(table) !== 'string') { 1361 | table = name; 1362 | name = self.index++; 1363 | } 1364 | 1365 | var condition = new SqlBuilder(0, 0, self); 1366 | self.command.push({ type: 'query', query: 'SELECT AVG(' + column + ') as sqlagentcolumn FROM ' + table, name: name, condition: condition, first: true, column: 'sqlagentcolumn', datatype: 1, scalar: true }); 1367 | self.builders[name] = condition; 1368 | return condition; 1369 | }; 1370 | 1371 | Agent.prototype.update = function(name, table) { 1372 | 1373 | var self = this; 1374 | 1375 | if (typeof(table) !== 'string') { 1376 | table = name; 1377 | name = self.index++; 1378 | } 1379 | 1380 | var condition = new SqlBuilder(0, 0, self); 1381 | self.command.push({ type: 'update', table: table, name: name, condition: condition }); 1382 | self.builders[name] = condition; 1383 | return condition; 1384 | }; 1385 | 1386 | Agent.prototype.delete = function(name, table) { 1387 | 1388 | var self = this; 1389 | 1390 | if (typeof(table) !== 'string') { 1391 | table = name; 1392 | name = self.index++; 1393 | } 1394 | 1395 | var condition = new SqlBuilder(0, 0, self); 1396 | self.command.push({ type: 'delete', query: 'DELETE FROM ' + table, name: name, condition: condition }); 1397 | self.builders[name] = condition; 1398 | return condition; 1399 | }; 1400 | 1401 | Agent.prototype.remove = function(name, table) { 1402 | return this.delete(name, table); 1403 | }; 1404 | 1405 | Agent.prototype.destroy = function(name) { 1406 | var self = this; 1407 | for (var i = 0, length = self.command.length; i < length; i++) { 1408 | var item = self.command[i]; 1409 | if (item.name !== name) 1410 | continue; 1411 | self.command.splice(i, 1); 1412 | delete self.builders[name]; 1413 | return true; 1414 | } 1415 | return false; 1416 | }; 1417 | 1418 | Agent.prototype.expected = function(name, index, property) { 1419 | 1420 | var self = this; 1421 | 1422 | if (typeof(index) === 'string') { 1423 | property = index; 1424 | index = undefined; 1425 | } 1426 | 1427 | return function() { 1428 | var output = self.results[name]; 1429 | if (!output) 1430 | return null; 1431 | if (index === undefined) { 1432 | if (property === undefined) 1433 | return output; 1434 | return output[property]; 1435 | } 1436 | output = output[index]; 1437 | if (output) 1438 | return output[property]; 1439 | return null; 1440 | }; 1441 | }; 1442 | 1443 | Agent.prototype.close = function() { 1444 | var self = this; 1445 | self.done && self.done(); 1446 | self.done = null; 1447 | return self; 1448 | }; 1449 | 1450 | Agent.prototype.rollback = function(where, e, next) { 1451 | var self = this; 1452 | 1453 | self.errors && self.errors.push(e); 1454 | self.command.length = 0; 1455 | 1456 | if (!self.isTransaction) 1457 | return next(); 1458 | 1459 | self.isRollback = true; 1460 | self.end(); 1461 | next(); 1462 | }; 1463 | 1464 | Agent.prototype._prepare = function(callback) { 1465 | 1466 | var self = this; 1467 | 1468 | self.isRollback = false; 1469 | self.isTransaction = false; 1470 | 1471 | if (!self.errors) 1472 | self.errors = self.isErrorBuilder ? new global.ErrorBuilder() : []; 1473 | 1474 | self.command.sqlagent(function(item, next) { 1475 | 1476 | if (item.type === 'validate') { 1477 | try { 1478 | var tmp = item.fn(self.errors, self.results, function(output) { 1479 | if (output === true || output === undefined) 1480 | return next(); 1481 | // reason 1482 | if (typeof(output) === 'string') 1483 | self.errors.push(output); 1484 | else if (item.error) 1485 | self.errors.push(item.error); 1486 | // we have error 1487 | if (self.isTransaction) { 1488 | self.command.length = 0; 1489 | self.isRollback = true; 1490 | self.end(); 1491 | next(); 1492 | } else 1493 | next(false); 1494 | }); 1495 | 1496 | var type = typeof(tmp); 1497 | if (type !== 'boolean' && type !== 'string') 1498 | return; 1499 | if (tmp === true || tmp === undefined) 1500 | return next(); 1501 | // reason 1502 | if (typeof(tmp) === 'string') 1503 | self.errors.push(tmp); 1504 | else if (item.error) 1505 | self.errors.push(item.error); 1506 | // we have error 1507 | if (self.isTransaction) { 1508 | self.command.length = 0; 1509 | self.isRollback = true; 1510 | self.end(); 1511 | next(); 1512 | } else 1513 | next(false); 1514 | return; 1515 | } catch (e) { 1516 | self.rollback('validate', e, next); 1517 | } 1518 | return; 1519 | } 1520 | 1521 | if (item.type === 'bookmark') { 1522 | try { 1523 | item.fn(self.errors, self.results); 1524 | return next(); 1525 | } catch (e) { 1526 | self.rollback('bookmark', e, next); 1527 | } 1528 | } 1529 | 1530 | if (item.type === 'modify') { 1531 | try { 1532 | item.fn(self.results); 1533 | next(); 1534 | } catch (e) { 1535 | self.rollback('modify', e, next); 1536 | } 1537 | return; 1538 | } 1539 | 1540 | if (item.type === 'prepare') { 1541 | try { 1542 | item.fn(self.errors, self.results, () => next()); 1543 | } catch (e) { 1544 | self.rollback('prepare', e, next); 1545 | } 1546 | return; 1547 | } 1548 | 1549 | if (item.type === 'unput') { 1550 | self.isPut = false; 1551 | next(); 1552 | return; 1553 | } 1554 | 1555 | if (item.type === 'put') { 1556 | if (item.disable) 1557 | self.$id = null; 1558 | else 1559 | self.$id = typeof(item.value) === 'function' ? item.value() : item.value; 1560 | self.isPut = !self.disable; 1561 | next(); 1562 | return; 1563 | } 1564 | 1565 | if (self.skipCount) { 1566 | self.skipCount--; 1567 | next(); 1568 | return; 1569 | } 1570 | 1571 | if (typeof(item.name) === 'string') { 1572 | if (self.skips[item.name] === true) { 1573 | next(); 1574 | return; 1575 | } 1576 | } 1577 | 1578 | switch (item.type) { 1579 | case 'select': 1580 | self._select(item); 1581 | break; 1582 | case 'update': 1583 | self._update(item); 1584 | break; 1585 | case 'insert': 1586 | self._insert(item); 1587 | break; 1588 | case 'delete': 1589 | self._delete(item); 1590 | break; 1591 | case 'compare': 1592 | self._compare(item); 1593 | break; 1594 | default: 1595 | self._query(item); 1596 | break; 1597 | } 1598 | 1599 | if (item.type !== 'begin' && item.type !== 'end') { 1600 | 1601 | if (!item.first) 1602 | item.first = isFIRST(item.$query); 1603 | 1604 | (Agent.debug || self.debug) && console.log(self.debugname, item.name, item.$query); 1605 | self.$events.query && self.emit('query', item.name, item.$query, item.$params); 1606 | 1607 | var request = new database.Request(self.$transaction ? self.$transaction : self.db); 1608 | item.$params && prepare_params_request(request, item.$params); 1609 | 1610 | request.query(item.$query, function(err, rows) { 1611 | self.$bind(item, err, rows ? rows.recordset : []); 1612 | next(); 1613 | }); 1614 | return; 1615 | } 1616 | 1617 | if (item.type === 'begin') { 1618 | (Agent.debug || self.debug) && console.log(self.debugname, 'begin transaction'); 1619 | self.$transaction = new database.Transaction(self.db); 1620 | self.$transaction.begin(function(err) { 1621 | if (err) { 1622 | self.errors.push(err.message); 1623 | self.command.length = 0; 1624 | next(false); 1625 | return; 1626 | } 1627 | self.isTransaction = true; 1628 | self.isRollback = false; 1629 | next(); 1630 | }); 1631 | return; 1632 | } 1633 | 1634 | if (item.type === 'end') { 1635 | self.isTransaction = false; 1636 | if (self.isRollback) { 1637 | (Agent.debug || self.debug) && console.log(self.debugname, 'rollback transaction'); 1638 | self.$transaction.rollback(function(err) { 1639 | self.$transaction = null; 1640 | if (!err) 1641 | return next(); 1642 | self.command.length = 0; 1643 | self.push(err.message); 1644 | self.$transaction = null; 1645 | next(false); 1646 | }); 1647 | return; 1648 | } 1649 | 1650 | (Agent.debug || self.debug) && console.log(self.debugname, 'commit transaction'); 1651 | self.$transaction.commit(function(err) { 1652 | 1653 | if (!err) { 1654 | self.$transaction = null; 1655 | return next(); 1656 | } 1657 | 1658 | self.errors.push(err.message); 1659 | self.command.length = 0; 1660 | self.$transaction.rollback(function(err) { 1661 | self.$transaction = null; 1662 | if (!err) 1663 | return next(); 1664 | self.errors.push(err.message); 1665 | next(); 1666 | }); 1667 | }); 1668 | return; 1669 | } 1670 | 1671 | }, function() { 1672 | 1673 | if (Agent.debug || self.debug) { 1674 | self.time = Date.now() - self.debugtime; 1675 | console.log(self.debugname, '----- done (' + self.time + ' ms)'); 1676 | } 1677 | 1678 | self.index = 0; 1679 | self.done && self.done(); 1680 | self.done = null; 1681 | var err = null; 1682 | 1683 | if (self.isErrorBuilder) { 1684 | if (self.errors.hasError()) 1685 | err = self.errors; 1686 | } else if (self.errors.length) 1687 | err = self.errors; 1688 | 1689 | self.$events.end && self.emit('end', err, self.results, self.time); 1690 | callback && callback(err, self.returnIndex !== undefined ? self.results[self.returnIndex] : self.results); 1691 | }); 1692 | 1693 | return self; 1694 | }; 1695 | 1696 | Agent.prototype.$bindwhen = function(name) { 1697 | var self = this; 1698 | if (!self.$when) 1699 | return self; 1700 | var tmp = self.$when[name]; 1701 | if (!tmp) 1702 | return self; 1703 | for (var i = 0, length = tmp.length; i < length; i++) 1704 | tmp[i](self.errors, self.results, self.results[name]); 1705 | return self; 1706 | }; 1707 | 1708 | Agent.prototype.$bind = function(item, err, rows) { 1709 | 1710 | var self = this; 1711 | var obj; 1712 | 1713 | if (err) { 1714 | item.condition && item.condition.$callback && item.condition.$callback(err); 1715 | self.errors.push(item.name + ': ' + err.message); 1716 | if (self.isTransaction) 1717 | self.isRollback = true; 1718 | self.last = item.name; 1719 | return; 1720 | } 1721 | 1722 | if (!rows || !rows.length) { 1723 | if (item.type === 'insert') { 1724 | self.id = null; 1725 | if (!self.isPut) 1726 | self.$id = self.id; 1727 | } else if (!item.first) 1728 | self.results[item.name] = []; 1729 | 1730 | if (item.listing) { 1731 | obj = {}; 1732 | obj.count = self.results[item.listing + '_count']; 1733 | obj.items = self.results[item.listing + '_items']; 1734 | obj.page = 1; 1735 | obj.pages = 0; 1736 | obj.limit = item.condition._take; 1737 | self.results[item.target] = obj; 1738 | self.results[item.listing + '_count'] = null; 1739 | self.results[item.listing + '_items'] = null; 1740 | item.condition && item.condition.$callback && item.condition.$callback(null, obj); 1741 | item.condition.$assignname && self.results[item.condition.$assignname] && (self.results[item.condition.$assignname][item.condition.$assignkey] = obj); 1742 | } else 1743 | item.condition && !item.nocallback && item.condition.$callback && item.condition.$callback(null, self.results[item.name]); 1744 | self.$events.data && self.emit('data', item.target || item.name, self.results); 1745 | self.last = item.name; 1746 | self.$bindwhen(item.name); 1747 | return; 1748 | } 1749 | 1750 | if (item.type === 'insert') { 1751 | self.id = rows.length ? rows[0].identity : null; 1752 | if (!self.isPut) 1753 | self.$id = self.id; 1754 | } 1755 | 1756 | if (item.first && item.column) { 1757 | if (rows.length) 1758 | self.results[item.name] = item.column === 'sqlagentcolumn_e' ? true : item.datatype === 1 ? item.condition && item.condition._group ? rows.length : parseFloat(rows[0][item.column] || 0) : rows[0][item.column]; 1759 | } else if (item.first) 1760 | self.results[item.name] = rows instanceof Array ? rows[0] : rows; 1761 | else 1762 | self.results[item.name] = rows; 1763 | 1764 | if (item.listing) { 1765 | obj = {}; 1766 | obj.count = self.results[item.listing + '_count']; 1767 | obj.items = self.results[item.listing + '_items']; 1768 | obj.page = ((item.condition._skip || 0) / (item.condition._take || 0)) + 1; 1769 | obj.limit = item.condition._take || 0; 1770 | obj.pages = Math.ceil(obj.count / obj.limit); 1771 | self.results[item.target] = obj; 1772 | self.results[item.listing + '_count'] = null; 1773 | self.results[item.listing + '_items'] = null; 1774 | item.condition && item.condition.$callback && item.condition.$callback(null, obj); 1775 | } else if (item.type === 'compare') { 1776 | 1777 | var keys = item.keys; 1778 | var val = self.results[item.name]; 1779 | var diff; 1780 | 1781 | if (val) { 1782 | diff = []; 1783 | for (var i = 0, length = keys.length; i < length; i++) { 1784 | var key = keys[i]; 1785 | var a = val[key]; 1786 | var b = item.value[key]; 1787 | if (a != b) 1788 | diff.push(key); 1789 | } 1790 | } else 1791 | diff = keys; 1792 | 1793 | self.results[item.name] = diff.length ? { diff: diff, record: val, value: item.value } : false; 1794 | } 1795 | 1796 | !item.listing && item.condition && !item.nocallback && item.condition.$callback && item.condition.$callback(null, self.results[item.name]); 1797 | item.condition && item.condition.$assignname && self.results[item.condition.$assignname] && (self.results[item.condition.$assignname][item.condition.$assignkey] = obj); 1798 | self.$events.data && self.emit('data', item.target || item.name, self.results); 1799 | self.last = item.name; 1800 | self.$bindwhen(item.name); 1801 | }; 1802 | 1803 | Agent.prototype.exec = function(callback, returnIndex) { 1804 | 1805 | var self = this; 1806 | 1807 | if (Agent.debug || self.debug) { 1808 | self.debugname = 'sqlagent/sqlserver (' + Math.floor(Math.random() * 1000) + ')'; 1809 | self.debugtime = Date.now(); 1810 | } 1811 | 1812 | if (returnIndex !== undefined && typeof(returnIndex) !== 'boolean') 1813 | self.returnIndex = returnIndex; 1814 | else 1815 | self.returnIndex = undefined; 1816 | 1817 | if (!self.command.length) { 1818 | callback && callback.call(self, null, {}); 1819 | return self; 1820 | } 1821 | 1822 | (Agent.debug || self.debug) && console.log(self.debugname, '----- exec'); 1823 | 1824 | if (!pools_cache[self.$conn]) { 1825 | if (typeof(self.options) === 'string') { 1826 | var options = Parser.parse(self.options); 1827 | self.options = {}; 1828 | self.options.server = options.host.replace(/_/g, '\\').split(':')[0]; 1829 | if (options.pathname && options.pathname.length > 1) 1830 | self.options.database = options.pathname.substring(1); 1831 | if (options.port) 1832 | self.options.port = +options.port; 1833 | var auth = options.auth; 1834 | if (auth) { 1835 | auth = auth.split(':'); 1836 | self.options.user = auth[0]; 1837 | self.options.password = auth[1]; 1838 | } 1839 | pools_cache[self.$conn] = self.options; 1840 | } else 1841 | pools_cache[self.$conn] = self.options; 1842 | } else 1843 | self.options = pools_cache[self.$conn]; 1844 | 1845 | //self.db = new database.connect(self.options, function(err) { 1846 | self.db = new database.ConnectionPool(self.options, function(err) { 1847 | if (err) { 1848 | if (!self.errors) 1849 | self.errors = self.isErrorBuilder ? new global.ErrorBuilder() : []; 1850 | self.errors.push(err); 1851 | callback && callback.call(self, self.errors, {}); 1852 | return; 1853 | } 1854 | self._prepare(callback); 1855 | }); 1856 | 1857 | return self; 1858 | }; 1859 | 1860 | Agent.destroy = function() { 1861 | var keys = Object.keys(pools_cache); 1862 | for (var i = 0, length = keys.length; i < length; i++) 1863 | pools_cache[keys[i]].end(function(){}); 1864 | }; 1865 | 1866 | Agent.prototype.done = function() { 1867 | this.db && this.db.close(); 1868 | return this; 1869 | }; 1870 | 1871 | Agent.prototype.$$exec = function(returnIndex) { 1872 | var self = this; 1873 | return function(callback) { 1874 | return self.exec(callback, returnIndex); 1875 | }; 1876 | }; 1877 | 1878 | Agent.prototype.promise = function(index, fn) { 1879 | var self = this; 1880 | 1881 | if (typeof(index) === 'function') { 1882 | fn = index; 1883 | index = undefined; 1884 | } 1885 | 1886 | return new Promise(function(resolve, reject) { 1887 | self.exec(function(err, result) { 1888 | if (err) 1889 | reject(err); 1890 | else 1891 | resolve(fn ? fn(result) : result); 1892 | }, index); 1893 | }); 1894 | }; 1895 | 1896 | function dateToString(dt) { 1897 | var arr = []; 1898 | arr.push(dt.getFullYear().toString()); 1899 | arr.push((dt.getMonth() + 1).toString()); 1900 | arr.push(dt.getDate().toString()); 1901 | arr.push(dt.getHours().toString()); 1902 | arr.push(dt.getMinutes().toString()); 1903 | arr.push(dt.getSeconds().toString()); 1904 | for (var i = 1, length = arr.length; i < length; i++) { 1905 | if (arr[i].length === 1) 1906 | arr[i] = '0' + arr[i]; 1907 | } 1908 | return arr[0] + '-' + arr[1] + '-' + arr[2] + ' ' + arr[3] + ':' + arr[4] + ':' + arr[5]; 1909 | } 1910 | 1911 | function prepare_params_request(request, params) { 1912 | 1913 | if (!params) 1914 | return; 1915 | 1916 | for (var i = 0, length = params.length; i < length; i++) { 1917 | var param = params[i]; 1918 | var type = param.type.toLowerCase(); 1919 | var value = param.value; 1920 | 1921 | if (param.isFN) { 1922 | value = value(params); 1923 | type = typeof(value); 1924 | } 1925 | 1926 | switch (type) { 1927 | case 'number': 1928 | request.input(param.name, value % 1 === 0 ? database.Int : database.Float, value); 1929 | break; 1930 | case 'decimal': 1931 | request.input(param.name, database.Decimal, value); 1932 | break; 1933 | case 'uniqueidentifier': 1934 | case 'guid': 1935 | request.input(param.name, database.UniqueIdentifier, value); 1936 | break; 1937 | case 'money': 1938 | request.input(param.name, database.Money, value); 1939 | break; 1940 | case 'float': 1941 | request.input(param.name, database.Float, value); 1942 | break; 1943 | case 'bigint': 1944 | request.input(param.name, database.BigInt, value); 1945 | break; 1946 | case 'smallint': 1947 | case 'byte': 1948 | request.input(param.name, database.SmallInt, value); 1949 | break; 1950 | case 'string': 1951 | case 'nvarchar': 1952 | request.input(param.name, database.NVarChar, value); 1953 | break; 1954 | case 'boolean': 1955 | case 'bit': 1956 | request.input(param.name, database.Bit, value); 1957 | break; 1958 | case 'datetime': 1959 | request.input(param.name, database.DateTime, value); 1960 | break; 1961 | case 'smalldatetime': 1962 | request.input(param.name, database.SmallDateTime, value); 1963 | break; 1964 | case 'binary': 1965 | request.input(param.name, database.Binary, value); 1966 | break; 1967 | case 'image': 1968 | request.input(param.name, database.Image, value); 1969 | break; 1970 | case 'varbinary': 1971 | request.input(param.name, database.VarBinary, value); 1972 | break; 1973 | case 'varchar': 1974 | request.input(param.name, database.VarChar, value); 1975 | break; 1976 | case 'text': 1977 | request.input(param.name, database.Text, value); 1978 | break; 1979 | case 'ntext': 1980 | request.input(param.name, database.NText, value); 1981 | break; 1982 | } 1983 | } 1984 | } 1985 | 1986 | function isFIRST(query) { 1987 | return query ? query.substring(0, 13).toLowerCase() === 'select top 1' : false; 1988 | } 1989 | 1990 | Agent.init = function(conn, debug) { 1991 | Agent.debug = debug ? true : false; 1992 | var id = (Math.random() * 100000) >> 0; 1993 | framework.database = function(errorBuilder) { 1994 | return new Agent(conn, errorBuilder, id); 1995 | }; 1996 | EMIT('database'); 1997 | }; 1998 | 1999 | Agent.escape = Agent.prototype.escape = SqlBuilder.escape = SqlBuilder.prototype.escape = function(value) { 2000 | 2001 | if (value == null) 2002 | return 'null'; 2003 | 2004 | var type = typeof(value); 2005 | 2006 | if (type === 'function') { 2007 | value = value(); 2008 | 2009 | if (value == null) 2010 | return 'null'; 2011 | 2012 | type = typeof(value); 2013 | } 2014 | 2015 | if (type === 'boolean') 2016 | return value === true ? '1' : '0'; 2017 | 2018 | if (type === 'number') 2019 | return value.toString(); 2020 | 2021 | if (type === 'string') 2022 | return SqlBuilder.escaper(value); 2023 | 2024 | if (value instanceof Array) 2025 | return SqlBuilder.escaper(value.join(',')); 2026 | 2027 | if (value instanceof Date) 2028 | return dateToString(value); 2029 | 2030 | return SqlBuilder.escaper(value.toString()); 2031 | }; 2032 | 2033 | module.exports = Agent; 2034 | global.SqlBuilder = SqlBuilder; 2035 | --------------------------------------------------------------------------------