1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 |
|
9 |
|
10 | const util = require('util');
|
11 | const SQLConnector = require('../../lib/sql');
|
12 | const debug = require('debug')('loopback:connector:test-sql');
|
13 |
|
14 | let transactionId = 0;
|
15 |
|
16 | function MockTransaction(connector, name) {
|
17 | this.connector = connector;
|
18 | this.name = name;
|
19 | this.data = {};
|
20 | }
|
21 |
|
22 | MockTransaction.prototype.commit = function(cb) {
|
23 | const self = this;
|
24 |
|
25 | for (const m in this.data) {
|
26 | self.connector.data[m] = self.connector.data[m] || [];
|
27 | for (let i = 0, n = this.data[m].length; i < n; i++) {
|
28 | self.connector.data[m].push(this.data[m]);
|
29 | }
|
30 | }
|
31 | this.data = {};
|
32 | cb();
|
33 | };
|
34 |
|
35 | MockTransaction.prototype.rollback = function(cb) {
|
36 | this.data = {};
|
37 | cb();
|
38 | };
|
39 |
|
40 | exports.initialize = function initializeDataSource(dataSource, callback) {
|
41 | process.nextTick(function() {
|
42 | if (callback) {
|
43 | const connector = new TestConnector(dataSource.settings);
|
44 | connector.dataSource = dataSource;
|
45 | dataSource.connector = connector;
|
46 | callback(null, connector);
|
47 | }
|
48 | });
|
49 | };
|
50 |
|
51 | function TestConnector(settings) {
|
52 | SQLConnector.call(this, 'testdb', settings);
|
53 | this._tables = {};
|
54 | this.data = {};
|
55 | }
|
56 |
|
57 | util.inherits(TestConnector, SQLConnector);
|
58 |
|
59 | TestConnector.prototype.escapeName = function(name) {
|
60 | return '`' + name + '`';
|
61 | };
|
62 |
|
63 | TestConnector.prototype.dbName = function(name) {
|
64 | return name.toUpperCase();
|
65 | };
|
66 |
|
67 | TestConnector.prototype.getPlaceholderForValue = function(key) {
|
68 | return '$' + key;
|
69 | };
|
70 |
|
71 | TestConnector.prototype.escapeValue = function(value) {
|
72 | if (typeof value === 'number' || typeof value === 'boolean') {
|
73 | return value;
|
74 | }
|
75 | if (typeof value === 'string') {
|
76 | return "'" + value + "'";
|
77 | }
|
78 | if (value == null) {
|
79 | return 'NULL';
|
80 | }
|
81 | if (typeof value === 'object') {
|
82 | return String(value);
|
83 | }
|
84 | return value;
|
85 | };
|
86 |
|
87 | TestConnector.prototype.toColumnValue = function(prop, val) {
|
88 | return val;
|
89 | };
|
90 |
|
91 | TestConnector.prototype._buildLimit = function(model, limit, offset) {
|
92 | if (isNaN(limit)) {
|
93 | limit = 0;
|
94 | }
|
95 | if (isNaN(offset)) {
|
96 | offset = 0;
|
97 | }
|
98 | if (!limit && !offset) {
|
99 | return '';
|
100 | }
|
101 | return 'LIMIT ' + (offset ? (offset + ',' + limit) : limit);
|
102 | };
|
103 |
|
104 | TestConnector.prototype.applyPagination =
|
105 | function(model, stmt, filter) {
|
106 |
|
107 | const limitClause = this._buildLimit(model, filter.limit,
|
108 | filter.offset || filter.skip);
|
109 | return stmt.merge(limitClause);
|
110 | };
|
111 |
|
112 | TestConnector.prototype.escapeName = function(name) {
|
113 | return '`' + name + '`';
|
114 | };
|
115 |
|
116 | TestConnector.prototype.dbName = function(name) {
|
117 | return name.toUpperCase();
|
118 | };
|
119 |
|
120 | TestConnector.prototype.getPlaceholderForValue = function(key) {
|
121 | return '$' + key;
|
122 | };
|
123 |
|
124 | TestConnector.prototype.escapeValue = function(value) {
|
125 | if (typeof value === 'number' || typeof value === 'boolean') {
|
126 | return value;
|
127 | }
|
128 | if (typeof value === 'string') {
|
129 | return "'" + value + "'";
|
130 | }
|
131 | if (value == null) {
|
132 | return 'NULL';
|
133 | }
|
134 | if (typeof value === 'object') {
|
135 | return String(value);
|
136 | }
|
137 | return value;
|
138 | };
|
139 |
|
140 | TestConnector.prototype.toColumnValue = function(prop, val, escaping) {
|
141 | return escaping ? this.escapeValue(val) : val;
|
142 | };
|
143 |
|
144 | TestConnector.prototype._buildLimit = function(model, limit, offset) {
|
145 | if (isNaN(limit)) {
|
146 | limit = 0;
|
147 | }
|
148 | if (isNaN(offset)) {
|
149 | offset = 0;
|
150 | }
|
151 | if (!limit && !offset) {
|
152 | return '';
|
153 | }
|
154 | return 'LIMIT ' + (offset ? (offset + ',' + limit) : limit);
|
155 | };
|
156 |
|
157 | TestConnector.prototype.applyPagination =
|
158 | function(model, stmt, filter) {
|
159 |
|
160 | const limitClause = this._buildLimit(model, filter.limit,
|
161 | filter.offset || filter.skip);
|
162 | return stmt.merge(limitClause);
|
163 | };
|
164 |
|
165 | TestConnector.prototype.dropTable = function(model, cb) {
|
166 | let err;
|
167 | const exists = model in this._tables;
|
168 | if (!exists) {
|
169 | err = new Error('Model doesn\'t exist: ' + model);
|
170 | } else {
|
171 | delete this._tables[model];
|
172 | }
|
173 | process.nextTick(function() {
|
174 | cb(err);
|
175 | });
|
176 | };
|
177 |
|
178 | TestConnector.prototype.createTable = function(model, cb) {
|
179 | let err;
|
180 | const exists = model in this._tables;
|
181 | if (exists) {
|
182 | err = new Error('Model already exists: ' + model);
|
183 | } else {
|
184 | this._tables[model] = model;
|
185 | }
|
186 | process.nextTick(function() {
|
187 | cb(err);
|
188 | });
|
189 | };
|
190 |
|
191 | TestConnector.prototype.getInsertedId = function(model, info) {
|
192 | return info;
|
193 | };
|
194 |
|
195 | TestConnector.prototype.fromColumnValue = function(propertyDef, value) {
|
196 | return value;
|
197 | };
|
198 |
|
199 | TestConnector.prototype.beginTransaction = function(isolationLevel, cb) {
|
200 | const name = 'tx_' + transactionId++;
|
201 | cb(null, new MockTransaction(this, name));
|
202 | };
|
203 |
|
204 | TestConnector.prototype.commit = function(tx, cb) {
|
205 | tx.commit(cb);
|
206 | };
|
207 |
|
208 | TestConnector.prototype.rollback = function(tx, cb) {
|
209 | tx.rollback(cb);
|
210 | };
|
211 |
|
212 | TestConnector.prototype.executeSQL = function(sql, params, options, callback) {
|
213 | const transaction = options.transaction;
|
214 | const model = options.model;
|
215 | const useTransaction = transaction && transaction.connector === this &&
|
216 | transaction.connection;
|
217 | const data = useTransaction ? transaction.connection.data : this.data;
|
218 | const name = useTransaction ? transaction.connection.name : undefined;
|
219 | if (sql.indexOf('INSERT') === 0) {
|
220 | data[model] = data[model] || [];
|
221 | data[model].push({sql: sql, params: params});
|
222 | debug('INSERT', data, sql, name);
|
223 | callback(null, 1);
|
224 | } else {
|
225 | debug('SELECT', data, sql, name);
|
226 | const instances = data[model] || [];
|
227 | const result = sql.indexOf('count(*) as "cnt"') !== -1 ?
|
228 | [{cnt: instances.length}] : instances;
|
229 | callback(null, result);
|
230 | }
|
231 | };
|