Merge commit 'jesse/master'
[taboo.git] / chrome / content / sqlite.js
blob5c0b1b6b8a8ae3818c2a5dd398c8275957f089ee
1 /*
2  * Copyright 2007 Jesse Andrews, and CommerceNet
3  *
4  * This file may be used under the terms of of the
5  * GNU General Public License Version 2 or later (the "GPL"),
6  * http://www.gnu.org/licenses/gpl.html
7  *
8  * Software distributed under the License is distributed on an "AS IS" basis,
9  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10  * for the specific language governing rights and limitations under the
11  * License.
12  *
13  */
17  * DB: a better mozstorage wrapper (for some definition of better)
18  *
19  *
20  * Initializing the Database:
21  *
22  *   Creating an in-memory database:
23  *
24  *     var db = new DB();
25  *
26  *   Loading/Creating a file-based database:
27  *
28  *     var file = Cc['@mozilla.org/file/directory_service;1']
29  *                  .getService(Ci.nsIProperties).get('ProfD', Ci.nsILocalFile);
30  *     file.append('mydb.sqlite');
31  *
32  *     var db = new DB(file);
33  *
34  * Creating/Using a table:
35  *
36  *   Creating a table:
37  *
38  *     Pass in the name of the table, and the schema as a object.
39  *     Make sure to include the primary key explicitly.
40  *
41  *     db.Table('urls', {url: 'TEXT PRIMARY KEY',
42  *                       name: 'TEXT'});
43  *
44  *     You need to include the schema even if the table already exists.
45  *     It will not overwrite the existing schema, but is needed to construct
46  *     the javascript object versions of the table data.
47  *
48  *   Using a table:
49  *
50  *     Creating a new row:
51  *
52  *       var bookmark = db.urls.new();
53  *       bookmark.url = 'http://commerce.net';
54  *       bookmark.name = 'CommerceNet';
55  *
56  *       bookmark.save()
57  *
58  *     Finding an existing row:
59  *
60  *       db.urls.find('http://commerce.net');
61  *        // select * from urls where url = 'http://commerce.net'
62  *
63  *       db.urls.find('http://commerce.net').name
64  *        => 'CommerceNet'
65  *
66  *       db.urls.find({url: 'http://commerce.net'});
67  *        // select * from urls where url = 'http://commerce.net'
68  *
69  *       db.urls.find({url: 'http://commerce.net', name: 'CommerceNet'})
70  *        // select * from urls where url = 'http://commerce.net' and
71  *        // name = 'CommerceNet'
72  *
73  *     Updating a record:
74  *
75  *       var bookmark = db.urls.find('http://commerce.net');
76  *       bookmark.name = 'Commerce.Net';
77  *       bookmark.save();
78  *
79  *     Deleting a record:
80  *
81  *       var bookmark = db.urls.find('http://commerce.net')
82  *       bookmark.destroy();
83  *
84  */
86 function DB(dbFile) {
87   var self = this;
89   const Cc = Components.classes;
90   const Ci = Components.interfaces;
92   // Create the sqlite database
94   var storageService = Cc['@mozilla.org/storage/service;1']
95     .getService(Ci.mozIStorageService);
97   var conn = storageService.openDatabase(dbFile);
99   // convert sql into a convenience wrapper
101   function wrap_sql(query) {
102     var stmt = conn.createStatement(query);
104     var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
105       .createInstance(Ci.mozIStorageStatementWrapper);
107     wrapper.initialize(stmt);
108     return wrapper;
109   }
111   this.Table = function(table_name, schema) {
112     var _PK;
113     var _COLS = [k for (k in schema)];
115     for (var k in schema) {
116       if (schema[k].match(/PRIMARY KEY/i)) { _PK = k; }
117     }
119     try {
120      conn.createTable(table_name, _COLS.map(
121        function(col) {return col + ' ' + schema[col]}).join(', ')
122      );
123     } catch(e) {
124       // table already exists - hopefully
125     }
127     var sql_insert = wrap_sql(
128       'INSERT INTO ' + table_name + ' (' + _COLS.join(', ') + ')' +
129       ' VALUES (' + _COLS.map(function(x) { return ':' + x }).join(', ') + ')'
130     );
132     var sql_update = wrap_sql(
133       'UPDATE ' + table_name +
134       ' SET ' + _COLS.map(function(x) { return x + ' = :' + x }).join(', ') +
135       ' WHERE ' + _PK + ' = :' + _PK
136     );
138     var sql_destroy = wrap_sql(
139       'DELETE FROM ' + table_name + ' WHERE ' + _PK + ' = :' + _PK
140     );
142     function Record(row) {
143       var inst = this;
144       var new_record = true;
146       if (row) {
147         new_record = false;
148         for (var k in schema) {
149           this[k] = row[k];
150         }
151       }
153       this.toString = function() {
154         return inst[_PK] + ' (' + table_name + ')';
155       }
157       this.destroy = function() {
158         sql_destroy.params[_PK] = inst[_PK];
159         try {
160           if (sql_destroy.step()) {
161             sql_destroy.reset();
162             return true
163           }
164         } catch(e) { };
165         sql_destroy.reset();
166         return false;
167       }
169       this.save = function() {
170         var query = new_record ? sql_insert : sql_update;
171         _COLS.forEach(function(k) {
172           if (inst[k]) {
173             query.params[k] = inst[k];
174           }
175         }, inst);
176         try {
177           query.step();
178           new_record = false;
179           query.reset()
180           return true;
181         } catch (e) {
182           query.reset()
183           return false;
184         }
185       }
186     }
188     self[table_name] = {
189       new: function() {
190         return new Record();
191       },
192       find: function(conditions, order) {
194         // todo - cache the following
195         var sql = 'SELECT * from ' + table_name;
196         var params = {};
197         var return_one = false;
199         if (typeof(conditions) == 'string' || typeof(conditions) == 'number') {
200           sql += ' WHERE ' + _PK + ' = :' + _PK;
201           params[_PK] = conditions;
202           return_one = true;
203         }
205         if (conditions instanceof Array) {
206           // SECURITY HOLE: this is the incorrect way to do this...
207           // the params need escaped
209           var query = conditions[0];
210           for (var i=1; i<conditions.length; i++) {
211             query = query.replace(new RegExp('\\?'+i, 'g'), '"'+conditions[i]+'"')
212           }
213           sql += ' WHERE ' + query;
214         }
215         else if (typeof(conditions) == 'object') {
216           sql += ' WHERE ' +
217              [col + ' = :' + col for (col in conditions)].join(' and ');
218           for (var k in conditions) {
219             params[k] = conditions[k];
220           }
221         }
223         if (order)
224           sql += ' ORDER BY ' + order;
226         var select = wrap_sql(sql);
228         for (var k in params) {
229           select.params[k] = params[k];
230         }
232         var records = [];
234         while (select.step()) {
235           records.push(new Record(select.row));
236         }
238         select.reset();
240         if (return_one) {
241           return records[0];
242         }
243         return records;
244       },
245       toString: function() {
246         return 'Table: ' + table_name;
247       }
248     }
249   }