Skip to content

Commit 8462d84

Browse files
authored
Merge pull request #274 from ml054/RDB-424
RDBC-424
2 parents e650b68 + ac96feb commit 8462d84

File tree

9 files changed

+416
-160
lines changed

9 files changed

+416
-160
lines changed

src/Documents/Commands/GetDocumentsCommand.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import { HashCalculator } from "../Queries/HashCalculator";
1414
import { IRavenObject } from "../../Types/IRavenObject";
1515
import { TimeSeriesRange } from "../Operations/TimeSeries/TimeSeriesRange";
1616
import { DateUtil } from "../../Utility/DateUtil";
17+
import { readToEnd, stringToReadable } from "../../Utility/StreamUtil";
18+
import { ServerResponse } from "../../Types";
19+
import { QueryResult } from "../Queries/QueryResult";
20+
import { QueryCommand } from "./QueryCommand";
21+
import { ObjectUtil } from "../../Utility/ObjectUtil";
1722

1823
export interface GetDocumentsCommandCounterOptions {
1924
counterIncludes?: string[];
@@ -274,11 +279,39 @@ export class GetDocumentsCommand extends RavenCommand<GetDocumentsResult> {
274279
conventions: DocumentConventions,
275280
bodyCallback?: (body: string) => void): Promise<GetDocumentsResult> {
276281

277-
return RavenCommandResponsePipeline.create<GetDocumentsResult>()
278-
.collectBody(bodyCallback)
279-
.parseJsonAsync()
280-
.jsonKeysTransform("DocumentLoad", conventions)
281-
.process(bodyStream);
282+
const body = await readToEnd(bodyStream);
283+
bodyCallback?.(body);
284+
285+
let parsedJson: any;
286+
if (body.length > conventions.syncJsonParseLimit) {
287+
const bodyStreamCopy = stringToReadable(body);
288+
// response is quite big - fallback to async (slower) parsing to avoid blocking event loop
289+
parsedJson = await RavenCommandResponsePipeline.create<ServerResponse<GetDocumentsResult>>()
290+
.parseJsonAsync()
291+
.process(bodyStreamCopy);
292+
} else {
293+
parsedJson = JSON.parse(body);
294+
}
295+
296+
return GetDocumentsCommand._mapToLocalObject(parsedJson, conventions);
297+
}
298+
299+
private static _mapToLocalObject(json: any, conventions: DocumentConventions): GetDocumentsResult {
300+
const mappedIncludes: Record<string, any> = {};
301+
if (json.Includes) {
302+
for (const [key, value] of Object.entries(json.Includes)) {
303+
mappedIncludes[key] = ObjectUtil.transformDocumentKeys(value, conventions);
304+
}
305+
}
306+
307+
return {
308+
results: json.Results.map(x => ObjectUtil.transformDocumentKeys(x, conventions)),
309+
includes: mappedIncludes,
310+
compareExchangeValueIncludes: ObjectUtil.mapCompareExchangeToLocalObject(json.CompareExchangeValueIncludes),
311+
timeSeriesIncludes: ObjectUtil.mapTimeSeriesIncludesToLocalObject(json.TimeSeriesIncludes),
312+
counterIncludes: ObjectUtil.mapCounterIncludesToLocalObject(json.CounterIncludes),
313+
nextPageStart: json.NextPageStart
314+
};
282315
}
283316

284317
public get isReadRequest(): boolean {

src/Documents/Commands/QueryCommand.ts

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import { StringBuilder } from "../../Utility/StringBuilder";
1212
import { ServerResponse } from "../../Types";
1313
import { QueryTimings } from "../Queries/Timings/QueryTimings";
1414
import { StringUtil } from "../../Utility/StringUtil";
15+
import { CounterDetail } from "../Operations/Counters/CounterDetail";
16+
import { CompareExchangeResultItem } from "../Operations/CompareExchange/CompareExchangeValueResultParser";
17+
import { TimeSeriesRangeResult } from "../Operations/TimeSeries/TimeSeriesRangeResult";
18+
import { TimeSeriesEntry } from "../Session/TimeSeries/TimeSeriesEntry";
19+
import { readToEnd, stringToReadable } from "../../Utility/StreamUtil";
20+
import { ObjectUtil } from "../../Utility/ObjectUtil";
1521

1622
export interface QueryCommandOptions {
1723
metadataOnly?: boolean;
@@ -105,13 +111,21 @@ export class QueryCommand extends RavenCommand<QueryResult> {
105111
fromCache: boolean,
106112
bodyCallback?: (body: string) => void): Promise<QueryResult> {
107113

108-
const rawResult = await RavenCommandResponsePipeline.create<ServerResponse<QueryResult>>()
109-
.collectBody(bodyCallback)
110-
.parseJsonAsync()
111-
.jsonKeysTransform("DocumentQuery", conventions)
112-
.process(bodyStream);
114+
const body = await readToEnd(bodyStream);
115+
bodyCallback?.(body);
116+
117+
let parsedJson: any;
118+
if (body.length > conventions.syncJsonParseLimit) {
119+
const bodyStreamCopy = stringToReadable(body);
120+
// response is quite big - fallback to async (slower) parsing to avoid blocking event loop
121+
parsedJson = await RavenCommandResponsePipeline.create<ServerResponse<QueryResult>>()
122+
.parseJsonAsync()
123+
.process(bodyStreamCopy);
124+
} else {
125+
parsedJson = JSON.parse(body);
126+
}
113127

114-
const queryResult = QueryCommand._mapToLocalObject(rawResult, conventions);
128+
const queryResult = QueryCommand._mapToLocalObject(parsedJson, conventions);
115129

116130
if (fromCache) {
117131
queryResult.durationInMs = -1;
@@ -125,31 +139,57 @@ export class QueryCommand extends RavenCommand<QueryResult> {
125139
return queryResult;
126140
}
127141

128-
private static _mapToLocalObject(json: ServerResponse<QueryResult>, conventions: DocumentConventions): QueryResult {
129-
const { indexTimestamp, lastQueryTime, timings, ...otherProps } = json;
130-
131-
const overrides: Partial<QueryResult> = {
132-
indexTimestamp: conventions.dateUtil.parse(indexTimestamp),
133-
lastQueryTime: conventions.dateUtil.parse(lastQueryTime),
134-
timings: QueryCommand._mapTimingsToLocalObject(timings)
135-
};
136-
137-
return Object.assign(new QueryResult(), otherProps, overrides);
138-
}
139-
140-
private static _mapTimingsToLocalObject(timings: ServerResponse<QueryTimings>) {
142+
//TODO: use ServerCasing<QueryTimings> instead of any, after upgrading to TS 4.2
143+
private static _mapTimingsToLocalObject(timings: any) {
141144
if (!timings) {
142145
return undefined;
143146
}
144147

145148
const mapped = new QueryTimings();
146-
mapped.durationInMs = timings.durationInMs;
147-
mapped.timings = timings.timings ? {} : undefined;
148-
if (timings.timings) {
149-
Object.keys(timings.timings).forEach(time => {
150-
mapped.timings[StringUtil.uncapitalize(time)] = QueryCommand._mapTimingsToLocalObject(timings.timings[time]);
149+
mapped.durationInMs = timings.DurationInMs;
150+
mapped.timings = timings.Timings ? {} : undefined;
151+
if (timings.Timings) {
152+
Object.keys(timings.Timings).forEach(time => {
153+
mapped.timings[StringUtil.uncapitalize(time)] = QueryCommand._mapTimingsToLocalObject(timings.Timings[time]);
151154
});
152155
}
153156
return mapped;
154157
}
158+
159+
160+
//TODO: use ServerCasing<ServerResponse<QueryResult>> instead of any, after upgrading to TS 4.2
161+
private static _mapToLocalObject(json: any, conventions: DocumentConventions): QueryResult {
162+
const mappedIncludes: Record<string, any> = {};
163+
if (json.Includes) {
164+
for (const [key, value] of Object.entries(json.Includes)) {
165+
mappedIncludes[key] = ObjectUtil.transformDocumentKeys(value, conventions);
166+
}
167+
}
168+
169+
const props: Omit<QueryResult, "scoreExplanations" | "cappedMaxResults" | "createSnapshot" | "resultSize"> = {
170+
results: json.Results.map(x => ObjectUtil.transformDocumentKeys(x, conventions)),
171+
includes: mappedIncludes,
172+
indexName: json.IndexName,
173+
indexTimestamp: conventions.dateUtil.parse(json.IndexTimestamp),
174+
includedPaths: json.IncludedPaths,
175+
isStale: json.IsStale,
176+
skippedResults: json.SkippedResults,
177+
totalResults: json.TotalResults,
178+
highlightings: json.Highlightings,
179+
explanations: json.Explanations,
180+
timingsInMs: json.TimingsInMs,
181+
lastQueryTime: conventions.dateUtil.parse(json.LastQueryTime),
182+
durationInMs: json.DurationInMs,
183+
resultEtag: json.ResultEtag,
184+
nodeTag: json.NodeTag,
185+
counterIncludes: ObjectUtil.mapCounterIncludesToLocalObject(json.CounterIncludes),
186+
includedCounterNames: json.IncludedCounterNames,
187+
timeSeriesIncludes: ObjectUtil.mapTimeSeriesIncludesToLocalObject(json.TimeSeriesIncludes),
188+
compareExchangeValueIncludes: ObjectUtil.mapCompareExchangeToLocalObject(json.CompareExchangeValueIncludes),
189+
timeSeriesFields: json.TimeSeriesFields,
190+
timings: QueryCommand._mapTimingsToLocalObject(json.Timings)
191+
}
192+
193+
return Object.assign(new QueryResult(), props);
194+
}
155195
}

src/Documents/Conventions/DocumentConventions.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export class DocumentConventions {
9090

9191
private _objectMapper: TypesAwareObjectMapper;
9292
private _dateUtil: DateUtil;
93+
private _syncJsonParseLimit: number;
9394

9495
private _useCompression: boolean;
9596
private _sendApplicationIdentifier: boolean;
@@ -147,6 +148,7 @@ export class DocumentConventions {
147148

148149
this._dateUtilOpts = {};
149150
this._dateUtil = new DateUtil(this._dateUtilOpts);
151+
this._syncJsonParseLimit = 2 * 1_024 * 1_024;
150152

151153
this._firstBroadcastAttemptTimeout = 5_000;
152154
this._secondBroadcastAttemptTimeout = 30_000;
@@ -239,6 +241,23 @@ export class DocumentConventions {
239241
this._objectMapper = value;
240242
}
241243

244+
/**
245+
* Sets json length limit for sync parsing. Beyond that size
246+
* we fallback to async parsing
247+
*/
248+
public get syncJsonParseLimit(): number {
249+
return this._syncJsonParseLimit;
250+
}
251+
252+
/**
253+
* Gets json length limit for sync parsing. Beyond that size
254+
* we fallback to async parsing
255+
*/
256+
public set syncJsonParseLimit(value: number) {
257+
this._assertNotFrozen();
258+
this._syncJsonParseLimit = value;
259+
}
260+
242261
public get dateUtil(): DateUtil {
243262
return this._dateUtil;
244263
}

src/Documents/Identity/GenerateEntityIdOnTheClient.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,6 @@ export class GenerateEntityIdOnTheClient {
7676
* Tries to set the identity property
7777
*/
7878
public trySetIdentity(entity: object, id: string, isProjection: boolean = false): void {
79-
this._trySetIdentityInternal(entity, id, isProjection);
80-
}
81-
82-
private _trySetIdentityInternal(entity: object, id: string, isProjection): void {
8379
const docType: DocumentType = TypeUtil.findType(entity, this._conventions.knownEntityTypes);
8480
const identityProperty = this._conventions.getIdentityProperty(docType);
8581

src/Mapping/Json/Docs/index.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/Mapping/Json/Streams/TransformJsonKeysProfiles.ts

Lines changed: 0 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export type TransformJsonKeysProfile =
66
"CommandResponsePayload"
77
| "NoChange"
88
| "DocumentLoad"
9-
| "DocumentQuery"
109
| "FacetQuery"
1110
| "Patch"
1211
| "CompareExchangeValue"
@@ -41,15 +40,6 @@ export function getTransformJsonKeysProfile(
4140
return { getCurrentTransform };
4241
}
4342

44-
if (profile === "DocumentQuery") {
45-
if (!conventions) {
46-
throwError("InvalidArgumentException", "Document conventions are required for this profile.");
47-
}
48-
49-
const getCurrentTransform = buildEntityKeysTransformForDocumentQuery(conventions.entityFieldNameConvention);
50-
return { getCurrentTransform };
51-
}
52-
5343
if (profile === "FacetQuery") {
5444
return {
5545
getCurrentTransform: facetQueryGetTransform
@@ -381,97 +371,6 @@ function buildEntityKeysTransformForDocumentLoad(entityCasingConvention) {
381371
};
382372
}
383373

384-
function buildEntityKeysTransformForDocumentQuery(entityCasingConvention: CasingConvention) {
385-
return function entityKeysTransform(key, stack) {
386-
const len = stack.length;
387-
388-
if (len === 1) {
389-
// Results, Includes, Timings...
390-
return "camel";
391-
}
392-
393-
// len === 2 is array index
394-
if (stack[0] === "Results" || stack[0] === "Includes") {
395-
if (len === 3) {
396-
// top document level
397-
return key === "@metadata" ? null : entityCasingConvention;
398-
}
399-
400-
if (len === 4) {
401-
if (stack[2] === "@metadata") {
402-
// handle @metadata object keys
403-
if (key[0] === "@" || key === "Raven-Node-Type") {
404-
return null;
405-
}
406-
}
407-
}
408-
409-
if (len === 5) {
410-
// do not touch @nested-object-types keys
411-
if (stack[len - 2] === "@nested-object-types") {
412-
return null;
413-
}
414-
}
415-
416-
if (len === 6) {
417-
// @metadata.@attachments.[].name
418-
if (stack[2] === "@metadata") {
419-
if (stack[3] === "@attachments") {
420-
return "camel";
421-
}
422-
423-
return null;
424-
}
425-
}
426-
}
427-
428-
if (stack[0] === "CounterIncludes") {
429-
if (len === 2 || len === 3) {
430-
return null;
431-
}
432-
if (len === 4) {
433-
return "camel";
434-
}
435-
}
436-
437-
if (stack[0] === "IncludedCounterNames") {
438-
if (len === 2) {
439-
return null;
440-
}
441-
}
442-
443-
if (len === 3) {
444-
if (stack[0] === "CompareExchangeValueIncludes") {
445-
return "camel";
446-
}
447-
}
448-
449-
if (len === 4) {
450-
if (stack[0] === "CompareExchangeValueIncludes" && stack[2] === "Value" && stack[3] === "Object") {
451-
return "camel";
452-
}
453-
}
454-
455-
if (len === 5) {
456-
if (stack[0] === "TimeSeriesIncludes") {
457-
return "camel";
458-
}
459-
}
460-
461-
if (len === 7) {
462-
if (stack[0] === "TimeSeriesIncludes") {
463-
return "camel";
464-
}
465-
}
466-
467-
if (stack[0] === "Timings") {
468-
return "camel";
469-
}
470-
471-
return entityCasingConvention;
472-
};
473-
}
474-
475374
function handleMetadataJsonKeys(key: string, stack: string[], stackLength: number, metadataKeyLevel: number): CasingConvention {
476375
if (stackLength === metadataKeyLevel) {
477376
return null; // don't touch @metadata key

src/Mapping/ObjectMapper.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,7 @@ export class TypesAwareObjectMapper implements ITypesAwareObjectMapper {
371371
throwError("InvalidArgumentException", "ctor argument must not be null or undefined.");
372372
}
373373

374-
// tslint:disable-next-line:new-parens
375-
return new (Function.prototype.bind.apply(ctor)) as TResult;
374+
return new ctor() as TResult;
376375
}
377376

378377
private _makeObjectLiteral(

0 commit comments

Comments
 (0)