Bumping manifests a=b2g-bump
[gecko.git] / services / metrics / storage.jsm
blobf4660aaeedb6c2bc3d79fb9f3b35c7586d717392
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #ifndef MERGED_COMPARTMENT
7 "use strict";
9 this.EXPORTED_SYMBOLS = [
10   "DailyValues",
11   "MetricsStorageBackend",
12   "dateToDays",
13   "daysToDate",
16 const {utils: Cu} = Components;
18 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
20 #endif
22 Cu.import("resource://gre/modules/Promise.jsm");
23 Cu.import("resource://gre/modules/Sqlite.jsm");
24 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
25 Cu.import("resource://gre/modules/Task.jsm");
26 Cu.import("resource://gre/modules/Log.jsm");
27 Cu.import("resource://services-common/utils.js");
30 // These do not account for leap seconds. Meh.
31 function dateToDays(date) {
32   return Math.floor(date.getTime() / MILLISECONDS_PER_DAY);
35 function daysToDate(days) {
36   return new Date(days * MILLISECONDS_PER_DAY);
39 /**
40  * Represents a collection of per-day values.
41  *
42  * This is a proxy around a Map which can transparently round Date instances to
43  * their appropriate key.
44  *
45  * This emulates Map by providing .size and iterator support. Note that keys
46  * from the iterator are Date instances corresponding to midnight of the start
47  * of the day. get(), has(), and set() are modeled as getDay(), hasDay(), and
48  * setDay(), respectively.
49  *
50  * All days are defined in terms of UTC (as opposed to local time).
51  */
52 this.DailyValues = function () {
53   this._days = new Map();
56 DailyValues.prototype = Object.freeze({
57   __iterator__: function () {
58     for (let [k, v] of this._days) {
59       yield [daysToDate(k), v];
60     }
61   },
63   get size() {
64     return this._days.size;
65   },
67   hasDay: function (date) {
68     return this._days.has(dateToDays(date));
69   },
71   getDay: function (date) {
72     return this._days.get(dateToDays(date));
73   },
75   setDay: function (date, value) {
76     this._days.set(dateToDays(date), value);
77   },
79   appendValue: function (date, value) {
80     let key = dateToDays(date);
82     if (this._days.has(key)) {
83       return this._days.get(key).push(value);
84     }
86     this._days.set(key, [value]);
87   },
88 });
91 /**
92  * DATABASE INFO
93  * =============
94  *
95  * We use a SQLite database as the backend for persistent storage of metrics
96  * data.
97  *
98  * Every piece of recorded data is associated with a measurement. A measurement
99  * is an entity with a name and version. Each measurement is associated with a
100  * named provider.
102  * When the metrics system is initialized, we ask providers (the entities that
103  * emit data) to configure the database for storage of their data. They tell
104  * storage what their requirements are. For example, they'll register
105  * named daily counters associated with specific measurements.
107  * Recorded data is stored differently depending on the requirements for
108  * storing it. We have facilities for storing the following classes of data:
110  *  1) Counts of event/field occurrences aggregated by day.
111  *  2) Discrete values of fields aggregated by day.
112  *  3) Discrete values of fields aggregated by day max 1 per day (last write
113  *     wins).
114  *  4) Discrete values of fields max 1 (last write wins).
116  * Most data is aggregated per day mainly for privacy reasons. This does throw
117  * away potentially useful data. But, it's not currently used, so there is no
118  * need to keep the granular information.
120  * Database Schema
121  * ---------------
123  * This database contains the following tables:
125  *   providers -- Maps provider string name to an internal ID.
126  *   provider_state -- Holds opaque persisted state for providers.
127  *   measurements -- Holds the set of known measurements (name, version,
128  *     provider tuples).
129  *   types -- The data types that can be stored in measurements/fields.
130  *   fields -- Describes entities that occur within measurements.
131  *   daily_counters -- Holds daily-aggregated counts of events. Each row is
132  *     associated with a field and a day.
133  *   daily_discrete_numeric -- Holds numeric values for fields grouped by day.
134  *     Each row contains a discrete value associated with a field that occurred
135  *     on a specific day. There can be multiple rows per field per day.
136  *   daily_discrete_text -- Holds text values for fields grouped by day. Each
137  *     row contains a discrete value associated with a field that occurred on a
138  *     specific day.
139  *   daily_last_numeric -- Holds numeric values where the last encountered
140  *     value for a given day is retained.
141  *   daily_last_text -- Like daily_last_numeric except for text values.
142  *   last_numeric -- Holds the most recent value for a numeric field.
143  *   last_text -- Like last_numeric except for text fields.
145  * Notes
146  * -----
148  * It is tempting to use SQLite's julianday() function to store days that
149  * things happened. However, a Julian Day begins at *noon* in 4714 B.C. This
150  * results in weird half day offsets from UNIX time. So, we instead store
151  * number of days since UNIX epoch, not Julian.
152  */
155  * All of our SQL statements are stored in a central mapping so they can easily
156  * be audited for security, perf, etc.
157  */
158 const SQL = {
159   // Create the providers table.
160   createProvidersTable: "\
161 CREATE TABLE providers (\
162   id INTEGER PRIMARY KEY AUTOINCREMENT, \
163   name TEXT, \
164   UNIQUE (name) \
167   createProviderStateTable: "\
168 CREATE TABLE provider_state (\
169   id INTEGER PRIMARY KEY AUTOINCREMENT, \
170   provider_id INTEGER, \
171   name TEXT, \
172   VALUE TEXT, \
173   UNIQUE (provider_id, name), \
174   FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE\
177   createProviderStateProviderIndex: "\
178 CREATE INDEX i_provider_state_provider_id ON provider_state (provider_id)",
180   createMeasurementsTable: "\
181 CREATE TABLE measurements (\
182   id INTEGER PRIMARY KEY AUTOINCREMENT, \
183   provider_id INTEGER, \
184   name TEXT, \
185   version INTEGER, \
186   UNIQUE (provider_id, name, version), \
187   FOREIGN KEY (provider_id) REFERENCES providers(id) ON DELETE CASCADE\
190   createMeasurementsProviderIndex: "\
191 CREATE INDEX i_measurements_provider_id ON measurements (provider_id)",
193   createMeasurementsView: "\
194 CREATE VIEW v_measurements AS \
195   SELECT \
196     providers.id AS provider_id, \
197     providers.name AS provider_name, \
198     measurements.id AS measurement_id, \
199     measurements.name AS measurement_name, \
200     measurements.version AS measurement_version \
201   FROM providers, measurements \
202   WHERE \
203     measurements.provider_id = providers.id",
205   createTypesTable: "\
206 CREATE TABLE types (\
207   id INTEGER PRIMARY KEY AUTOINCREMENT, \
208   name TEXT, \
209   UNIQUE (name)\
212   createFieldsTable: "\
213 CREATE TABLE fields (\
214   id INTEGER PRIMARY KEY AUTOINCREMENT, \
215   measurement_id INTEGER, \
216   name TEXT, \
217   value_type INTEGER , \
218   UNIQUE (measurement_id, name), \
219   FOREIGN KEY (measurement_id) REFERENCES measurements(id) ON DELETE CASCADE \
220   FOREIGN KEY (value_type) REFERENCES types(id) ON DELETE CASCADE \
223   createFieldsMeasurementIndex: "\
224 CREATE INDEX i_fields_measurement_id ON fields (measurement_id)",
226   createFieldsView: "\
227 CREATE VIEW v_fields AS \
228   SELECT \
229     providers.id AS provider_id, \
230     providers.name AS provider_name, \
231     measurements.id AS measurement_id, \
232     measurements.name AS measurement_name, \
233     measurements.version AS measurement_version, \
234     fields.id AS field_id, \
235     fields.name AS field_name, \
236     types.id AS type_id, \
237     types.name AS type_name \
238   FROM providers, measurements, fields, types \
239   WHERE \
240     fields.measurement_id = measurements.id \
241     AND measurements.provider_id = providers.id \
242     AND fields.value_type = types.id",
244   createDailyCountersTable: "\
245 CREATE TABLE daily_counters (\
246   field_id INTEGER, \
247   day INTEGER, \
248   value INTEGER, \
249   UNIQUE(field_id, day), \
250   FOREIGN KEY (field_id) REFERENCES fields(id) ON DELETE CASCADE\
253   createDailyCountersFieldIndex: "\
254 CREATE INDEX i_daily_counters_field_id ON daily_counters (field_id)",
256   createDailyCountersDayIndex: "\
257 CREATE INDEX i_daily_counters_day ON daily_counters (day)",
259   createDailyCountersView: "\
260 CREATE VIEW v_daily_counters AS SELECT \
261   providers.id AS provider_id, \
262   providers.name AS provider_name, \
263   measurements.id AS measurement_id, \
264   measurements.name AS measurement_name, \
265   measurements.version AS measurement_version, \
266   fields.id AS field_id, \
267   fields.name AS field_name, \
268   daily_counters.day AS day, \
269   daily_counters.value AS value \
270 FROM providers, measurements, fields, daily_counters \
271 WHERE \
272   daily_counters.field_id = fields.id \
273   AND fields.measurement_id = measurements.id \
274   AND measurements.provider_id = providers.id",
276   createDailyDiscreteNumericsTable: "\
277 CREATE TABLE daily_discrete_numeric (\
278   id INTEGER PRIMARY KEY AUTOINCREMENT, \
279   field_id INTEGER, \
280   day INTEGER, \
281   value INTEGER, \
282   FOREIGN KEY (field_id) REFERENCES fields(id) ON DELETE CASCADE\
285   createDailyDiscreteNumericsFieldIndex: "\
286 CREATE INDEX i_daily_discrete_numeric_field_id \
287 ON daily_discrete_numeric (field_id)",
289   createDailyDiscreteNumericsDayIndex: "\
290 CREATE INDEX i_daily_discrete_numeric_day \
291 ON daily_discrete_numeric (day)",
293   createDailyDiscreteTextTable: "\
294 CREATE TABLE daily_discrete_text (\
295   id INTEGER PRIMARY KEY AUTOINCREMENT, \
296   field_id INTEGER, \
297   day INTEGER, \
298   value TEXT, \
299   FOREIGN KEY (field_id) REFERENCES fields(id) ON DELETE CASCADE\
302   createDailyDiscreteTextFieldIndex: "\
303 CREATE INDEX i_daily_discrete_text_field_id \
304 ON daily_discrete_text (field_id)",
306   createDailyDiscreteTextDayIndex: "\
307 CREATE INDEX i_daily_discrete_text_day \
308 ON daily_discrete_text (day)",
310   createDailyDiscreteView: "\
311 CREATE VIEW v_daily_discrete AS \
312   SELECT \
313     providers.id AS provider_id, \
314     providers.name AS provider_name, \
315     measurements.id AS measurement_id, \
316     measurements.name AS measurement_name, \
317     measurements.version AS measurement_version, \
318     fields.id AS field_id, \
319     fields.name AS field_name, \
320     daily_discrete_numeric.id AS value_id, \
321     daily_discrete_numeric.day AS day, \
322     daily_discrete_numeric.value AS value, \
323     \"numeric\" AS value_type \
324     FROM providers, measurements, fields, daily_discrete_numeric \
325     WHERE \
326       daily_discrete_numeric.field_id = fields.id \
327       AND fields.measurement_id = measurements.id \
328       AND measurements.provider_id = providers.id \
329   UNION ALL \
330   SELECT \
331     providers.id AS provider_id, \
332     providers.name AS provider_name, \
333     measurements.id AS measurement_id, \
334     measurements.name AS measurement_name, \
335     measurements.version AS measurement_version, \
336     fields.id AS field_id, \
337     fields.name AS field_name, \
338     daily_discrete_text.id AS value_id, \
339     daily_discrete_text.day AS day, \
340     daily_discrete_text.value AS value, \
341     \"text\" AS value_type \
342     FROM providers, measurements, fields, daily_discrete_text \
343     WHERE \
344       daily_discrete_text.field_id = fields.id \
345       AND fields.measurement_id = measurements.id \
346       AND measurements.provider_id = providers.id \
347   ORDER BY day ASC, value_id ASC",
349   createLastNumericTable: "\
350 CREATE TABLE last_numeric (\
351   field_id INTEGER PRIMARY KEY, \
352   day INTEGER, \
353   value NUMERIC, \
354   FOREIGN KEY (field_id) REFERENCES fields(id) ON DELETE CASCADE\
357   createLastTextTable: "\
358 CREATE TABLE last_text (\
359   field_id INTEGER PRIMARY KEY, \
360   day INTEGER, \
361   value TEXT, \
362   FOREIGN KEY (field_id) REFERENCES fields(id) ON DELETE CASCADE\
365   createLastView: "\
366 CREATE VIEW v_last AS \
367   SELECT \
368     providers.id AS provider_id, \
369     providers.name AS provider_name, \
370     measurements.id AS measurement_id, \
371     measurements.name AS measurement_name, \
372     measurements.version AS measurement_version, \
373     fields.id AS field_id, \
374     fields.name AS field_name, \
375     last_numeric.day AS day, \
376     last_numeric.value AS value, \
377     \"numeric\" AS value_type \
378     FROM providers, measurements, fields, last_numeric \
379     WHERE \
380       last_numeric.field_id = fields.id \
381       AND fields.measurement_id = measurements.id \
382       AND measurements.provider_id = providers.id \
383   UNION ALL \
384   SELECT \
385     providers.id AS provider_id, \
386     providers.name AS provider_name, \
387     measurements.id AS measurement_id, \
388     measurements.name AS measurement_name, \
389     measurements.version AS measurement_version, \
390     fields.id AS field_id, \
391     fields.name AS field_name, \
392     last_text.day AS day, \
393     last_text.value AS value, \
394     \"text\" AS value_type \
395     FROM providers, measurements, fields, last_text \
396     WHERE \
397       last_text.field_id = fields.id \
398       AND fields.measurement_id = measurements.id \
399       AND measurements.provider_id = providers.id",
401   createDailyLastNumericTable: "\
402 CREATE TABLE daily_last_numeric (\
403   field_id INTEGER, \
404   day INTEGER, \
405   value NUMERIC, \
406   UNIQUE (field_id, day) \
407   FOREIGN KEY (field_id) REFERENCES fields(id) ON DELETE CASCADE\
410   createDailyLastNumericFieldIndex: "\
411 CREATE INDEX i_daily_last_numeric_field_id ON daily_last_numeric (field_id)",
413   createDailyLastNumericDayIndex: "\
414 CREATE INDEX i_daily_last_numeric_day ON daily_last_numeric (day)",
416   createDailyLastTextTable: "\
417 CREATE TABLE daily_last_text (\
418   field_id INTEGER, \
419   day INTEGER, \
420   value TEXT, \
421   UNIQUE (field_id, day) \
422   FOREIGN KEY (field_id) REFERENCES fields(id) ON DELETE CASCADE\
425   createDailyLastTextFieldIndex: "\
426 CREATE INDEX i_daily_last_text_field_id ON daily_last_text (field_id)",
428   createDailyLastTextDayIndex: "\
429 CREATE INDEX i_daily_last_text_day ON daily_last_text (day)",
431   createDailyLastView: "\
432 CREATE VIEW v_daily_last AS \
433   SELECT \
434     providers.id AS provider_id, \
435     providers.name AS provider_name, \
436     measurements.id AS measurement_id, \
437     measurements.name AS measurement_name, \
438     measurements.version AS measurement_version, \
439     fields.id AS field_id, \
440     fields.name AS field_name, \
441     daily_last_numeric.day AS day, \
442     daily_last_numeric.value AS value, \
443     \"numeric\" as value_type \
444     FROM providers, measurements, fields, daily_last_numeric \
445     WHERE \
446       daily_last_numeric.field_id = fields.id \
447       AND fields.measurement_id = measurements.id \
448       AND measurements.provider_id = providers.id \
449   UNION ALL \
450   SELECT \
451     providers.id AS provider_id, \
452     providers.name AS provider_name, \
453     measurements.id AS measurement_id, \
454     measurements.name AS measurement_name, \
455     measurements.version AS measurement_version, \
456     fields.id AS field_id, \
457     fields.name AS field_name, \
458     daily_last_text.day AS day, \
459     daily_last_text.value AS value, \
460     \"text\" as value_type \
461     FROM providers, measurements, fields, daily_last_text \
462     WHERE \
463       daily_last_text.field_id = fields.id \
464       AND fields.measurement_id = measurements.id \
465       AND measurements.provider_id = providers.id",
467   // Mutation.
469   addProvider: "INSERT INTO providers (name) VALUES (:provider)",
471   setProviderState: "\
472 INSERT OR REPLACE INTO provider_state \
473   (provider_id, name, value) \
474   VALUES (:provider_id, :name, :value)",
476   addMeasurement: "\
477 INSERT INTO measurements (provider_id, name, version) \
478   VALUES (:provider_id, :measurement, :version)",
480   addType: "INSERT INTO types (name) VALUES (:name)",
482   addField: "\
483 INSERT INTO fields (measurement_id, name, value_type) \
484   VALUES (:measurement_id, :field, :value_type)",
486   incrementDailyCounterFromFieldID: "\
487 INSERT OR REPLACE INTO daily_counters VALUES (\
488   :field_id, \
489   :days, \
490   COALESCE(\
491     (SELECT value FROM daily_counters WHERE \
492       field_id = :field_id AND day = :days \
493     ), \
494     0\
495   ) + :by)",
497   deleteLastNumericFromFieldID: "\
498 DELETE FROM last_numeric WHERE field_id = :field_id",
500   deleteLastTextFromFieldID: "\
501 DELETE FROM last_text WHERE field_id = :field_id",
503   setLastNumeric: "\
504 INSERT OR REPLACE INTO last_numeric VALUES (:field_id, :days, :value)",
506   setLastText: "\
507 INSERT OR REPLACE INTO last_text VALUES (:field_id, :days, :value)",
509   setDailyLastNumeric: "\
510 INSERT OR REPLACE INTO daily_last_numeric VALUES (:field_id, :days, :value)",
512   setDailyLastText: "\
513 INSERT OR REPLACE INTO daily_last_text VALUES (:field_id, :days, :value)",
515   addDailyDiscreteNumeric: "\
516 INSERT INTO daily_discrete_numeric \
517 (field_id, day, value) VALUES (:field_id, :days, :value)",
519   addDailyDiscreteText: "\
520 INSERT INTO daily_discrete_text \
521 (field_id, day, value) VALUES (:field_id, :days, :value)",
523   pruneOldDailyCounters: "DELETE FROM daily_counters WHERE day < :days",
524   pruneOldDailyDiscreteNumeric: "DELETE FROM daily_discrete_numeric WHERE day < :days",
525   pruneOldDailyDiscreteText: "DELETE FROM daily_discrete_text WHERE day < :days",
526   pruneOldDailyLastNumeric: "DELETE FROM daily_last_numeric WHERE day < :days",
527   pruneOldDailyLastText: "DELETE FROM daily_last_text WHERE day < :days",
528   pruneOldLastNumeric: "DELETE FROM last_numeric WHERE day < :days",
529   pruneOldLastText: "DELETE FROM last_text WHERE day < :days",
531   // Retrieval.
533   getProviderID: "SELECT id FROM providers WHERE name = :provider",
535   getProviders: "SELECT id, name FROM providers",
537   getProviderStateWithName: "\
538 SELECT value FROM provider_state \
539   WHERE provider_id = :provider_id \
540   AND name = :name",
542   getMeasurements: "SELECT * FROM v_measurements",
544   getMeasurementID: "\
545 SELECT id FROM measurements \
546   WHERE provider_id = :provider_id \
547     AND name = :measurement \
548     AND version = :version",
550   getFieldID: "\
551 SELECT id FROM fields \
552   WHERE measurement_id = :measurement_id \
553     AND name = :field \
554     AND value_type = :value_type \
557   getTypes: "SELECT * FROM types",
559   getTypeID: "SELECT id FROM types WHERE name = :name",
561   getDailyCounterCountsFromFieldID: "\
562 SELECT day, value FROM daily_counters \
563   WHERE field_id = :field_id \
564   ORDER BY day ASC",
566   getDailyCounterCountFromFieldID: "\
567 SELECT value FROM daily_counters \
568   WHERE field_id = :field_id \
569     AND day = :days",
571   getMeasurementDailyCounters: "\
572 SELECT field_name, day, value FROM v_daily_counters \
573 WHERE measurement_id = :measurement_id",
575   getFieldInfo: "SELECT * FROM v_fields",
577   getLastNumericFromFieldID: "\
578 SELECT day, value FROM last_numeric WHERE field_id = :field_id",
580   getLastTextFromFieldID: "\
581 SELECT day, value FROM last_text WHERE field_id = :field_id",
583   getMeasurementLastValues: "\
584 SELECT field_name, day, value FROM v_last \
585 WHERE measurement_id = :measurement_id",
587   getDailyDiscreteNumericFromFieldID: "\
588 SELECT day, value FROM daily_discrete_numeric \
589   WHERE field_id = :field_id \
590   ORDER BY day ASC, id ASC",
592   getDailyDiscreteNumericFromFieldIDAndDay: "\
593 SELECT day, value FROM daily_discrete_numeric \
594   WHERE field_id = :field_id AND day = :days \
595   ORDER BY id ASC",
597   getDailyDiscreteTextFromFieldID: "\
598 SELECT day, value FROM daily_discrete_text \
599   WHERE field_id = :field_id \
600   ORDER BY day ASC, id ASC",
602   getDailyDiscreteTextFromFieldIDAndDay: "\
603 SELECT day, value FROM daily_discrete_text \
604   WHERE field_id = :field_id AND day = :days \
605   ORDER BY id ASC",
607   getMeasurementDailyDiscreteValues: "\
608 SELECT field_name, day, value_id, value FROM v_daily_discrete \
609 WHERE measurement_id = :measurement_id \
610 ORDER BY day ASC, value_id ASC",
612   getDailyLastNumericFromFieldID: "\
613 SELECT day, value FROM daily_last_numeric \
614   WHERE field_id = :field_id \
615   ORDER BY day ASC",
617   getDailyLastNumericFromFieldIDAndDay: "\
618 SELECT day, value FROM daily_last_numeric \
619   WHERE field_id = :field_id AND day = :days",
621   getDailyLastTextFromFieldID: "\
622 SELECT day, value FROM daily_last_text \
623   WHERE field_id = :field_id \
624   ORDER BY day ASC",
626   getDailyLastTextFromFieldIDAndDay: "\
627 SELECT day, value FROM daily_last_text \
628   WHERE field_id = :field_id AND day = :days",
630   getMeasurementDailyLastValues: "\
631 SELECT field_name, day, value FROM v_daily_last \
632 WHERE measurement_id = :measurement_id",
636 function dailyKeyFromDate(date) {
637   let year = String(date.getUTCFullYear());
638   let month = String(date.getUTCMonth() + 1);
639   let day = String(date.getUTCDate());
641   if (month.length < 2) {
642     month = "0" + month;
643   }
645   if (day.length < 2) {
646     day = "0" + day;
647   }
649   return year + "-" + month + "-" + day;
654  * Create a new backend instance bound to a SQLite database at the given path.
656  * This returns a promise that will resolve to a `MetricsStorageSqliteBackend`
657  * instance. The resolved instance will be initialized and ready for use.
659  * Very few consumers have a need to call this. Instead, a higher-level entity
660  * likely calls this and sets up the database connection for a service or
661  * singleton.
662  */
663 this.MetricsStorageBackend = function (path) {
664   return Task.spawn(function initTask() {
665     let connection = yield Sqlite.openConnection({
666       path: path,
668       // There should only be one connection per database, so we disable this
669       // for perf reasons.
670       sharedMemoryCache: false,
671     });
673     // If we fail initializing the storage object, we need to close the
674     // database connection or else Storage will assert on shutdown.
675     let storage;
676     try {
677       storage = new MetricsStorageSqliteBackend(connection);
678       yield storage._init();
679     } catch (ex) {
680       yield connection.close();
681       throw ex;
682     }
684     throw new Task.Result(storage);
685   });
688 // Expose an asynchronous barrier `shutdown` that clients may use to
689 // perform last minute cleanup and shutdown requests before this module
690 // is shut down.
691 // See the documentation of AsyncShutdown.Barrier for more details.
692 let shutdown = new AsyncShutdown.Barrier("Metrics Storage Backend");
693 this.MetricsStorageBackend.shutdown = shutdown.client;
694 Sqlite.shutdown.addBlocker("Metrics Storage: Shutting down",
695   () => shutdown.wait());
698  * Manages storage of metrics data in a SQLite database.
700  * This is the main type used for interfacing with the database.
702  * Instances of this should be obtained by calling MetricsStorageConnection().
704  * The current implementation will not work if the database is mutated by
705  * multiple connections because of the way we cache primary keys.
707  * FUTURE enforce 1 read/write connection per database limit.
708  */
709 function MetricsStorageSqliteBackend(connection) {
710   this._log = Log.repository.getLogger("Services.Metrics.MetricsStorage");
712   this._connection = connection;
713   this._enabledWALCheckpointPages = null;
715   // Integer IDs to string name.
716   this._typesByID = new Map();
718   // String name to integer IDs.
719   this._typesByName = new Map();
721   // Maps provider names to integer IDs.
722   this._providerIDs = new Map();
724   // Maps :-delimited strings of [provider name, name, version] to integer IDs.
725   this._measurementsByInfo = new Map();
727   // Integer IDs to Arrays of [provider name, name, version].
728   this._measurementsByID = new Map();
730   // Integer IDs to Arrays of [measurement id, field name, value name]
731   this._fieldsByID = new Map();
733   // Maps :-delimited strings of [measurement id, field name] to integer ID.
734   this._fieldsByInfo = new Map();
736   // Maps measurement ID to sets of field IDs.
737   this._fieldsByMeasurement = new Map();
739   this._queuedOperations = [];
740   this._queuedInProgress = false;
743 MetricsStorageSqliteBackend.prototype = Object.freeze({
744   // Max size (in kibibytes) the WAL log is allowed to grow to before it is
745   // checkpointed.
746   //
747   // This was first deployed in bug 848136. We want a value large enough
748   // that we aren't checkpointing all the time. However, we want it
749   // small enough so we don't have to read so much when we open the
750   // database.
751   MAX_WAL_SIZE_KB: 512,
753   FIELD_DAILY_COUNTER: "daily-counter",
754   FIELD_DAILY_DISCRETE_NUMERIC: "daily-discrete-numeric",
755   FIELD_DAILY_DISCRETE_TEXT: "daily-discrete-text",
756   FIELD_DAILY_LAST_NUMERIC: "daily-last-numeric",
757   FIELD_DAILY_LAST_TEXT: "daily-last-text",
758   FIELD_LAST_NUMERIC: "last-numeric",
759   FIELD_LAST_TEXT: "last-text",
761   _BUILTIN_TYPES: [
762     "FIELD_DAILY_COUNTER",
763     "FIELD_DAILY_DISCRETE_NUMERIC",
764     "FIELD_DAILY_DISCRETE_TEXT",
765     "FIELD_DAILY_LAST_NUMERIC",
766     "FIELD_DAILY_LAST_TEXT",
767     "FIELD_LAST_NUMERIC",
768     "FIELD_LAST_TEXT",
769   ],
771   // Statements that are used to create the initial DB schema.
772   _SCHEMA_STATEMENTS: [
773     "createProvidersTable",
774     "createProviderStateTable",
775     "createProviderStateProviderIndex",
776     "createMeasurementsTable",
777     "createMeasurementsProviderIndex",
778     "createMeasurementsView",
779     "createTypesTable",
780     "createFieldsTable",
781     "createFieldsMeasurementIndex",
782     "createFieldsView",
783     "createDailyCountersTable",
784     "createDailyCountersFieldIndex",
785     "createDailyCountersDayIndex",
786     "createDailyCountersView",
787     "createDailyDiscreteNumericsTable",
788     "createDailyDiscreteNumericsFieldIndex",
789     "createDailyDiscreteNumericsDayIndex",
790     "createDailyDiscreteTextTable",
791     "createDailyDiscreteTextFieldIndex",
792     "createDailyDiscreteTextDayIndex",
793     "createDailyDiscreteView",
794     "createDailyLastNumericTable",
795     "createDailyLastNumericFieldIndex",
796     "createDailyLastNumericDayIndex",
797     "createDailyLastTextTable",
798     "createDailyLastTextFieldIndex",
799     "createDailyLastTextDayIndex",
800     "createDailyLastView",
801     "createLastNumericTable",
802     "createLastTextTable",
803     "createLastView",
804   ],
806   // Statements that are used to prune old data.
807   _PRUNE_STATEMENTS: [
808     "pruneOldDailyCounters",
809     "pruneOldDailyDiscreteNumeric",
810     "pruneOldDailyDiscreteText",
811     "pruneOldDailyLastNumeric",
812     "pruneOldDailyLastText",
813     "pruneOldLastNumeric",
814     "pruneOldLastText",
815   ],
817   /**
818    * Close the database connection.
819    *
820    * This should be called on all instances or the SQLite layer may complain
821    * loudly. After this has been called, the connection cannot be used.
822    *
823    * @return Promise<> A promise fulfilled once the connection is closed.
824    * This promise never rejects.
825    */
826   close: function () {
827     return Task.spawn(function doClose() {
828       // There is some light magic involved here. First, we enqueue an
829       // operation to ensure that all pending operations have the opportunity
830       // to execute. We additionally execute a SQL operation. Due to the FIFO
831       // execution order of issued statements, this will cause us to wait on
832       // any outstanding statements before closing.
833       try {
834         yield this.enqueueOperation(function dummyOperation() {
835           return this._connection.execute("SELECT 1");
836         }.bind(this));
837       } catch (ex) {}
839       try {
840         yield this._connection.close();
841       } finally {
842         this._connection = null;
843       }
844     }.bind(this));
845   },
847   /**
848    * Whether a provider is known to exist.
849    *
850    * @param provider
851    *        (string) Name of the provider.
852    */
853   hasProvider: function (provider) {
854     return this._providerIDs.has(provider);
855   },
857   /**
858    * Whether a measurement is known to exist.
859    *
860    * @param provider
861    *        (string) Name of the provider.
862    * @param name
863    *        (string) Name of the measurement.
864    * @param version
865    *        (Number) Integer measurement version.
866    */
867   hasMeasurement: function (provider, name, version) {
868     return this._measurementsByInfo.has([provider, name, version].join(":"));
869   },
871   /**
872    * Whether a named field exists in a measurement.
873    *
874    * @param measurementID
875    *        (Number) The integer primary key of the measurement.
876    * @param field
877    *        (string) The name of the field to look for.
878    */
879   hasFieldFromMeasurement: function (measurementID, field) {
880     return this._fieldsByInfo.has([measurementID, field].join(":"));
881   },
883   /**
884    * Whether a field is known.
885    *
886    * @param provider
887    *        (string) Name of the provider having the field.
888    * @param measurement
889    *        (string) Name of the measurement in the provider having the field.
890    * @param field
891    *        (string) Name of the field in the measurement.
892    */
893   hasField: function (provider, measurement, version, field) {
894     let key = [provider, measurement, version].join(":");
895     let measurementID = this._measurementsByInfo.get(key);
896     if (!measurementID) {
897       return false;
898     }
900     return this.hasFieldFromMeasurement(measurementID, field);
901   },
903   /**
904    * Look up the integer primary key of a provider.
905    *
906    * @param provider
907    *        (string) Name of the provider.
908    */
909   providerID: function (provider) {
910     return this._providerIDs.get(provider);
911   },
913   /**
914    * Look up the integer primary key of a measurement.
915    *
916    * @param provider
917    *        (string) Name of the provider.
918    * @param measurement
919    *        (string) Name of the measurement.
920    * @param version
921    *        (Number) Integer version of the measurement.
922    */
923   measurementID: function (provider, measurement, version) {
924     return this._measurementsByInfo.get([provider, measurement, version].join(":"));
925   },
927   fieldIDFromMeasurement: function (measurementID, field) {
928     return this._fieldsByInfo.get([measurementID, field].join(":"));
929   },
931   fieldID: function (provider, measurement, version, field) {
932     let measurementID = this.measurementID(provider, measurement, version);
933     if (!measurementID) {
934       return null;
935     }
937     return this.fieldIDFromMeasurement(measurementID, field);
938   },
940   measurementHasAnyDailyCounterFields: function (measurementID) {
941     return this.measurementHasAnyFieldsOfTypes(measurementID,
942                                                [this.FIELD_DAILY_COUNTER]);
943   },
945   measurementHasAnyLastFields: function (measurementID) {
946     return this.measurementHasAnyFieldsOfTypes(measurementID,
947                                                [this.FIELD_LAST_NUMERIC,
948                                                 this.FIELD_LAST_TEXT]);
949   },
951   measurementHasAnyDailyLastFields: function (measurementID) {
952     return this.measurementHasAnyFieldsOfTypes(measurementID,
953                                                [this.FIELD_DAILY_LAST_NUMERIC,
954                                                 this.FIELD_DAILY_LAST_TEXT]);
955   },
957   measurementHasAnyDailyDiscreteFields: function (measurementID) {
958     return this.measurementHasAnyFieldsOfTypes(measurementID,
959                                                [this.FIELD_DAILY_DISCRETE_NUMERIC,
960                                                 this.FIELD_DAILY_DISCRETE_TEXT]);
961   },
963   measurementHasAnyFieldsOfTypes: function (measurementID, types) {
964     if (!this._fieldsByMeasurement.has(measurementID)) {
965       return false;
966     }
968     let fieldIDs = this._fieldsByMeasurement.get(measurementID);
969     for (let fieldID of fieldIDs) {
970       let fieldType = this._fieldsByID.get(fieldID)[2];
971       if (types.indexOf(fieldType) != -1) {
972         return true;
973       }
974     }
976     return false;
977   },
979   /**
980    * Register a measurement with the backend.
981    *
982    * Measurements must be registered before storage can be allocated to them.
983    *
984    * A measurement consists of a string name and integer version attached
985    * to a named provider.
986    *
987    * This returns a promise that resolves to the storage ID for this
988    * measurement.
989    *
990    * If the measurement is not known to exist, it is registered with storage.
991    * If the measurement has already been registered, this is effectively a
992    * no-op (that still returns a promise resolving to the storage ID).
993    *
994    * @param provider
995    *        (string) Name of the provider this measurement belongs to.
996    * @param name
997    *        (string) Name of this measurement.
998    * @param version
999    *        (Number) Integer version of this measurement.
1000    */
1001   registerMeasurement: function (provider, name, version) {
1002     if (this.hasMeasurement(provider, name, version)) {
1003       return CommonUtils.laterTickResolvingPromise(
1004         this.measurementID(provider, name, version));
1005     }
1007     // Registrations might not be safe to perform in parallel with provider
1008     // operations. So, we queue them.
1009     let self = this;
1010     return this.enqueueOperation(function createMeasurementOperation() {
1011       return Task.spawn(function createMeasurement() {
1012         let providerID = self._providerIDs.get(provider);
1014         if (!providerID) {
1015           yield self._connection.executeCached(SQL.addProvider, {provider: provider});
1016           let rows = yield self._connection.executeCached(SQL.getProviderID,
1017                                                           {provider: provider});
1019           providerID = rows[0].getResultByIndex(0);
1021           self._providerIDs.set(provider, providerID);
1022         }
1024         let params = {
1025           provider_id: providerID,
1026           measurement: name,
1027           version: version,
1028         };
1030         yield self._connection.executeCached(SQL.addMeasurement, params);
1031         let rows = yield self._connection.executeCached(SQL.getMeasurementID, params);
1033         let measurementID = rows[0].getResultByIndex(0);
1035         self._measurementsByInfo.set([provider, name, version].join(":"), measurementID);
1036         self._measurementsByID.set(measurementID, [provider, name, version]);
1037         self._fieldsByMeasurement.set(measurementID, new Set());
1039         throw new Task.Result(measurementID);
1040       });
1041     });
1042   },
1044   /**
1045    * Register a field with the backend.
1046    *
1047    * Fields are what recorded pieces of data are primarily associated with.
1048    *
1049    * Fields are associated with measurements. Measurements must be registered
1050    * via `registerMeasurement` before fields can be registered. This is
1051    * enforced by this function requiring the database primary key of the
1052    * measurement as an argument.
1053    *
1054    * @param measurementID
1055    *        (Number) Integer primary key of measurement this field belongs to.
1056    * @param field
1057    *        (string) Name of this field.
1058    * @param valueType
1059    *        (string) Type name of this field. Must be a registered type. Is
1060    *        likely one of the FIELD_ constants on this type.
1061    *
1062    * @return Promise<integer>
1063    */
1064   registerField: function (measurementID, field, valueType) {
1065     if (!valueType) {
1066       throw new Error("Value type must be defined.");
1067     }
1069     if (!this._measurementsByID.has(measurementID)) {
1070       throw new Error("Measurement not known: " + measurementID);
1071     }
1073     if (!this._typesByName.has(valueType)) {
1074       throw new Error("Unknown value type: " + valueType);
1075     }
1077     let typeID = this._typesByName.get(valueType);
1079     if (!typeID) {
1080       throw new Error("Undefined type: " + valueType);
1081     }
1083     if (this.hasFieldFromMeasurement(measurementID, field)) {
1084       let id = this.fieldIDFromMeasurement(measurementID, field);
1085       let existingType = this._fieldsByID.get(id)[2];
1087       if (valueType != existingType) {
1088         throw new Error("Field already defined with different type: " + existingType);
1089       }
1091       return CommonUtils.laterTickResolvingPromise(
1092         this.fieldIDFromMeasurement(measurementID, field));
1093     }
1095     let self = this;
1096     return Task.spawn(function createField() {
1097       let params = {
1098         measurement_id: measurementID,
1099         field: field,
1100         value_type: typeID,
1101       };
1103       yield self._connection.executeCached(SQL.addField, params);
1105       let rows = yield self._connection.executeCached(SQL.getFieldID, params);
1107       let fieldID = rows[0].getResultByIndex(0);
1109       self._fieldsByID.set(fieldID, [measurementID, field, valueType]);
1110       self._fieldsByInfo.set([measurementID, field].join(":"), fieldID);
1111       self._fieldsByMeasurement.get(measurementID).add(fieldID);
1113       throw new Task.Result(fieldID);
1114     });
1115   },
1117   /**
1118    * Initializes this instance with the database.
1119    *
1120    * This performs 2 major roles:
1121    *
1122    *   1) Set up database schema (creates tables).
1123    *   2) Synchronize database with local instance.
1124    */
1125   _init: function() {
1126     let self = this;
1127     return Task.spawn(function initTask() {
1128       // 0. Database file and connection configuration.
1130       // This should never fail. But, we assume the default of 1024 in case it
1131       // does.
1132       let rows = yield self._connection.execute("PRAGMA page_size");
1133       let pageSize = 1024;
1134       if (rows.length) {
1135         pageSize = rows[0].getResultByIndex(0);
1136       }
1138       self._log.debug("Page size is " + pageSize);
1140       // Ensure temp tables are stored in memory, not on disk.
1141       yield self._connection.execute("PRAGMA temp_store=MEMORY");
1143       let journalMode;
1144       rows = yield self._connection.execute("PRAGMA journal_mode=WAL");
1145       if (rows.length) {
1146         journalMode = rows[0].getResultByIndex(0);
1147       }
1149       self._log.info("Journal mode is " + journalMode);
1151       if (journalMode == "wal") {
1152         self._enabledWALCheckpointPages =
1153           Math.ceil(self.MAX_WAL_SIZE_KB * 1024 / pageSize);
1155         self._log.info("WAL auto checkpoint pages: " +
1156                        self._enabledWALCheckpointPages);
1158         // We disable auto checkpoint during initialization to make it
1159         // quicker.
1160         yield self.setAutoCheckpoint(0);
1161       } else {
1162         if (journalMode != "truncate") {
1163          // Fall back to truncate (which is faster than delete).
1164           yield self._connection.execute("PRAGMA journal_mode=TRUNCATE");
1165         }
1167         // And always use full synchronous mode to reduce possibility for data
1168         // loss.
1169         yield self._connection.execute("PRAGMA synchronous=FULL");
1170       }
1172       let doCheckpoint = false;
1174       // 1. Create the schema.
1175       yield self._connection.executeTransaction(function ensureSchema(conn) {
1176         let schema = yield conn.getSchemaVersion();
1178         if (schema == 0) {
1179           self._log.info("Creating database schema.");
1181           for (let k of self._SCHEMA_STATEMENTS) {
1182             yield self._connection.execute(SQL[k]);
1183           }
1185           yield self._connection.setSchemaVersion(1);
1186           doCheckpoint = true;
1187         } else if (schema != 1) {
1188           throw new Error("Unknown database schema: " + schema);
1189         } else {
1190           self._log.debug("Database schema up to date.");
1191         }
1192       });
1194       // 2. Retrieve existing types.
1195       yield self._connection.execute(SQL.getTypes, null, function onRow(row) {
1196         let id = row.getResultByName("id");
1197         let name = row.getResultByName("name");
1199         self._typesByID.set(id, name);
1200         self._typesByName.set(name, id);
1201       });
1203       // 3. Populate built-in types with database.
1204       let missingTypes = [];
1205       for (let type of self._BUILTIN_TYPES) {
1206         type = self[type];
1207         if (self._typesByName.has(type)) {
1208           continue;
1209         }
1211         missingTypes.push(type);
1212       }
1214       // Don't perform DB transaction unless there is work to do.
1215       if (missingTypes.length) {
1216         yield self._connection.executeTransaction(function populateBuiltinTypes() {
1217           for (let type of missingTypes) {
1218             let params = {name: type};
1219             yield self._connection.executeCached(SQL.addType, params);
1220             let rows = yield self._connection.executeCached(SQL.getTypeID, params);
1221             let id = rows[0].getResultByIndex(0);
1223             self._typesByID.set(id, type);
1224             self._typesByName.set(type, id);
1225           }
1226         });
1228         doCheckpoint = true;
1229       }
1231       // 4. Obtain measurement info.
1232       yield self._connection.execute(SQL.getMeasurements, null, function onRow(row) {
1233         let providerID = row.getResultByName("provider_id");
1234         let providerName = row.getResultByName("provider_name");
1235         let measurementID = row.getResultByName("measurement_id");
1236         let measurementName = row.getResultByName("measurement_name");
1237         let measurementVersion = row.getResultByName("measurement_version");
1239         self._providerIDs.set(providerName, providerID);
1241         let info = [providerName, measurementName, measurementVersion].join(":");
1243         self._measurementsByInfo.set(info, measurementID);
1244         self._measurementsByID.set(measurementID, info);
1245         self._fieldsByMeasurement.set(measurementID, new Set());
1246       });
1248       // 5. Obtain field info.
1249       yield self._connection.execute(SQL.getFieldInfo, null, function onRow(row) {
1250         let measurementID = row.getResultByName("measurement_id");
1251         let fieldID = row.getResultByName("field_id");
1252         let fieldName = row.getResultByName("field_name");
1253         let typeName = row.getResultByName("type_name");
1255         self._fieldsByID.set(fieldID, [measurementID, fieldName, typeName]);
1256         self._fieldsByInfo.set([measurementID, fieldName].join(":"), fieldID);
1257         self._fieldsByMeasurement.get(measurementID).add(fieldID);
1258       });
1260       // Perform a checkpoint after initialization (if needed) and
1261       // enable auto checkpoint during regular operation.
1262       if (doCheckpoint) {
1263         yield self.checkpoint();
1264       }
1266       yield self.setAutoCheckpoint(1);
1267     });
1268   },
1270   /**
1271    * Prune all data from earlier than the specified date.
1272    *
1273    * Data stored on days before the specified Date will be permanently
1274    * deleted.
1275    *
1276    * This returns a promise that will be resolved when data has been deleted.
1277    *
1278    * @param date
1279    *        (Date) Old data threshold.
1280    * @return Promise<>
1281    */
1282   pruneDataBefore: function (date) {
1283     let statements = this._PRUNE_STATEMENTS;
1285     let self = this;
1286     return this.enqueueOperation(function doPrune() {
1287       return self._connection.executeTransaction(function prune(conn) {
1288         let days = dateToDays(date);
1290         let params = {days: days};
1291         for (let name of statements) {
1292           yield conn.execute(SQL[name], params);
1293         }
1294       });
1295     });
1296   },
1298   /**
1299    * Reduce memory usage as much as possible.
1300    *
1301    * This returns a promise that will be resolved on completion.
1302    *
1303    * @return Promise<>
1304    */
1305   compact: function () {
1306     let self = this;
1307     return this.enqueueOperation(function doCompact() {
1308       self._connection.discardCachedStatements();
1309       return self._connection.shrinkMemory();
1310     });
1311   },
1313   /**
1314    * Checkpoint writes requiring flush to disk.
1315    *
1316    * This is called to persist queued and non-flushed writes to disk.
1317    * It will force an fsync, so it is expensive and should be used
1318    * sparingly.
1319    */
1320   checkpoint: function () {
1321     if (!this._enabledWALCheckpointPages) {
1322       return CommonUtils.laterTickResolvingPromise();
1323     }
1325     return this.enqueueOperation(function checkpoint() {
1326       this._log.info("Performing manual WAL checkpoint.");
1327       return this._connection.execute("PRAGMA wal_checkpoint");
1328     }.bind(this));
1329   },
1331   setAutoCheckpoint: function (on) {
1332     // If we aren't in WAL mode, wal_autocheckpoint won't do anything so
1333     // we no-op.
1334     if (!this._enabledWALCheckpointPages) {
1335       return CommonUtils.laterTickResolvingPromise();
1336     }
1338     let val = on ? this._enabledWALCheckpointPages : 0;
1340     return this.enqueueOperation(function setWALCheckpoint() {
1341       this._log.info("Setting WAL auto checkpoint to " + val);
1342       return this._connection.execute("PRAGMA wal_autocheckpoint=" + val);
1343     }.bind(this));
1344   },
1346   /**
1347    * Ensure a field ID matches a specified type.
1348    *
1349    * This is called internally as part of adding values to ensure that
1350    * the type of a field matches the operation being performed.
1351    */
1352   _ensureFieldType: function (id, type) {
1353     let info = this._fieldsByID.get(id);
1355     if (!info || !Array.isArray(info)) {
1356       throw new Error("Unknown field ID: " + id);
1357     }
1359     if (type != info[2]) {
1360       throw new Error("Field type does not match the expected for this " +
1361                       "operation. Actual: " + info[2] + "; Expected: " +
1362                       type);
1363     }
1364   },
1366   /**
1367    * Enqueue a storage operation to be performed when the database is ready.
1368    *
1369    * The primary use case of this function is to prevent potentially
1370    * conflicting storage operations from being performed in parallel. By
1371    * calling this function, passed storage operations will be serially
1372    * executed, avoiding potential order of operation issues.
1373    *
1374    * The passed argument is a function that will perform storage operations.
1375    * The function should return a promise that will be resolved when all
1376    * storage operations have been completed.
1377    *
1378    * The passed function may be executed immediately. If there are already
1379    * queued operations, it will be appended to the queue and executed after all
1380    * before it have finished.
1381    *
1382    * This function returns a promise that will be resolved or rejected with
1383    * the same value that the function's promise was resolved or rejected with.
1384    *
1385    * @param func
1386    *        (function) Function performing storage interactions.
1387    * @return Promise<>
1388    */
1389   enqueueOperation: function (func) {
1390     if (typeof(func) != "function") {
1391       throw new Error("enqueueOperation expects a function. Got: " + typeof(func));
1392     }
1394     this._log.trace("Enqueueing operation.");
1395     let deferred = Promise.defer();
1397     this._queuedOperations.push([func, deferred]);
1399     if (this._queuedOperations.length == 1) {
1400       this._popAndPerformQueuedOperation();
1401     }
1403     return deferred.promise;
1404   },
1406   /**
1407    * Enqueue a function to be performed as a transaction.
1408    *
1409    * The passed function should be a generator suitable for calling with
1410    * `executeTransaction` from the SQLite connection.
1411    */
1412   enqueueTransaction: function (func, type) {
1413     return this.enqueueOperation(
1414       this._connection.executeTransaction.bind(this._connection, func, type)
1415     );
1416   },
1418   _popAndPerformQueuedOperation: function () {
1419     if (!this._queuedOperations.length || this._queuedInProgress) {
1420       return;
1421     }
1423     this._log.trace("Performing queued operation.");
1424     let [func, deferred] = this._queuedOperations.shift();
1425     let promise;
1427     try {
1428       this._queuedInProgress = true;
1429       promise = func();
1430     } catch (ex) {
1431       this._log.warn("Queued operation threw during execution: " +
1432                      CommonUtils.exceptionStr(ex));
1433       this._queuedInProgress = false;
1434       deferred.reject(ex);
1435       this._popAndPerformQueuedOperation();
1436       return;
1437     }
1439     if (!promise || typeof(promise.then) != "function") {
1440       let msg = "Queued operation did not return a promise: " + func;
1441       this._log.warn(msg);
1443       this._queuedInProgress = false;
1444       deferred.reject(new Error(msg));
1445       this._popAndPerformQueuedOperation();
1446       return;
1447     }
1449     promise.then(
1450       function onSuccess(result) {
1451         this._log.trace("Queued operation completed.");
1452         this._queuedInProgress = false;
1453         deferred.resolve(result);
1454         this._popAndPerformQueuedOperation();
1455       }.bind(this),
1456       function onError(error) {
1457         this._log.warn("Failure when performing queued operation: " +
1458                        CommonUtils.exceptionStr(error));
1459         this._queuedInProgress = false;
1460         deferred.reject(error);
1461         this._popAndPerformQueuedOperation();
1462       }.bind(this)
1463     );
1464   },
1466   /**
1467    * Obtain all values associated with a measurement.
1468    *
1469    * This returns a promise that resolves to an object. The keys of the object
1470    * are:
1471    *
1472    *   days -- DailyValues where the values are Maps of field name to data
1473    *     structures. The data structures could be simple (string or number) or
1474    *     Arrays if the field type allows multiple values per day.
1475    *
1476    *   singular -- Map of field names to values. This holds all fields that
1477    *     don't have a temporal component.
1478    *
1479    * @param id
1480    *        (Number) Primary key of measurement whose values to retrieve.
1481    */
1482   getMeasurementValues: function (id) {
1483     let deferred = Promise.defer();
1484     let days = new DailyValues();
1485     let singular = new Map();
1487     let self = this;
1488     this.enqueueOperation(function enqueuedGetMeasurementValues() {
1489       return Task.spawn(function fetchMeasurementValues() {
1490         function handleResult(data) {
1491           for (let [field, values] of data) {
1492             for (let [day, value] of Iterator(values)) {
1493               if (!days.hasDay(day)) {
1494                 days.setDay(day, new Map());
1495               }
1497               days.getDay(day).set(field, value);
1498             }
1499           }
1500         }
1502         if (self.measurementHasAnyDailyCounterFields(id)) {
1503           let counters = yield self.getMeasurementDailyCountersFromMeasurementID(id);
1504           handleResult(counters);
1505         }
1507         if (self.measurementHasAnyDailyLastFields(id)) {
1508           let dailyLast = yield self.getMeasurementDailyLastValuesFromMeasurementID(id);
1509           handleResult(dailyLast);
1510         }
1512         if (self.measurementHasAnyDailyDiscreteFields(id)) {
1513           let dailyDiscrete = yield self.getMeasurementDailyDiscreteValuesFromMeasurementID(id);
1514           handleResult(dailyDiscrete);
1515         }
1517         if (self.measurementHasAnyLastFields(id)) {
1518           let last = yield self.getMeasurementLastValuesFromMeasurementID(id);
1520           for (let [field, value] of last) {
1521             singular.set(field, value);
1522           }
1523         }
1525       });
1526     }).then(function onSuccess() {
1527       deferred.resolve({singular: singular, days: days});
1528     }, function onError(error) {
1529       deferred.reject(error);
1530     });
1532     return deferred.promise;
1533   },
1535   //---------------------------------------------------------------------------
1536   // Low-level storage operations
1537   //
1538   // These will be performed immediately (or at least as soon as the underlying
1539   // connection allows them to be.) It is recommended to call these from within
1540   // a function added via `enqueueOperation()` or they may inadvertently be
1541   // performed during another enqueued operation, which may be a transaction
1542   // that is rolled back.
1543   // ---------------------------------------------------------------------------
1545   /**
1546    * Set state for a provider.
1547    *
1548    * Providers have the ability to register persistent state with the backend.
1549    * Persistent state doesn't expire. The format of the data is completely up
1550    * to the provider beyond the requirement that values be UTF-8 strings.
1551    *
1552    * This returns a promise that will be resolved when the underlying database
1553    * operation has completed.
1554    *
1555    * @param provider
1556    *        (string) Name of the provider.
1557    * @param key
1558    *        (string) Key under which to store this state.
1559    * @param value
1560    *        (string) Value for this state.
1561    * @return Promise<>
1562    */
1563   setProviderState: function (provider, key, value) {
1564     if (typeof(key) != "string") {
1565       throw new Error("State key must be a string. Got: " + key);
1566     }
1568     if (typeof(value) != "string") {
1569       throw new Error("State value must be a string. Got: " + value);
1570     }
1572     let id = this.providerID(provider);
1573     if (!id) {
1574       throw new Error("Unknown provider: " + provider);
1575     }
1577     return this._connection.executeCached(SQL.setProviderState, {
1578       provider_id: id,
1579       name: key,
1580       value: value,
1581     });
1582   },
1584   /**
1585    * Obtain named state for a provider.
1586    *
1587    *
1588    * The returned promise will resolve to the state from the database or null
1589    * if the key is not stored.
1590    *
1591    * @param provider
1592    *        (string) The name of the provider whose state to obtain.
1593    * @param key
1594    *        (string) The state's key to retrieve.
1595    *
1596    * @return Promise<data>
1597    */
1598   getProviderState: function (provider, key) {
1599     let id = this.providerID(provider);
1600     if (!id) {
1601       throw new Error("Unknown provider: " + provider);
1602     }
1604     let conn = this._connection;
1605     return Task.spawn(function queryDB() {
1606       let rows = yield conn.executeCached(SQL.getProviderStateWithName, {
1607         provider_id: id,
1608         name: key,
1609       });
1611       if (!rows.length) {
1612         throw new Task.Result(null);
1613       }
1615       throw new Task.Result(rows[0].getResultByIndex(0));
1616     });
1617   },
1619   /**
1620    * Increment a daily counter from a numeric field id.
1621    *
1622    * @param id
1623    *        (integer) Primary key of field to increment.
1624    * @param date
1625    *        (Date) When the increment occurred. This is typically "now" but can
1626    *        be explicitly defined for events that occurred in the past.
1627    * @param by
1628    *        (integer) How much to increment the value by. Defaults to 1.
1629    */
1630   incrementDailyCounterFromFieldID: function (id, date=new Date(), by=1) {
1631     this._ensureFieldType(id, this.FIELD_DAILY_COUNTER);
1633     let params = {
1634       field_id: id,
1635       days: dateToDays(date),
1636       by: by,
1637     };
1639     return this._connection.executeCached(SQL.incrementDailyCounterFromFieldID,
1640                                           params);
1641   },
1643   /**
1644    * Obtain all counts for a specific daily counter.
1645    *
1646    * @param id
1647    *        (integer) The ID of the field being retrieved.
1648    */
1649   getDailyCounterCountsFromFieldID: function (id) {
1650     this._ensureFieldType(id, this.FIELD_DAILY_COUNTER);
1652     let self = this;
1653     return Task.spawn(function fetchCounterDays() {
1654       let rows = yield self._connection.executeCached(SQL.getDailyCounterCountsFromFieldID,
1655                                                       {field_id: id});
1657       let result = new DailyValues();
1658       for (let row of rows) {
1659         let days = row.getResultByIndex(0);
1660         let counter = row.getResultByIndex(1);
1662         let date = daysToDate(days);
1663         result.setDay(date, counter);
1664       }
1666       throw new Task.Result(result);
1667     });
1668   },
1670   /**
1671    * Get the value of a daily counter for a given day.
1672    *
1673    * @param field
1674    *        (integer) Field ID to retrieve.
1675    * @param date
1676    *        (Date) Date for day from which to obtain data.
1677    */
1678   getDailyCounterCountFromFieldID: function (field, date) {
1679     this._ensureFieldType(field, this.FIELD_DAILY_COUNTER);
1681     let params = {
1682       field_id: field,
1683       days: dateToDays(date),
1684     };
1686     let self = this;
1687     return Task.spawn(function fetchCounter() {
1688       let rows = yield self._connection.executeCached(SQL.getDailyCounterCountFromFieldID,
1689                                                       params);
1690       if (!rows.length) {
1691         throw new Task.Result(null);
1692       }
1694       throw new Task.Result(rows[0].getResultByIndex(0));
1695     });
1696   },
1698   /**
1699    * Define the value for a "last numeric" field.
1700    *
1701    * The previous value (if any) will be replaced by the value passed, even if
1702    * the date of the incoming value is older than what's recorded in the
1703    * database.
1704    *
1705    * @param fieldID
1706    *        (Number) Integer primary key of field to update.
1707    * @param value
1708    *        (Number) Value to record.
1709    * @param date
1710    *        (Date) When this value was produced.
1711    */
1712   setLastNumericFromFieldID: function (fieldID, value, date=new Date()) {
1713     this._ensureFieldType(fieldID, this.FIELD_LAST_NUMERIC);
1715     if (typeof(value) != "number") {
1716       throw new Error("Value is not a number: " + value);
1717     }
1719     let params = {
1720       field_id: fieldID,
1721       days: dateToDays(date),
1722       value: value,
1723     };
1725     return this._connection.executeCached(SQL.setLastNumeric, params);
1726   },
1728   /**
1729    * Define the value of a "last text" field.
1730    *
1731    * See `setLastNumericFromFieldID` for behavior.
1732    */
1733   setLastTextFromFieldID: function (fieldID, value, date=new Date()) {
1734     this._ensureFieldType(fieldID, this.FIELD_LAST_TEXT);
1736     if (typeof(value) != "string") {
1737       throw new Error("Value is not a string: " + value);
1738     }
1740     let params = {
1741       field_id: fieldID,
1742       days: dateToDays(date),
1743       value: value,
1744     };
1746     return this._connection.executeCached(SQL.setLastText, params);
1747   },
1749   /**
1750    * Obtain the value of a "last numeric" field.
1751    *
1752    * This returns a promise that will be resolved with an Array of [date, value]
1753    * if a value is known or null if no last value is present.
1754    *
1755    * @param fieldID
1756    *        (Number) Integer primary key of field to retrieve.
1757    */
1758   getLastNumericFromFieldID: function (fieldID) {
1759     this._ensureFieldType(fieldID, this.FIELD_LAST_NUMERIC);
1761     let self = this;
1762     return Task.spawn(function fetchLastField() {
1763       let rows = yield self._connection.executeCached(SQL.getLastNumericFromFieldID,
1764                                                       {field_id: fieldID});
1766       if (!rows.length) {
1767         throw new Task.Result(null);
1768       }
1770       let row = rows[0];
1771       let days = row.getResultByIndex(0);
1772       let value = row.getResultByIndex(1);
1774       throw new Task.Result([daysToDate(days), value]);
1775     });
1776   },
1778   /**
1779    * Obtain the value of a "last text" field.
1780    *
1781    * See `getLastNumericFromFieldID` for behavior.
1782    */
1783   getLastTextFromFieldID: function (fieldID) {
1784     this._ensureFieldType(fieldID, this.FIELD_LAST_TEXT);
1786     let self = this;
1787     return Task.spawn(function fetchLastField() {
1788       let rows = yield self._connection.executeCached(SQL.getLastTextFromFieldID,
1789                                                       {field_id: fieldID});
1791       if (!rows.length) {
1792         throw new Task.Result(null);
1793       }
1795       let row = rows[0];
1796       let days = row.getResultByIndex(0);
1797       let value = row.getResultByIndex(1);
1799       throw new Task.Result([daysToDate(days), value]);
1800     });
1801   },
1803   /**
1804    * Delete the value (if any) in a "last numeric" field.
1805    */
1806   deleteLastNumericFromFieldID: function (fieldID) {
1807     this._ensureFieldType(fieldID, this.FIELD_LAST_NUMERIC);
1809     return this._connection.executeCached(SQL.deleteLastNumericFromFieldID,
1810                                           {field_id: fieldID});
1811   },
1813   /**
1814    * Delete the value (if any) in a "last text" field.
1815    */
1816   deleteLastTextFromFieldID: function (fieldID) {
1817     this._ensureFieldType(fieldID, this.FIELD_LAST_TEXT);
1819     return this._connection.executeCached(SQL.deleteLastTextFromFieldID,
1820                                           {field_id: fieldID});
1821   },
1823   /**
1824    * Record a value for a "daily last numeric" field.
1825    *
1826    * The field can hold 1 value per calendar day. If the field already has a
1827    * value for the day specified (defaults to now), that value will be
1828    * replaced, even if the date specified is older (within the day) than the
1829    * previously recorded value.
1830    *
1831    * @param fieldID
1832    *        (Number) Integer primary key of field.
1833    * @param value
1834    *        (Number) Value to record.
1835    * @param date
1836    *        (Date) When the value was produced. Defaults to now.
1837    */
1838   setDailyLastNumericFromFieldID: function (fieldID, value, date=new Date()) {
1839     this._ensureFieldType(fieldID, this.FIELD_DAILY_LAST_NUMERIC);
1841     let params = {
1842       field_id: fieldID,
1843       days: dateToDays(date),
1844       value: value,
1845     };
1847     return this._connection.executeCached(SQL.setDailyLastNumeric, params);
1848   },
1850   /**
1851    * Record a value for a "daily last text" field.
1852    *
1853    * See `setDailyLastNumericFromFieldID` for behavior.
1854    */
1855   setDailyLastTextFromFieldID: function (fieldID, value, date=new Date()) {
1856     this._ensureFieldType(fieldID, this.FIELD_DAILY_LAST_TEXT);
1858     let params = {
1859       field_id: fieldID,
1860       days: dateToDays(date),
1861       value: value,
1862     };
1864     return this._connection.executeCached(SQL.setDailyLastText, params);
1865   },
1867   /**
1868    * Obtain value(s) from a "daily last numeric" field.
1869    *
1870    * This returns a promise that resolves to a DailyValues instance. If `date`
1871    * is specified, that instance will have at most 1 entry. If there is no
1872    * `date` constraint, then all stored values will be retrieved.
1873    *
1874    * @param fieldID
1875    *        (Number) Integer primary key of field to retrieve.
1876    * @param date optional
1877    *        (Date) If specified, only return data for this day.
1878    *
1879    * @return Promise<DailyValues>
1880    */
1881   getDailyLastNumericFromFieldID: function (fieldID, date=null) {
1882     this._ensureFieldType(fieldID, this.FIELD_DAILY_LAST_NUMERIC);
1884     let params = {field_id: fieldID};
1885     let name = "getDailyLastNumericFromFieldID";
1887     if (date) {
1888       params.days = dateToDays(date);
1889       name = "getDailyLastNumericFromFieldIDAndDay";
1890     }
1892     return this._getDailyLastFromFieldID(name, params);
1893   },
1895   /**
1896    * Obtain value(s) from a "daily last text" field.
1897    *
1898    * See `getDailyLastNumericFromFieldID` for behavior.
1899    */
1900   getDailyLastTextFromFieldID: function (fieldID, date=null) {
1901     this._ensureFieldType(fieldID, this.FIELD_DAILY_LAST_TEXT);
1903     let params = {field_id: fieldID};
1904     let name = "getDailyLastTextFromFieldID";
1906     if (date) {
1907       params.days = dateToDays(date);
1908       name = "getDailyLastTextFromFieldIDAndDay";
1909     }
1911     return this._getDailyLastFromFieldID(name, params);
1912   },
1914   _getDailyLastFromFieldID: function (name, params) {
1915     let self = this;
1916     return Task.spawn(function fetchDailyLastForField() {
1917       let rows = yield self._connection.executeCached(SQL[name], params);
1919       let result = new DailyValues();
1920       for (let row of rows) {
1921         let d = daysToDate(row.getResultByIndex(0));
1922         let value = row.getResultByIndex(1);
1924         result.setDay(d, value);
1925       }
1927       throw new Task.Result(result);
1928     });
1929   },
1931   /**
1932    * Add a new value for a "daily discrete numeric" field.
1933    *
1934    * This appends a new value to the list of values for a specific field. All
1935    * values are retained. Duplicate values are allowed.
1936    *
1937    * @param fieldID
1938    *        (Number) Integer primary key of field.
1939    * @param value
1940    *        (Number) Value to record.
1941    * @param date optional
1942    *        (Date) When this value occurred. Values are bucketed by day.
1943    */
1944   addDailyDiscreteNumericFromFieldID: function (fieldID, value, date=new Date()) {
1945     this._ensureFieldType(fieldID, this.FIELD_DAILY_DISCRETE_NUMERIC);
1947     if (typeof(value) != "number") {
1948       throw new Error("Number expected. Got: " + value);
1949     }
1951     let params = {
1952       field_id: fieldID,
1953       days: dateToDays(date),
1954       value: value,
1955     };
1957     return this._connection.executeCached(SQL.addDailyDiscreteNumeric, params);
1958   },
1960   /**
1961    * Add a new value for a "daily discrete text" field.
1962    *
1963    * See `addDailyDiscreteNumericFromFieldID` for behavior.
1964    */
1965   addDailyDiscreteTextFromFieldID: function (fieldID, value, date=new Date()) {
1966     this._ensureFieldType(fieldID, this.FIELD_DAILY_DISCRETE_TEXT);
1968     if (typeof(value) != "string") {
1969       throw new Error("String expected. Got: " + value);
1970     }
1972     let params = {
1973       field_id: fieldID,
1974       days: dateToDays(date),
1975       value: value,
1976     };
1978     return this._connection.executeCached(SQL.addDailyDiscreteText, params);
1979   },
1981   /**
1982    * Obtain values for a "daily discrete numeric" field.
1983    *
1984    * This returns a promise that resolves to a `DailyValues` instance. If
1985    * `date` is specified, there will be at most 1 key in that instance. If
1986    * not, all data from the database will be retrieved.
1987    *
1988    * Values in that instance will be arrays of the raw values.
1989    *
1990    * @param fieldID
1991    *        (Number) Integer primary key of field to retrieve.
1992    * @param date optional
1993    *        (Date) Day to obtain data for. Date can be any time in the day.
1994    */
1995   getDailyDiscreteNumericFromFieldID: function (fieldID, date=null) {
1996     this._ensureFieldType(fieldID, this.FIELD_DAILY_DISCRETE_NUMERIC);
1998     let params = {field_id: fieldID};
2000     let name = "getDailyDiscreteNumericFromFieldID";
2002     if (date) {
2003       params.days = dateToDays(date);
2004       name = "getDailyDiscreteNumericFromFieldIDAndDay";
2005     }
2007     return this._getDailyDiscreteFromFieldID(name, params);
2008   },
2010   /**
2011    * Obtain values for a "daily discrete text" field.
2012    *
2013    * See `getDailyDiscreteNumericFromFieldID` for behavior.
2014    */
2015   getDailyDiscreteTextFromFieldID: function (fieldID, date=null) {
2016     this._ensureFieldType(fieldID, this.FIELD_DAILY_DISCRETE_TEXT);
2018     let params = {field_id: fieldID};
2020     let name = "getDailyDiscreteTextFromFieldID";
2022     if (date) {
2023       params.days = dateToDays(date);
2024       name = "getDailyDiscreteTextFromFieldIDAndDay";
2025     }
2027     return this._getDailyDiscreteFromFieldID(name, params);
2028   },
2030   _getDailyDiscreteFromFieldID: function (name, params) {
2031     let self = this;
2032     return Task.spawn(function fetchDailyDiscreteValuesForField() {
2033       let rows = yield self._connection.executeCached(SQL[name], params);
2035       let result = new DailyValues();
2036       for (let row of rows) {
2037         let d = daysToDate(row.getResultByIndex(0));
2038         let value = row.getResultByIndex(1);
2040         result.appendValue(d, value);
2041       }
2043       throw new Task.Result(result);
2044     });
2045   },
2047   /**
2048    * Obtain the counts of daily counters in a measurement.
2049    *
2050    * This returns a promise that resolves to a Map of field name strings to
2051    * DailyValues that hold per-day counts.
2052    *
2053    * @param id
2054    *        (Number) Integer primary key of measurement.
2055    *
2056    * @return Promise<Map>
2057    */
2058   getMeasurementDailyCountersFromMeasurementID: function (id) {
2059     let self = this;
2060     return Task.spawn(function fetchDailyCounters() {
2061       let rows = yield self._connection.execute(SQL.getMeasurementDailyCounters,
2062                                                 {measurement_id: id});
2064       let result = new Map();
2065       for (let row of rows) {
2066         let field = row.getResultByName("field_name");
2067         let date = daysToDate(row.getResultByName("day"));
2068         let value = row.getResultByName("value");
2070         if (!result.has(field)) {
2071           result.set(field, new DailyValues());
2072         }
2074         result.get(field).setDay(date, value);
2075       }
2077       throw new Task.Result(result);
2078     });
2079   },
2081   /**
2082    * Obtain the values of "last" fields from a measurement.
2083    *
2084    * This returns a promise that resolves to a Map of field name to an array
2085    * of [date, value].
2086    *
2087    * @param id
2088    *        (Number) Integer primary key of measurement whose data to retrieve.
2089    *
2090    * @return Promise<Map>
2091    */
2092   getMeasurementLastValuesFromMeasurementID: function (id) {
2093     let self = this;
2094     return Task.spawn(function fetchMeasurementLastValues() {
2095       let rows = yield self._connection.execute(SQL.getMeasurementLastValues,
2096                                                 {measurement_id: id});
2098       let result = new Map();
2099       for (let row of rows) {
2100         let date = daysToDate(row.getResultByIndex(1));
2101         let value = row.getResultByIndex(2);
2102         result.set(row.getResultByIndex(0), [date, value]);
2103       }
2105       throw new Task.Result(result);
2106     });
2107   },
2109   /**
2110    * Obtain the values of "last daily" fields from a measurement.
2111    *
2112    * This returns a promise that resolves to a Map of field name to DailyValues
2113    * instances. Each DailyValues instance has days for which a daily last value
2114    * is defined. The values in each DailyValues are the raw last value for that
2115    * day.
2116    *
2117    * @param id
2118    *        (Number) Integer primary key of measurement whose data to retrieve.
2119    *
2120    * @return Promise<Map>
2121    */
2122   getMeasurementDailyLastValuesFromMeasurementID: function (id) {
2123     let self = this;
2124     return Task.spawn(function fetchMeasurementDailyLastValues() {
2125       let rows = yield self._connection.execute(SQL.getMeasurementDailyLastValues,
2126                                                 {measurement_id: id});
2128       let result = new Map();
2129       for (let row of rows) {
2130         let field = row.getResultByName("field_name");
2131         let date = daysToDate(row.getResultByName("day"));
2132         let value = row.getResultByName("value");
2134         if (!result.has(field)) {
2135           result.set(field, new DailyValues());
2136         }
2138         result.get(field).setDay(date, value);
2139       }
2141       throw new Task.Result(result);
2142     });
2143   },
2145   /**
2146    * Obtain the values of "daily discrete" fields from a measurement.
2147    *
2148    * This obtains all discrete values for all "daily discrete" fields in a
2149    * measurement.
2150    *
2151    * This returns a promise that resolves to a Map. The Map's keys are field
2152    * string names. Values are `DailyValues` instances. The values inside
2153    * the `DailyValues` are arrays of the raw discrete values.
2154    *
2155    * @param id
2156    *        (Number) Integer primary key of measurement.
2157    *
2158    * @return Promise<Map>
2159    */
2160   getMeasurementDailyDiscreteValuesFromMeasurementID: function (id) {
2161     let deferred = Promise.defer();
2162     let result = new Map();
2164     this._connection.execute(SQL.getMeasurementDailyDiscreteValues,
2165                              {measurement_id: id}, function onRow(row) {
2166       let field = row.getResultByName("field_name");
2167       let date = daysToDate(row.getResultByName("day"));
2168       let value = row.getResultByName("value");
2170       if (!result.has(field)) {
2171         result.set(field, new DailyValues());
2172       }
2174       result.get(field).appendValue(date, value);
2175     }).then(function onComplete() {
2176       deferred.resolve(result);
2177     }, function onError(error) {
2178       deferred.reject(error);
2179     });
2181     return deferred.promise;
2182   },
2185 // Alias built-in field types to public API.
2186 for (let property of MetricsStorageSqliteBackend.prototype._BUILTIN_TYPES) {
2187   this.MetricsStorageBackend[property] = MetricsStorageSqliteBackend.prototype[property];