legacy-debug.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. /**
  2. * SQL proxy lets you store data in a SQL database.
  3. * The Sencha Touch SQL proxy outputs model data into an HTML5
  4. * local database using WebSQL.
  5. *
  6. * You can create a Store for the proxy, for example:
  7. *
  8. * Ext.require(["Ext.data.proxy.SQL"]);
  9. *
  10. * Ext.define("User", {
  11. * extend: "Ext.data.Model",
  12. * config: {
  13. * fields: [ "firstName", "lastName" ]
  14. * }
  15. * });
  16. *
  17. * Ext.create("Ext.data.Store", {
  18. * model: "User",
  19. * storeId: "Users",
  20. * proxy: {
  21. * type: "sql"
  22. * }
  23. * });
  24. *
  25. * Ext.getStore("Users").add({
  26. * firstName: "Polly",
  27. * lastName: "Hedra"
  28. * });
  29. *
  30. * Ext.getStore("Users").sync();
  31. *
  32. * To destroy a table use:
  33. *
  34. * Ext.getStore("Users").getProxy().dropTable();
  35. *
  36. * To recreate a table use:
  37. *
  38. * Ext.data.Store.sync() or Ext.data.Model.save()
  39. */
  40. Ext.define('Ext.data.proxy.Sql', {
  41. alias: 'proxy.sql',
  42. extend: 'Ext.data.proxy.Client',
  43. alternateClassName: 'Ext.data.proxy.SQL',
  44. isSQLProxy: true,
  45. config: {
  46. /**
  47. * @cfg {Object} reader
  48. * @hide
  49. */
  50. reader: null,
  51. /**
  52. * @cfg {Object} writer
  53. * @hide
  54. */
  55. writer: null,
  56. /**
  57. * @cfg {String} table
  58. * Optional Table name to use if not provided ModelName will be used
  59. */
  60. table: null,
  61. /**
  62. * @cfg {String} database
  63. * Database name to access tables from
  64. */
  65. database: 'Sencha'
  66. },
  67. _createOptions: {
  68. silent: true,
  69. dirty: false
  70. },
  71. updateModel: function(model) {
  72. var me = this,
  73. modelName, len, i, columns, quoted;
  74. if (model) {
  75. me.uniqueIdStrategy = model.identifier.isUnique;
  76. if (!me.getTable()) {
  77. modelName = model.entityName;
  78. me.setTable(modelName.slice(modelName.lastIndexOf('.') + 1));
  79. }
  80. me.columns = columns = me.getPersistedModelColumns(model);
  81. me.quotedColumns = quoted = [];
  82. for (i = 0 , len = columns.length; i < len; ++i) {
  83. quoted.push('"' + columns[i] + '"');
  84. }
  85. }
  86. me.callParent([
  87. model
  88. ]);
  89. },
  90. setException: function(operation, error) {
  91. operation.setException(error);
  92. },
  93. create: function(operation) {
  94. var me = this,
  95. records = operation.getRecords(),
  96. result, error;
  97. operation.setStarted();
  98. me.executeTransaction(function(transaction) {
  99. me.insertRecords(records, transaction, function(resultSet, statementError) {
  100. result = resultSet;
  101. error = statementError;
  102. });
  103. }, function(transactionError) {
  104. operation.setException(transactionError);
  105. }, function() {
  106. if (error) {
  107. operation.setException(statementError);
  108. } else {
  109. operation.process(result);
  110. }
  111. });
  112. },
  113. read: function(operation) {
  114. var me = this,
  115. model = me.getModel(),
  116. records = operation.getRecords(),
  117. record = records ? records[0] : null,
  118. result, error, id, params;
  119. if (record && !record.phantom) {
  120. id = record.getId();
  121. } else {
  122. id = operation.getId();
  123. }
  124. if (id !== undefined) {
  125. params = {
  126. idOnly: true,
  127. id: id
  128. };
  129. } else {
  130. params = {
  131. page: operation.getPage(),
  132. start: operation.getStart(),
  133. limit: operation.getLimit(),
  134. sorters: operation.getSorters(),
  135. filters: operation.getFilters()
  136. };
  137. }
  138. operation.setStarted();
  139. me.executeTransaction(function(transaction) {
  140. me.selectRecords(transaction, params, function(resultSet, statementError) {
  141. result = resultSet;
  142. error = statementError;
  143. });
  144. }, function(transactionError) {
  145. operation.setException(transactionError);
  146. }, function() {
  147. if (error) {
  148. operation.setException(statementError);
  149. } else {
  150. operation.process(result);
  151. }
  152. });
  153. },
  154. update: function(operation) {
  155. var me = this,
  156. records = operation.getRecords(),
  157. result, error;
  158. operation.setStarted();
  159. me.executeTransaction(function(transaction) {
  160. me.updateRecords(transaction, records, function(resultSet, statementError) {
  161. result = resultSet;
  162. error = statementError;
  163. });
  164. }, function(transactionError) {
  165. operation.setException(transactionError);
  166. }, function() {
  167. if (error) {
  168. operation.setException(statementError);
  169. } else {
  170. operation.process(result);
  171. }
  172. });
  173. },
  174. erase: function(operation) {
  175. var me = this,
  176. records = operation.getRecords(),
  177. result, error;
  178. operation.setStarted();
  179. me.executeTransaction(function(transaction) {
  180. me.destroyRecords(transaction, records, function(resultSet, statementError) {
  181. result = resultSet;
  182. error = statementError;
  183. });
  184. }, function(transactionError) {
  185. operation.setException(transactionError);
  186. }, function() {
  187. if (error) {
  188. operation.setException(error);
  189. } else {
  190. operation.process(result);
  191. }
  192. });
  193. },
  194. createTable: function(transaction) {
  195. var me = this;
  196. if (!transaction) {
  197. me.executeTransaction(function(transaction) {
  198. me.createTable(transaction);
  199. });
  200. return;
  201. }
  202. me.executeStatement(transaction, 'CREATE TABLE IF NOT EXISTS "' + me.getTable() + '" (' + me.getSchemaString() + ')', function() {
  203. me.tableExists = true;
  204. });
  205. },
  206. insertRecords: function(records, transaction, callback) {
  207. var me = this,
  208. columns = me.columns,
  209. totalRecords = records.length,
  210. executed = 0,
  211. uniqueIdStrategy = me.uniqueIdStrategy,
  212. setOptions = me._createOptions,
  213. len = records.length,
  214. i, record, placeholders, sql, data, values, errors, completeIf;
  215. completeIf = function(transaction) {
  216. ++executed;
  217. if (executed === totalRecords) {
  218. callback.call(me, new Ext.data.ResultSet({
  219. success: !errors
  220. }), errors);
  221. }
  222. };
  223. placeholders = Ext.String.repeat('?', columns.length, ',');
  224. sql = 'INSERT INTO "' + me.getTable() + '" (' + me.quotedColumns.join(',') + ') VALUES (' + placeholders + ')';
  225. for (i = 0; i < len; ++i) {
  226. record = records[i];
  227. data = me.getRecordData(record);
  228. values = me.getColumnValues(columns, data);
  229. // Capture the record in closure scope so we can access it later
  230. (function(record) {
  231. me.executeStatement(transaction, sql, values, function(transaction, resultSet) {
  232. if (!uniqueIdStrategy) {
  233. record.setId(resultSet.insertId, setOptions);
  234. }
  235. completeIf();
  236. }, function(transaction, error) {
  237. if (!errors) {
  238. errors = [];
  239. }
  240. errors.push(error);
  241. completeIf();
  242. });
  243. })(record);
  244. }
  245. },
  246. selectRecords: function(transaction, params, callback, scope) {
  247. var me = this,
  248. Model = me.getModel(),
  249. idProperty = Model.idProperty,
  250. sql = 'SELECT * FROM "' + me.getTable() + '"',
  251. filterStatement = ' WHERE ',
  252. sortStatement = ' ORDER BY ',
  253. values = [],
  254. sorters, filters, placeholder, i, len, result, filter, sorter, property, operator, value;
  255. if (params.idOnly) {
  256. sql += filterStatement + '"' + idProperty + '" = ?';
  257. values.push(params);
  258. } else {
  259. filters = params.filters;
  260. len = filters && filters.length;
  261. if (len) {
  262. for (i = 0; i < len; i++) {
  263. filter = filters[i];
  264. property = filter.getProperty();
  265. value = me.toSqlValue(filter.getValue(), Model.getField(property));
  266. operator = filter.getOperator();
  267. if (property !== null) {
  268. operator = operator || '=';
  269. placeholder = '?';
  270. if (operator === 'like' || (operator === '=' && filter.getAnyMatch())) {
  271. operator = 'LIKE';
  272. value = '%' + value + '%';
  273. }
  274. if (operator === 'in' || operator === 'notin') {
  275. if (operator === 'notin') {
  276. operator = 'not in';
  277. }
  278. placeholder = '(' + Ext.String.repeat('?', value.length, ',') + ')';
  279. values = values.concat(value);
  280. } else {
  281. values.push(value);
  282. }
  283. sql += filterStatement + '"' + property + '" ' + operator + ' ' + placeholder;
  284. filterStatement = ' AND ';
  285. }
  286. }
  287. }
  288. sorters = params.sorters;
  289. len = sorters && sorters.length;
  290. if (len) {
  291. for (i = 0; i < len; i++) {
  292. sorter = sorters[i];
  293. property = sorter.getProperty();
  294. if (property !== null) {
  295. sql += sortStatement + '"' + property + '" ' + sorter.getDirection();
  296. sortStatement = ', ';
  297. }
  298. }
  299. }
  300. // handle start, limit, sort, filter and group params
  301. if (params.page !== undefined) {
  302. sql += ' LIMIT ' + parseInt(params.start, 10) + ', ' + parseInt(params.limit, 10);
  303. }
  304. }
  305. me.executeStatement(transaction, sql, values, function(transaction, resultSet) {
  306. var rows = resultSet.rows,
  307. count = rows.length,
  308. records = [],
  309. fields = Model.fields,
  310. fieldsLen = fields.length,
  311. raw, data, i, len, j, field, name;
  312. for (i = 0 , len = count; i < len; ++i) {
  313. raw = rows.item(i);
  314. data = {};
  315. for (j = 0; j < fieldsLen; ++j) {
  316. field = fields[j];
  317. name = field.name;
  318. data[name] = me.fromSqlValue(raw[name], field);
  319. }
  320. records.push(new Model(data));
  321. }
  322. callback.call(me, new Ext.data.ResultSet({
  323. records: records,
  324. success: true,
  325. total: count,
  326. count: count
  327. }));
  328. }, function(transaction, error) {
  329. callback.call(me, new Ext.data.ResultSet({
  330. success: false,
  331. total: 0,
  332. count: 0
  333. }), error);
  334. });
  335. },
  336. updateRecords: function(transaction, records, callback) {
  337. var me = this,
  338. columns = me.columns,
  339. quotedColumns = me.quotedColumns,
  340. totalRecords = records.length,
  341. executed = 0,
  342. updates = [],
  343. setOptions = me._createOptions,
  344. len, i, record, placeholders, sql, data, values, errors, completeIf;
  345. completeIf = function(transaction) {
  346. ++executed;
  347. if (executed === totalRecords) {
  348. callback.call(me, new Ext.data.ResultSet({
  349. success: !errors
  350. }), errors);
  351. }
  352. };
  353. for (i = 0 , len = quotedColumns.length; i < len; i++) {
  354. updates.push(quotedColumns[i] + ' = ?');
  355. }
  356. sql = 'UPDATE "' + me.getTable() + '" SET ' + updates.join(', ') + ' WHERE "' + me.getModel().idProperty + '" = ?';
  357. for (i = 0 , len = records.length; i < len; ++i) {
  358. record = records[i];
  359. data = me.getRecordData(record);
  360. values = me.getColumnValues(columns, data);
  361. values.push(record.getId());
  362. // Capture the record in closure scope so we can access it later
  363. (function(record) {
  364. me.executeStatement(transaction, sql, values, function(transaction, resultSet) {
  365. completeIf();
  366. }, function(transaction, error) {
  367. if (!errors) {
  368. errors = [];
  369. }
  370. errors.push(error);
  371. completeIf();
  372. });
  373. })(record);
  374. }
  375. },
  376. destroyRecords: function(transaction, records, callback) {
  377. var me = this,
  378. table = me.getTable(),
  379. idProperty = me.getModel().idProperty,
  380. ids = [],
  381. values = [],
  382. destroyedRecords = [],
  383. len = records.length,
  384. idStr = '"' + idProperty + '" = ?',
  385. i, result, record, sql;
  386. for (i = 0; i < len; i++) {
  387. ids.push(idStr);
  388. values.push(records[i].getId());
  389. }
  390. sql = 'DELETE FROM "' + me.getTable() + '" WHERE ' + ids.join(' OR ');
  391. me.executeStatement(transaction, sql, values, function(transaction, resultSet) {
  392. callback.call(me, new Ext.data.ResultSet({
  393. success: true
  394. }));
  395. }, function(transaction, error) {
  396. callback.call(me, new Ext.data.ResultSet({
  397. success: false
  398. }), error);
  399. });
  400. },
  401. /**
  402. * Formats the data for each record before sending it to the server. This
  403. * method should be overridden to format the data in a way that differs from the default.
  404. * @param {Object} record The record that we are writing to the server.
  405. * @return {Object} An object literal of name/value keys to be written to the server.
  406. * By default this method returns the data property on the record.
  407. */
  408. getRecordData: function(record) {
  409. var me = this,
  410. fields = record.fields,
  411. idProperty = record.idProperty,
  412. uniqueIdStrategy = me.uniqueIdStrategy,
  413. data = {},
  414. len = fields.length,
  415. recordData = record.data,
  416. i, name, value, field;
  417. for (i = 0; i < len; ++i) {
  418. field = fields[i];
  419. if (field.persist !== false) {
  420. name = field.name;
  421. if (name === idProperty && !uniqueIdStrategy) {
  422. continue;
  423. }
  424. data[name] = me.toSqlValue(recordData[name], field);
  425. }
  426. }
  427. return data;
  428. },
  429. getColumnValues: function(columns, data) {
  430. var len = columns.length,
  431. values = [],
  432. i, column, value;
  433. for (i = 0; i < len; i++) {
  434. column = columns[i];
  435. value = data[column];
  436. if (value !== undefined) {
  437. values.push(value);
  438. }
  439. }
  440. return values;
  441. },
  442. getSchemaString: function() {
  443. var me = this,
  444. schema = [],
  445. model = me.getModel(),
  446. idProperty = model.idProperty,
  447. fields = model.fields,
  448. uniqueIdStrategy = me.uniqueIdStrategy,
  449. len = fields.length,
  450. i, field, type, name;
  451. for (i = 0; i < len; i++) {
  452. field = fields[i];
  453. type = field.getType();
  454. name = field.name;
  455. if (name === idProperty) {
  456. if (uniqueIdStrategy) {
  457. type = me.convertToSqlType(type);
  458. schema.unshift('"' + idProperty + '" ' + type);
  459. } else {
  460. schema.unshift('"' + idProperty + '" INTEGER PRIMARY KEY AUTOINCREMENT');
  461. }
  462. } else {
  463. type = me.convertToSqlType(type);
  464. schema.push('"' + name + '" ' + type);
  465. }
  466. }
  467. return schema.join(', ');
  468. },
  469. convertToSqlType: function(type) {
  470. switch (type.toLowerCase()) {
  471. case 'string':
  472. case 'auto':
  473. return 'TEXT';
  474. case 'int':
  475. case 'date':
  476. return 'INTEGER';
  477. case 'float':
  478. return 'REAL';
  479. case 'bool':
  480. return 'NUMERIC';
  481. }
  482. },
  483. dropTable: function() {
  484. var me = this;
  485. me.executeTransaction(function(transaction) {
  486. me.executeStatement(transaction, 'DROP TABLE "' + me.getTable() + '"', function() {
  487. me.tableExists = false;
  488. });
  489. }, null, null, false);
  490. },
  491. getDatabaseObject: function() {
  492. return window.openDatabase(this.getDatabase(), '1.0', 'Sencha Database', 5 * 1024 * 1024);
  493. },
  494. privates: {
  495. executeStatement: function(transaction, sql, values, success, failure) {
  496. var me = this;
  497. transaction.executeSql(sql, values, success ? function() {
  498. success.apply(me, arguments);
  499. } : null, failure ? function() {
  500. failure.apply(me, arguments);
  501. } : null);
  502. },
  503. executeTransaction: function(runner, failure, success, autoCreateTable) {
  504. var me = this;
  505. autoCreateTable = autoCreateTable !== false;
  506. me.getDatabaseObject().transaction(runner ? function(transaction) {
  507. if (autoCreateTable && !me.tableExists) {
  508. me.createTable(transaction);
  509. }
  510. runner.apply(me, arguments);
  511. } : null, failure ? function() {
  512. failure.apply(me, arguments);
  513. } : null, success ? function() {
  514. success.apply(me, arguments);
  515. } : null);
  516. },
  517. fromSqlValue: function(value, field) {
  518. if (field.isDateField) {
  519. value = value ? new Date(value) : null;
  520. } else if (field.isBooleanField) {
  521. value = value === 1;
  522. }
  523. return value;
  524. },
  525. getPersistedModelColumns: function(model) {
  526. var fields = model.fields,
  527. uniqueIdStrategy = this.uniqueIdStrategy,
  528. idProperty = model.idProperty,
  529. columns = [],
  530. len = fields.length,
  531. i, field, name;
  532. for (i = 0; i < len; ++i) {
  533. field = fields[i];
  534. name = field.name;
  535. if (name === idProperty && !uniqueIdStrategy) {
  536. continue;
  537. }
  538. if (field.persist !== false) {
  539. columns.push(field.name);
  540. }
  541. }
  542. return columns;
  543. },
  544. toSqlValue: function(value, field) {
  545. if (field.isDateField) {
  546. value = value ? value.getTime() : null;
  547. } else if (field.isBooleanField) {
  548. value = value ? 1 : 0;
  549. }
  550. return value;
  551. }
  552. }
  553. });