Skip to content

Commit 3f1e9ca

Browse files
authored
Merge pull request #170 from gregolsky/v4.1
RDBC-247: add singleOrNull() and firstOrNull(), verify single() and f…
2 parents 4b2e82e + e6dc1da commit 3f1e9ca

File tree

5 files changed

+200
-35
lines changed

5 files changed

+200
-35
lines changed

src/Documents/Session/AbstractDocumentQuery.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,16 @@ import { SuggestionWithTerm } from "../Queries/Suggestions/SuggestionWithTerm";
6969
import { SuggestionWithTerms } from "../Queries/Suggestions/SuggestionWithTerms";
7070
import { QueryData } from "../Queries/QueryData";
7171
import { QueryTimings } from "../Queries/Timings/QueryTimings";
72-
import { JsonSerializer } from "../../Mapping/Json/Serializer";
7372
import { Explanations } from "../Queries/Explanation/Explanations";
7473
import { Highlightings } from "../Queries/Highlighting/Hightlightings";
7574
import {
76-
HighlightingOptions,
7775
extractHighlightingOptionsFromParameters } from "../Queries/Highlighting/HighlightingOptions";
7876
import { HighlightingParameters } from "../Queries/Highlighting/HighlightingParameters";
7977
import { QueryHighlightings } from "../Queries/Highlighting/QueryHighlightings";
8078
import { ExplanationOptions } from "../Queries/Explanation/ExplanationOptions";
8179
import { CountersByDocId } from "./CounterInternalTypes";
82-
import { IncludeBuilder } from "./Loaders/IncludeBuilder";
8380
import { IncludeBuilderBase } from "./Loaders/IncludeBuilderBase";
81+
import { passResultToCallback } from "../../Utility/PromiseUtil";
8482

8583
/**
8684
* A query against a Raven index
@@ -1898,28 +1896,51 @@ export abstract class AbstractDocumentQuery<T extends object, TSelf extends Abst
18981896

18991897
public async first(callback?: AbstractCallback<T>): Promise<T> {
19001898
callback = callback || TypeUtil.NOOP;
1901-
const result = BluebirdPromise.resolve()
1902-
.then(() => this._executeQueryOperation(2))
1903-
.then(entries => entries[0] || null)
1904-
.tap(x => callback(null, x))
1905-
.tapCatch(err => callback(err));
1906-
return Promise.resolve(result);
1899+
const resultPromise = this._executeQueryOperation(2)
1900+
.then(entries => {
1901+
if (entries.length === 0) {
1902+
throwError("InvalidOperationException", "Expected at least one result.");
1903+
}
1904+
1905+
return entries[0];
1906+
});
1907+
1908+
passResultToCallback(resultPromise, callback);
1909+
return resultPromise;
1910+
}
1911+
1912+
public async firstOrNull(callback?: AbstractCallback<T>): Promise<T> {
1913+
callback = callback || TypeUtil.NOOP;
1914+
const resultPromise = this._executeQueryOperation(1)
1915+
.then(entries => entries[0] || null);
1916+
1917+
passResultToCallback(resultPromise, callback);
1918+
return resultPromise;
19071919
}
19081920

19091921
public async single(callback?: AbstractCallback<T>): Promise<T> {
19101922
callback = callback || TypeUtil.NOOP;
1911-
const result = BluebirdPromise.resolve()
1912-
.then(() => this._executeQueryOperation(2))
1923+
const resultPromise = this._executeQueryOperation(2)
19131924
.then(entries => {
19141925
if (entries.length !== 1) {
1915-
throw getError("InvalidOperationException", "Expected single result, got: " + entries.length);
1926+
throwError("InvalidOperationException",
1927+
`Expected single result, but got ${ entries.length ? "more than that" : 0 }.`);
19161928
}
1929+
1930+
return entries[0];
1931+
});
19171932

1918-
return entries[0] || null;
1919-
})
1920-
.tap(x => callback(null, x))
1921-
.tapCatch(err => callback(err));
1922-
return Promise.resolve(result);
1933+
passResultToCallback(resultPromise, callback);
1934+
return resultPromise;
1935+
}
1936+
1937+
public async singleOrNull(callback?: AbstractCallback<T>): Promise<T> {
1938+
callback = callback || TypeUtil.NOOP;
1939+
const resultPromise = this._executeQueryOperation(2)
1940+
.then(entries => entries.length === 1 ? entries[0] : null);
1941+
1942+
passResultToCallback(resultPromise, callback);
1943+
return resultPromise;
19231944
}
19241945

19251946
public async count(callback?: AbstractCallback<number>): Promise<number> {

src/Documents/Session/IDocumentQueryBaseSingle.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@ export interface IDocumentQueryBaseSingle<T extends object> {
1010
first(callback?: AbstractCallback<T>): Promise<T>;
1111

1212
/**
13-
* Returns first element or throws if sequence is empty or contains more than one element.
13+
* Returns first element if there's any or null otherwise.
14+
*/
15+
firstOrNull(callback?: AbstractCallback<T>): Promise<T>;
16+
17+
/**
18+
* Returns single element or throws if sequence is empty or contains more than one element.
1419
*/
1520
single(callback?: AbstractCallback<T>): Promise<T>;
1621

22+
/**
23+
* Returns single element if there's any or null otherwise.
24+
*/
25+
singleOrNull(callback?: AbstractCallback<T>): Promise<T>;
26+
1727
/**
1828
* Gets the total count of records for this query
1929
*/

test/Issues/RDBC_247.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import * as mocha from "mocha";
2+
import * as assert from "assert";
3+
import { User, Company, Order } from "../Assets/Entities";
4+
import { assertThrows } from "../Utils/AssertExtensions";
5+
import { testContext, disposeTestDocumentStore } from "../Utils/TestUtil";
6+
7+
import {
8+
RavenErrorType,
9+
IDocumentStore,
10+
IDocumentSession,
11+
} from "../../src";
12+
13+
describe("RDBC-247", function () {
14+
15+
let store: IDocumentStore;
16+
17+
beforeEach(async function () {
18+
store = await testContext.getDocumentStore();
19+
});
20+
21+
afterEach(async () =>
22+
await disposeTestDocumentStore(store));
23+
24+
beforeEach(async () => {
25+
const session = store.openSession();
26+
await session.store(Object.assign(new User(), { name: "John" }));
27+
await session.store(Object.assign(new User(), { name: "John2" }));
28+
await session.store(Object.assign(new User(), { name: "John3" }));
29+
await session.store(Object.assign(new User(), { name: "John4" }));
30+
await session.saveChanges();
31+
});
32+
33+
let session: IDocumentSession;
34+
35+
beforeEach(() => session = store.openSession());
36+
37+
describe("single()", function () {
38+
39+
it("should throw if 0 results", async function () {
40+
await assertThrows(
41+
async () => await session.query({ collection: "users" })
42+
.whereEquals("name", "Merry")
43+
.single(),
44+
err => {
45+
assert.strictEqual(err.name, "InvalidOperationException");
46+
assert.strictEqual(err.message, "Expected single result, but got 0.");
47+
});
48+
});
49+
50+
it("should throw if more than 1 result", async function() {
51+
await assertThrows(
52+
async () => await session.query({ collection: "users" }).single(),
53+
err => {
54+
assert.strictEqual(err.name, "InvalidOperationException");
55+
assert.strictEqual(err.message, "Expected single result, but got more than that.");
56+
});
57+
58+
});
59+
60+
it("should return exactly 1 result", async function() {
61+
const result = await session.query<User>({ collection: "users" })
62+
.whereEquals("name", "John")
63+
.single();
64+
65+
assert.ok(result);
66+
assert.ok(result instanceof User);
67+
assert.strictEqual(result.name, "John");
68+
});
69+
70+
});
71+
72+
describe("singleOrNull()", function () {
73+
74+
it("should return null if 0 results", async function () {
75+
const result = await session.query<User>({ collection: "users" })
76+
.whereEquals("name", "Merry")
77+
.singleOrNull();
78+
assert.strictEqual(result, null);
79+
});
80+
81+
it("should return null if more than 1 result", async function() {
82+
const result = await session.query<User>({ collection: "users" })
83+
.singleOrNull();
84+
assert.strictEqual(result, null);
85+
});
86+
87+
it("should return exactly 1 result", async function() {
88+
const result = await session.query<User>({ collection: "users" })
89+
.whereEquals("name", "John")
90+
.singleOrNull();
91+
92+
assert.ok(result);
93+
assert.ok(result instanceof User);
94+
assert.strictEqual(result.name, "John");
95+
});
96+
97+
});
98+
99+
describe("first()", function () {
100+
101+
it("should return first result", async function() {
102+
const result = await session.query<User>({ collection: "users" })
103+
.whereStartsWith("name", "John")
104+
.orderBy("name")
105+
.first();
106+
107+
assert.ok(result);
108+
assert.ok(result instanceof User);
109+
assert.strictEqual(result.name, "John");
110+
});
111+
112+
it("should throw for no results", async function() {
113+
await assertThrows(
114+
async () => await session.query({ collection: "users" })
115+
.whereEquals("name", "Merry")
116+
.first(),
117+
err => {
118+
assert.strictEqual(err.name, "InvalidOperationException");
119+
assert.strictEqual(err.message, "Expected at least one result.");
120+
});
121+
});
122+
123+
});
124+
125+
describe("firstOrNull()", function () {
126+
127+
it("should return first result", async function() {
128+
const result = await session.query<User>({ collection: "users" })
129+
.whereStartsWith("name", "John")
130+
.orderBy("name")
131+
.firstOrNull();
132+
133+
assert.ok(result);
134+
assert.ok(result instanceof User);
135+
assert.strictEqual(result.name, "John");
136+
});
137+
138+
it("should return null for no results", async function() {
139+
const result = await session.query<User>({ collection: "users" })
140+
.whereStartsWith("name", "Merry")
141+
.firstOrNull();
142+
143+
assert.strictEqual(result, null);
144+
});
145+
146+
});
147+
148+
});

test/Utils/AssertExtensions.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,5 @@
11
import * as assert from "assert";
22

3-
export async function throwsAsync(fn, regExp) {
4-
// tslint:disable-next-line:no-empty
5-
let f = () => {
6-
};
7-
try {
8-
await fn();
9-
} catch (e) {
10-
f = () => {
11-
throw e;
12-
};
13-
} finally {
14-
assert.throws(f, regExp);
15-
}
16-
}
17-
183
export async function assertThrows(func: Function, errAssert?: (err: Error) => void) {
194
try {
205
await func();

tslint.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@
3232
"adjacent-overload-signatures": false,
3333
"ban-types": false,
3434
"no-unused-variable": false,
35-
"space-before-function-paren": ["warn", "always"],
35+
"space-before-function-paren": [ "warn", "always"],
3636
"object-curly-spacing": [true, "always"],
3737
"comment-format": false,
38-
"no-shadowed-variable": false
38+
"no-shadowed-variable": false,
39+
"mocha-avoid-only": true
3940
},
4041
"rulesDirectory": [
4142
"node_modules/tslint-microsoft-contrib"

0 commit comments

Comments
 (0)