Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(323)

Unified Diff: pkg/gcloud/lib/src/db/db.dart

Issue 804973002: Add appengine/gcloud/mustache dependencies. (Closed) Base URL: git@github.com:dart-lang/pub-dartlang-dart.git@master
Patch Set: Added AUTHORS/LICENSE/PATENTS files Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/gcloud/lib/src/db/annotations.dart ('k') | pkg/gcloud/lib/src/db/model_db.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/gcloud/lib/src/db/db.dart
diff --git a/pkg/gcloud/lib/src/db/db.dart b/pkg/gcloud/lib/src/db/db.dart
new file mode 100644
index 0000000000000000000000000000000000000000..07c5efb7b5ac3b7bafedfcd0da4508e144c370c3
--- /dev/null
+++ b/pkg/gcloud/lib/src/db/db.dart
@@ -0,0 +1,382 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of gcloud.db;
+
+/**
+ * A function definition for transactional functions.
+ *
+ * The function will be given a [Transaction] object which can be used to make
+ * lookups/queries and queue modifications (inserts/updates/deletes).
+ */
+typedef Future TransactionHandler(Transaction transaction);
+
+/**
+ * A datastore transaction.
+ *
+ * It can be used for making lookups/queries and queue modifcations
+ * (inserts/updates/deletes). Finally the transaction can be either committed
+ * or rolled back.
+ */
+class Transaction {
+ static const int _TRANSACTION_STARTED = 0;
+ static const int _TRANSACTION_ROLLED_BACK = 1;
+ static const int _TRANSACTION_COMMITTED = 2;
+
+ final DatastoreDB db;
+ final datastore.Transaction _datastoreTransaction;
+
+ final List<Model> _inserts = [];
+ final List<Key> _deletes = [];
+
+ int _transactionState = _TRANSACTION_STARTED;
+
+ Transaction(this.db, this._datastoreTransaction);
+
+ /**
+ * Looks up [keys] within this transaction.
+ */
+ Future<List<Model>> lookup(List<Key> keys) {
+ return _lookupHelper(db, keys, datastoreTransaction: _datastoreTransaction);
+ }
+
+ /**
+ * Enqueues [inserts] and [deletes] which should be commited at commit time.
+ */
+ void queueMutations({List<Model> inserts, List<Key> deletes}) {
+ _checkSealed();
+ if (inserts != null) {
+ _inserts.addAll(inserts);
+ }
+ if (deletes != null) {
+ _deletes.addAll(deletes);
+ }
+ }
+
+ /**
+ * Query for [kind] models with [ancestorKey].
+ *
+ * Note that [ancestorKey] is required, since a transaction is not allowed to
+ * touch/look at an arbitrary number of rows.
+ */
+ Query query(Type kind, Key ancestorKey, {Partition partition}) {
+ _checkSealed();
+ return new Query(db,
+ kind,
+ partition: partition,
+ ancestorKey: ancestorKey,
+ datastoreTransaction: _datastoreTransaction);
+ }
+
+ /**
+ * Rolls this transaction back.
+ */
+ Future rollback() {
+ _checkSealed(changeState: _TRANSACTION_ROLLED_BACK);
+ return db.datastore.rollback(_datastoreTransaction);
+ }
+
+ /**
+ * Commits this transaction including all of the queued mutations.
+ */
+ Future commit() {
+ _checkSealed(changeState: _TRANSACTION_COMMITTED);
+ return _commitHelper(db,
+ inserts: _inserts,
+ deletes: _deletes,
+ datastoreTransaction: _datastoreTransaction);
+ }
+
+ _checkSealed({int changeState}) {
+ if (_transactionState == _TRANSACTION_COMMITTED) {
+ throw new StateError(
+ 'The transaction has already been committed.');
+ } else if (_transactionState == _TRANSACTION_ROLLED_BACK) {
+ throw new StateError(
+ 'The transaction has already been rolled back.');
+ }
+ if (changeState != null) {
+ _transactionState = changeState;
+ }
+ }
+}
+
+class Query {
+ final _relationMapping = const <String, datastore.FilterRelation> {
+ '<': datastore.FilterRelation.LessThan,
+ '<=': datastore.FilterRelation.LessThanOrEqual,
+ '>': datastore.FilterRelation.GreatherThan,
+ '>=': datastore.FilterRelation.GreatherThanOrEqual,
+ '=': datastore.FilterRelation.Equal,
+ 'IN': datastore.FilterRelation.In,
+ };
+
+ final DatastoreDB _db;
+ final datastore.Transaction _transaction;
+ final String _kind;
+
+ final Partition _partition;
+ final Key _ancestorKey;
+
+ final List<datastore.Filter> _filters = [];
+ final List<datastore.Order> _orders = [];
+ int _offset;
+ int _limit;
+
+ Query(DatastoreDB dbImpl, Type kind,
+ {Partition partition, Key ancestorKey,
+ datastore.Transaction datastoreTransaction})
+ : _db = dbImpl,
+ _kind = dbImpl.modelDB.kindName(kind),
+ _partition = partition,
+ _ancestorKey = ancestorKey, _transaction = datastoreTransaction;
+
+ /**
+ * Adds a filter to this [Query].
+ *
+ * [filterString] has form "name OP" where 'name' is a fieldName of the
+ * model and OP is an operator. The following operators are supported:
+ *
+ * * '<' (less than)
+ * * '<=' (less than or equal)
+ * * '>' (greater than)
+ * * '>=' (greater than or equal)
+ * * '=' (equal)
+ * * 'IN' (in - `comparisonObject` must be a list)
+ *
+ * [comparisonObject] is the object for comparison.
+ */
+ void filter(String filterString, Object comparisonObject) {
+ var parts = filterString.split(' ');
+ if (parts.length != 2 || !_relationMapping.containsKey(parts[1])) {
+ throw new ArgumentError(
+ "Invalid filter string '$filterString'.");
+ }
+
+ // TODO: do value transformation on [comparisionObject]
+
+ var propertyName = _convertToDatastoreName(parts[0]);
+ _filters.add(new datastore.Filter(
+ _relationMapping[parts[1]], propertyName, comparisonObject));
+ }
+
+ /**
+ * Adds an order to this [Query].
+ *
+ * [orderString] has the form "-name" where 'name' is a fieldName of the model
+ * and the optional '-' says whether the order is decending or ascending.
+ */
+ void order(String orderString) {
+ // TODO: validate [orderString] (e.g. is name valid)
+ if (orderString.startsWith('-')) {
+ _orders.add(new datastore.Order(
+ datastore.OrderDirection.Decending,
+ _convertToDatastoreName(orderString.substring(1))));
+ } else {
+ _orders.add(new datastore.Order(
+ datastore.OrderDirection.Ascending,
+ _convertToDatastoreName(orderString)));
+ }
+ }
+
+ /**
+ * Sets the [offset] of this [Query].
+ *
+ * When running this query, [offset] results will be skipped.
+ */
+ void offset(int offset) {
+ _offset = offset;
+ }
+
+ /**
+ * Sets the [limit] of this [Query].
+ *
+ * When running this query, a maximum of [limit] results will be returned.
+ */
+ void limit(int limit) {
+ _limit = limit;
+ }
+
+ /**
+ * Execute this [Query] on the datastore.
+ *
+ * Outside of transactions this method might return stale data or may not
+ * return the newest updates performed on the datastore since updates
+ * will be reflected in the indices in an eventual consistent way.
+ */
+ Stream<Model> run() {
+ var ancestorKey;
+ if (_ancestorKey != null) {
+ ancestorKey = _db.modelDB.toDatastoreKey(_ancestorKey);
+ }
+ var query = new datastore.Query(
+ ancestorKey: ancestorKey, kind: _kind,
+ filters: _filters, orders: _orders,
+ offset: _offset, limit: _limit);
+
+ var partition;
+ if (_partition != null) {
+ partition = new datastore.Partition(_partition.namespace);
+ }
+
+ return new StreamFromPages((int pageSize) {
+ return _db.datastore.query(
+ query, transaction: _transaction, partition: partition);
+ }).stream.map(_db.modelDB.fromDatastoreEntity);
+ }
+
+ // TODO:
+ // - add runPaged() returning Page<Model>
+ // - add run*() method once we have EntityResult{Entity,Cursor} in low-level
+ // API.
+
+ String _convertToDatastoreName(String name) {
+ var propertyName =
+ _db.modelDB.fieldNameToPropertyName(_kind, name);
+ if (propertyName == null) {
+ throw new ArgumentError(
+ "Field $name is not available for kind $_kind");
+ }
+ return propertyName;
+ }
+}
+
+class DatastoreDB {
+ final datastore.Datastore datastore;
+ final ModelDB _modelDB;
+ Partition _defaultPartition;
+
+ DatastoreDB(this.datastore, {ModelDB modelDB})
+ : _modelDB = modelDB != null ? modelDB : new ModelDBImpl() {
+ _defaultPartition = new Partition(null);
+ }
+
+ /**
+ * The [ModelDB] used to serialize/deserialize objects.
+ */
+ ModelDB get modelDB => _modelDB;
+
+ /**
+ * Gets the empty key using the default [Partition].
+ *
+ * Model keys with parent set to [emptyKey] will create their own entity
+ * groups.
+ */
+ Key get emptyKey => defaultPartition.emptyKey;
+
+ /**
+ * Gets the default [Partition].
+ */
+ Partition get defaultPartition => _defaultPartition;
+
+ /**
+ * Creates a new [Partition] with namespace [namespace].
+ */
+ Partition newPartition(String namespace) {
+ return new Partition(namespace);
+ }
+
+ /**
+ * Begins a new a new transaction.
+ *
+ * A transaction can touch only a limited number of entity groups. This limit
+ * is currently 5.
+ */
+ // TODO: Add retries and/or auto commit/rollback.
+ Future withTransaction(TransactionHandler transactionHandler) {
+ return datastore.beginTransaction(crossEntityGroup: true)
+ .then((datastoreTransaction) {
+ var transaction = new Transaction(this, datastoreTransaction);
+ return transactionHandler(transaction);
+ });
+ }
+
+ /**
+ * Build a query for [kind] models.
+ */
+ Query query(Type kind, {Partition partition, Key ancestorKey}) {
+ return new Query(this,
+ kind,
+ partition: partition,
+ ancestorKey: ancestorKey);
+ }
+
+ /**
+ * Looks up [keys] in the datastore and returns a list of [Model] objects.
+ *
+ * For transactions, please use [beginTransaction] and call the [lookup]
+ * method on it's returned [Transaction] object.
+ */
+ Future<List<Model>> lookup(List<Key> keys) {
+ return _lookupHelper(this, keys);
+ }
+
+ /**
+ * Add [inserts] to the datastore and remove [deletes] from it.
+ *
+ * The order of inserts and deletes is not specified. When the commit is done
+ * direct lookups will see the effect but non-ancestor queries will see the
+ * change in an eventual consistent way.
+ *
+ * For transactions, please use `beginTransaction` and it's returned
+ * [Transaction] object.
+ */
+ Future commit({List<Model> inserts, List<Key> deletes}) {
+ return _commitHelper(this, inserts: inserts, deletes: deletes);
+ }
+}
+
+Future _commitHelper(DatastoreDB db,
+ {List<Model> inserts,
+ List<Key> deletes,
+ datastore.Transaction datastoreTransaction}) {
+ var entityInserts, entityAutoIdInserts, entityDeletes;
+ var autoIdModelInserts;
+ if (inserts != null) {
+ entityInserts = [];
+ entityAutoIdInserts = [];
+ autoIdModelInserts = [];
+
+ for (var model in inserts) {
+ // If parent was not explicity set, we assume this model will map to
+ // it's own entity group.
+ if (model.parentKey == null) {
+ model.parentKey = db.defaultPartition.emptyKey;
+ }
+ if (model.id == null) {
+ autoIdModelInserts.add(model);
+ entityAutoIdInserts.add(db.modelDB.toDatastoreEntity(model));
+ } else {
+ entityInserts.add(db.modelDB.toDatastoreEntity(model));
+ }
+ }
+ }
+ if (deletes != null) {
+ entityDeletes = deletes.map(db.modelDB.toDatastoreKey).toList();
+ }
+
+ return db.datastore.commit(inserts: entityInserts,
+ autoIdInserts: entityAutoIdInserts,
+ deletes: entityDeletes,
+ transaction: datastoreTransaction)
+ .then((datastore.CommitResult result) {
+ if (entityAutoIdInserts != null && entityAutoIdInserts.length > 0) {
+ for (var i = 0; i < result.autoIdInsertKeys.length; i++) {
+ var key = db.modelDB.fromDatastoreKey(result.autoIdInsertKeys[i]);
+ autoIdModelInserts[i].parentKey = key.parent;
+ autoIdModelInserts[i].id = key.id;
+ }
+ }
+ });
+}
+
+Future<List<Model>> _lookupHelper(
+ DatastoreDB db, List<Key> keys,
+ {datastore.Transaction datastoreTransaction}) {
+ var entityKeys = keys.map(db.modelDB.toDatastoreKey).toList();
+ return db.datastore.lookup(entityKeys, transaction: datastoreTransaction)
+ .then((List<datastore.Entity> entities) {
+ return entities.map(db.modelDB.fromDatastoreEntity).toList();
+ });
+}
« no previous file with comments | « pkg/gcloud/lib/src/db/annotations.dart ('k') | pkg/gcloud/lib/src/db/model_db.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698