Skip to content
This repository was archived by the owner on Apr 11, 2023. It is now read-only.

Commit 919292d

Browse files
committed
ADD: batchExecuteQuery context function, odata implementation, run one by one fallback when not supported
ADD: batchExecuteQuery unittests
1 parent 57c4cde commit 919292d

File tree

8 files changed

+550
-72
lines changed

8 files changed

+550
-72
lines changed

Types/EntityContext.js

Lines changed: 216 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,76 @@ $data.Class.define('$data.EntityContext', null, null,
685685
callBack = $data.typeSystem.createCallbackSetting(callBack);
686686
var that = this;
687687
var clbWrapper = {};
688-
clbWrapper.success = function (query) {
688+
clbWrapper.success = that.executeQuerySuccess(that, returnTransaction, callBack);
689+
clbWrapper.error = that.executeQueryError(that, returnTransaction, callBack);
690+
691+
var sets = query.getEntitySets();
692+
693+
var authorizedFn = function () {
694+
var ex = true;
695+
var wait = false;
696+
var ctx = that;
697+
698+
var readyFn = function (cancel) {
699+
if (cancel === false) ex = false;
700+
701+
if (ex) {
702+
if (query.transaction) {
703+
if ($data.QueryCache && $data.QueryCache.isInCache(that, query)) {
704+
$data.QueryCache.executeQuery(that, query, clbWrapper);
705+
} else {
706+
ctx.storageProvider.executeQuery(query, clbWrapper);
707+
}
708+
} else {
709+
ctx.beginTransaction(function (tran) {
710+
query.transaction = tran;
711+
if ($data.QueryCache && $data.QueryCache.isInCache(that, query)) {
712+
$data.QueryCache.executeQuery(that, query, clbWrapper);
713+
} else {
714+
ctx.storageProvider.executeQuery(query, clbWrapper);
715+
}
716+
});
717+
}
718+
} else {
719+
query.rawDataList = [];
720+
query.result = [];
721+
clbWrapper.success(query);
722+
}
723+
};
724+
725+
var i = 0;
726+
var callbackFn = function (cancel) {
727+
if (cancel === false) ex = false;
728+
729+
var es = sets[i];
730+
if (es.beforeRead) {
731+
i++;
732+
var r = es.beforeRead.call(this, sets, query);
733+
if (typeof r === 'function') {
734+
r.call(this, (i < sets.length && ex) ? callbackFn : readyFn, sets, query);
735+
} else {
736+
if (r === false) ex = false;
737+
738+
if (i < sets.length && ex) {
739+
callbackFn();
740+
} else readyFn();
741+
}
742+
} else readyFn();
743+
};
744+
745+
if (sets.length) callbackFn();
746+
else readyFn();
747+
};
748+
749+
if (this.user && this.checkPermission) {
750+
this.checkPermission(query.expression.nodeType === $data.Expressions.ExpressionType.BatchDelete ? $data.Access.DeleteBatch : $data.Access.Read, this.user, sets, {
751+
success: authorizedFn,
752+
error: clbWrapper.error
753+
});
754+
} else authorizedFn();
755+
},
756+
executeQuerySuccess: function (that, returnTransaction, callBack) {
757+
return function (query) {
689758
if ($data.QueryCache && $data.QueryCache.isCacheable(that, query)) {
690759
$data.QueryCache.addToCache(that, query);
691760
}
@@ -762,78 +831,166 @@ $data.Class.define('$data.EntityContext', null, null,
762831
if (sets.length) callbackFn();
763832
else readyFn();
764833
};
765-
766-
clbWrapper.error = function () {
767-
if(returnTransaction)
834+
},
835+
executeQueryError: function (that, returnTransaction, callBack) {
836+
return function () {
837+
if (returnTransaction)
768838
callBack.error.apply(this, arguments);
769839
else
770840
callBack.error.apply(this, Array.prototype.filter.call(arguments, function (p) { return !(p instanceof $data.Transaction); }));
771841
};
772-
var sets = query.getEntitySets();
842+
},
773843

774-
var authorizedFn = function () {
775-
var ex = true;
776-
var wait = false;
777-
var ctx = that;
778844

779-
var readyFn = function (cancel) {
780-
if (cancel === false) ex = false;
845+
batchExecuteQuery: function (queryableOptions, callBack, transaction) {
846+
var pHandler = new $data.PromiseHandler();
847+
var cbWrapper = pHandler.createCallback(callBack);
781848

782-
if (ex) {
783-
if (query.transaction) {
784-
if ($data.QueryCache && $data.QueryCache.isInCache(that, query)) {
785-
$data.QueryCache.executeQuery(that, query, clbWrapper);
786-
} else {
787-
ctx.storageProvider.executeQuery(query, clbWrapper);
788-
}
789-
} else {
790-
ctx.beginTransaction(function (tran) {
791-
query.transaction = tran;
792-
if ($data.QueryCache && $data.QueryCache.isInCache(that, query)) {
793-
$data.QueryCache.executeQuery(that, query, clbWrapper);
794-
} else {
795-
ctx.storageProvider.executeQuery(query, clbWrapper);
796-
}
797-
});
798-
}
799-
} else {
800-
query.rawDataList = [];
801-
query.result = [];
802-
clbWrapper.success(query);
803-
}
804-
};
849+
var self = this;
850+
var methodOperationMappings = {
851+
count: 'length',
852+
length: 'length',
853+
forEach: 'forEach',
854+
toArray: 'toArray',
855+
single: 'single',
856+
some: 'some',
857+
every: 'every',
858+
first: 'first',
859+
removeAll: 'batchDelete'
860+
};
861+
var methodFrameMappings = {
862+
count: 'CountExpression',
863+
length: 'CountExpression',
864+
forEach: 'ForEachExpression',
865+
toArray: 'ToArrayExpression',
866+
single: 'SingleExpression',
867+
some: 'SomeExpression',
868+
every: 'EveryExpression',
869+
first: 'FirstExpression',
870+
removeAll: 'BatchDeleteExpression'
871+
};
805872

806-
var i = 0;
807-
var callbackFn = function (cancel) {
808-
if (cancel === false) ex = false;
809873

810-
var es = sets[i];
811-
if (es.beforeRead) {
812-
i++;
813-
var r = es.beforeRead.call(this, sets, query);
814-
if (typeof r === 'function') {
815-
r.call(this, (i < sets.length && ex) ? callbackFn : readyFn, sets, query);
816-
} else {
817-
if (r === false) ex = false;
874+
var returnFunc = function () {
875+
return pHandler.getPromise();
876+
}
818877

819-
if (i < sets.length && ex) {
820-
callbackFn();
821-
} else readyFn();
878+
if (typeof queryableOptions.length != "number") {
879+
cbWrapper.error(new Exception('QueryableOptions array parameter missing', 'Invalid arguments'));
880+
return returnFunc();
881+
}
882+
883+
var qOptions = [];
884+
for (var i = 0; i < queryableOptions.length; i++) {
885+
var queryOption = {};
886+
if (queryableOptions[i] instanceof $data.Queryable) {
887+
queryOption.queryable = queryableOptions[i];
888+
queryOption.method = 'toArray';
889+
} else if (queryableOptions[i].queryable instanceof $data.Queryable) {
890+
queryOption.queryable = queryableOptions[i].queryable;
891+
queryOption.method = queryableOptions[i].method || 'toArray';
892+
} else if (queryableOptions[i][0] instanceof $data.Queryable) {
893+
queryOption.queryable = queryableOptions[i][0];
894+
queryOption.method = queryableOptions[i][1] || 'toArray';
895+
} else {
896+
cbWrapper.error(new Exception('$data.Queryable is missing in queryableOptions at index ' + i, 'Invalid arguments'));
897+
return returnFunc();
898+
}
899+
900+
if (queryOption.queryable.entityContext !== self) {
901+
cbWrapper.error(new Exception('Queryable at index ' + i + ' contains different entity context', 'Invalid arguments'));
902+
return returnFunc();
903+
}
904+
905+
queryOption.queryable._checkOperation(methodOperationMappings[queryOption.method] || queryOption.method);
906+
qOptions.push(queryOption);
907+
}
908+
909+
910+
var executableQueries = [];
911+
for (var i = 0; i < qOptions.length; i++) {
912+
var queryOption = qOptions[i];
913+
914+
var frameExpressionName = methodFrameMappings[queryOption.method] || queryOption.method;
915+
if (frameExpressionName && $data.Expressions[frameExpressionName] && $data.Expressions[frameExpressionName].isAssignableTo($data.Expressions.FrameOperator)) {
916+
917+
var queryExpression = Container['create' + frameExpressionName](queryOption.queryable.expression);
918+
var preparator = Container.createQueryExpressionCreator(queryOption.queryable.entityContext);
919+
920+
try {
921+
var expression = preparator.Visit(queryExpression);
922+
queryOption.queryable.entityContext.log({ event: "EntityExpression", data: expression });
923+
924+
var queryable = Container.createQueryable(queryOption.queryable, expression);
925+
executableQueries.push(queryable);
926+
} catch (e) {
927+
cbWrapper.error(e);
928+
return returnFunc();
929+
}
930+
} else {
931+
cbWrapper.error(new Exception('Invalid frame method \'' + frameExpressionName + '\' in queryableOptions at index ' + i, 'Invalid arguments'));
932+
return returnFunc();
933+
}
934+
}
935+
936+
var queryResults = [];
937+
if (self.storageProvider.supportedContextOperation && self.storageProvider.supportedContextOperation.batchExecuteQuery) {
938+
//wrap queries
939+
var batchExecuteQueryExpression = Container.createBatchExecuteQueryExpression(executableQueries.map(function (queryable) {
940+
return new $data.Query(queryable.expression, queryable.defaultType, self);
941+
}));
942+
943+
var batchExecuteQuery = Container.createQueryable(self, batchExecuteQueryExpression);
944+
self.executeQuery(batchExecuteQuery, {
945+
success: function (results) {
946+
var batchResult = [];
947+
var hasError = false;
948+
for (var i = 0; i < results.length && !hasError; i++) {
949+
var query = results[i];
950+
self.executeQuerySuccess(self, returnTransaction, {
951+
success: function (result) {
952+
batchResult.push(result);
953+
},
954+
error: function () {
955+
hasError = true;
956+
}
957+
})(query);
958+
}
959+
if (!hasError) {
960+
self._applyTransaction(cbWrapper, cbWrapper.success, [batchResult], batchExecuteQuery.transaction, returnTransaction);
822961
}
823-
} else readyFn();
824-
};
825962

826-
if (sets.length) callbackFn();
827-
else readyFn();
828-
};
963+
},
964+
error: cbWrapper.error
965+
}, transaction);
966+
} else {
967+
var returnTransaction = this._isReturnTransaction(transaction);
968+
969+
var readIterator = function (queries, index, iteratorCallback, itTransaction) {
970+
var query = queries[index];
971+
if (!query) {
972+
return iteratorCallback.success(itTransaction);
973+
}
829974

830-
if (this.user && this.checkPermission) {
831-
this.checkPermission(query.expression.nodeType === $data.Expressions.ExpressionType.BatchDelete ? $data.Access.DeleteBatch : $data.Access.Read, this.user, sets, {
832-
success: authorizedFn,
833-
error: clbWrapper.error
834-
});
835-
} else authorizedFn();
975+
self.executeQuery(executableQueries[index], {
976+
success: function (result, tr) {
977+
queryResults.push(result);
978+
readIterator(executableQueries, index + 1, iteratorCallback, tr);
979+
},
980+
error: iteratorCallback.error
981+
}, itTransaction);
982+
}
983+
984+
readIterator(executableQueries, 0, {
985+
success: function (lastTran) {
986+
self._applyTransaction(cbWrapper, cbWrapper.success, [queryResults], lastTran, returnTransaction);
987+
},
988+
error: cbWrapper.error
989+
}, transaction)
990+
}
991+
return returnFunc();
836992
},
993+
837994
saveChanges: function (callback, transaction) {
838995
/// <signature>
839996
/// <summary>

Types/Expressions/EntityExpressions/EntityExpressionVisitor.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@ $C('$data.Expressions.EntityExpressionVisitor', null, null, {
9292
return expression;
9393
},
9494

95+
VisitBatchExecuteQueryExpression: function (expression, context) {
96+
var newQueries = expression.members.map(function (expr) {
97+
return this.Visit(expr, context);
98+
}, this);
99+
100+
var equal = true;
101+
for (var i = 0; i < expression.members.length; i++) {
102+
equal = equal && (expression.members[i] === newQueries[i]);
103+
}
104+
if (!equal) {
105+
return Container.createBatchExecuteQueryExpression(newQueries);
106+
}
107+
108+
return expression;
109+
},
110+
95111
VisitObjectLiteralExpression: function (expression, context) {
96112
var newValues = expression.members.map(function (ofe) {
97113
return this.Visit(ofe, context);

Types/Expressions/EntityExpressions/FilterExpression.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ $C('$data.Expressions.InlineCountExpression', $data.Expressions.EntitySetExpress
2020
nodeType: { value: $data.Expressions.ExpressionType.InlineCount, enumerable: true }
2121
});
2222

23+
$C('$data.Expressions.BatchExecuteQueryExpression', $data.Expressions.ExpressionNode, null, {
24+
constructor: function (members) {
25+
this.members = members;
26+
},
27+
nodeType: { value: $data.Expressions.ExpressionType.BatchExecuteQuery, enumerable: true }
28+
});
29+
2330
$C('$data.Expressions.FrameOperator', $data.Expressions.ExpressionNode, null, {
2431
constructor: function () {
2532
this.isTerminated = true;

Types/Expressions/ExpressionNode2.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ $data.Class.define("$data.Expressions.ExpressionType", null, null, {}, {
5959
First: "First",
6060
Count: "Count",
6161
InlineCount: "InlineCount",
62+
BatchExecuteQuery: "BatchExecuteQuery",
6263
Single: "Single",
6364
Find: "Find",
6465
Some: "Some",

Types/Query.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ $C('$data.Query', null, null,
3636
ret.push(ctx._entitySetReferences[expression.elementType.name]);
3737
}
3838
if (expression.source) fn(expression.source);
39+
if (expression.members) {
40+
for (var i = 0; i < expression.members.length; i++) {
41+
fn(expression.members[i].expression);
42+
}
43+
}
3944
};
4045

4146
fn(this.expression);

0 commit comments

Comments
 (0)