Skip to content

Commit e137a57

Browse files
committed
Fix import
Fixes #461. Fixes #496.
1 parent a7ca44b commit e137a57

File tree

6 files changed

+292
-93
lines changed

6 files changed

+292
-93
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Reimplemented `collection.import`
13+
14+
The previous implementation was broken. The new implementation should be backwards-compatible
15+
in cases where it previously wasn't broken but is more flexible and also handles buffers.
16+
1017
### Fixed
1118

1219
- Added missing dependency on `@types/node` ([#567](https://github.com/arangodb/arangojs/issues/567))

docs/Drivers/JS/Reference/Collection/BulkImport.md

Lines changed: 110 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,90 +11,140 @@ Bulk imports the given _data_ into the collection.
1111

1212
**Arguments**
1313

14-
* **data**: `Array<Array<any>> | Array<Object>`
14+
- **data**: `Array | Buffer | string`
1515

16-
The data to import. This can be an array of documents:
16+
The data to import. Depending on the _type_ option this can be any of the
17+
following:
1718

18-
```js
19-
[
20-
{key1: value1, key2: value2}, // document 1
21-
{key1: value1, key2: value2}, // document 2
22-
...
23-
]
24-
```
19+
For type `"documents"` or `"auto"`:
2520

26-
Or it can be an array of value arrays following an array of keys.
21+
- an array of documents, e.g.
2722

28-
```js
29-
[
30-
['key1', 'key2'], // key names
31-
[value1, value2], // document 1
32-
[value1, value2], // document 2
33-
...
34-
]
35-
```
36-
37-
* **opts**: `Object` (optional) If _opts_ is set, it must be an object with any
23+
```json
24+
[
25+
{ "_key": "banana", "color": "yellow" },
26+
{ "_key": "peach", "color": "pink" }
27+
]
28+
```
29+
30+
- a string or buffer containing one JSON document per line, e.g.
31+
32+
```
33+
{"_key":"banana","color":"yellow"}
34+
{"_key":"peach","color":"pink"}
35+
```
36+
37+
For type `"array"` or `"auto"`:
38+
39+
- a string or buffer containing a JSON array of documents, e.g.
40+
41+
```json
42+
[
43+
{ "_key": "banana", "color": "yellow" },
44+
{ "_key": "peach", "color": "pink" }
45+
]
46+
```
47+
48+
For type `null`:
49+
50+
- an array containing an array of keys followed by arrays of values, e.g.
51+
52+
```
53+
[
54+
["_key", "color"],
55+
["banana", "yellow"],
56+
["peach", "pink"]
57+
]
58+
```
59+
60+
- a string or buffer containing a JSON array of keys followed by
61+
one JSON array of values per line, e.g.
62+
63+
```
64+
["_key", "color"]
65+
["banana", "yellow"]
66+
["peach", "pink"]
67+
```
68+
69+
- **opts**: `Object` (optional) If _opts_ is set, it must be an object with any
3870
of the following properties:
3971

40-
* **waitForSync**: `boolean` (Default: `false`)
72+
- **type**: `string | null` (Default: `"auto"`)
73+
74+
Indicates which format the data uses.
75+
Can be `"documents"`, `"array"` or `"auto"`.
76+
Use `null` to explicitly set no type.
77+
78+
- **fromPrefix**: `string` (optional)
79+
80+
Prefix to prepend to `_from` attributes.
81+
82+
- **toPrefix**: `string` (optional)
83+
84+
Prefix to prepend to `_to` attributes.
85+
86+
- **overwrite**: `boolean` (Default: `false`)
87+
88+
If set to `true`, the collection is truncated before the data is imported.
89+
90+
- **waitForSync**: `boolean` (Default: `false`)
4191

4292
Wait until the documents have been synced to disk.
4393

44-
* **details**: `boolean` (Default: `false`)
94+
- **onDuplicate**: `string` (Default: `"error"`)
4595

46-
Whether the response should contain additional details about documents that
47-
could not be imported.false\*.
96+
Controls behavior when a unique constraint is violated.
97+
Can be `"error"`, `"update"`, `"replace"` or `"ignore"`.
98+
99+
- **complete**: `boolean` (Default: `false`)
48100

49-
* **type**: `string` (Default: `"auto"`)
101+
If set to `true`, the import will abort if any error occurs.
50102

51-
Indicates which format the data uses. Can be `"documents"`, `"array"` or
52-
`"auto"`.
103+
- **details**: `boolean` (Default: `false`)
53104

54-
If _data_ is a JavaScript array, it will be transmitted as a line-delimited JSON
55-
stream. If _opts.type_ is set to `"array"`, it will be transmitted as regular
56-
JSON instead. If _data_ is a string, it will be transmitted as it is without any
57-
processing.
105+
Whether the response should contain additional details about documents that
106+
could not be imported.
58107

59108
For more information on the _opts_ object, see
60-
[the HTTP API documentation for bulk imports](https://docs.arangodb.com/latest/HTTP/BulkImports/ImportingSelfContained.html).
109+
[the HTTP API documentation for bulk imports](https://docs.arangodb.com/latest/HTTP/BulkImports/).
61110

62111
**Examples**
63112

64113
```js
65114
const db = new Database();
66-
const collection = db.collection('users');
115+
const collection = db.collection("users");
67116

68-
// document stream
69-
const result = await collection.import([
70-
{username: 'admin', password: 'hunter2'},
71-
{username: 'jcd', password: 'bionicman'},
72-
{username: 'jreyes', password: 'amigo'},
73-
{username: 'ghermann', password: 'zeitgeist'}
74-
]);
75-
assert.equal(result.created, 4);
117+
const result = await collection.import(
118+
[
119+
{ username: "jcd", password: "bionicman" },
120+
{ username: "jreyes", password: "amigo" },
121+
{ username: "ghermann", password: "zeitgeist" }
122+
],
123+
{ type: "documents" } // optional
124+
);
76125

77126
// -- or --
78127

79-
// array stream with header
80-
const result = await collection.import([
81-
['username', 'password'], // keys
82-
['admin', 'hunter2'], // row 1
83-
['jcd', 'bionicman'], // row 2
84-
['jreyes', 'amigo'],
85-
['ghermann', 'zeitgeist']
86-
]);
87-
assert.equal(result.created, 4);
128+
const buf = fs.readFileSync("dx_users.json");
129+
// [
130+
// {"username": "jcd", "password": "bionicman"},
131+
// {"username": "jreyes", "password": "amigo"},
132+
// {"username": "ghermann", "password": "zeitgeist"}
133+
// ]
134+
const result = await collection.import(
135+
buf,
136+
{ type: "array" } // optional
137+
);
138+
```
88139

89140
// -- or --
90141

91-
// raw line-delimited JSON array stream with header
92-
const result = await collection.import([
93-
'["username", "password"]',
94-
'["admin", "hunter2"]',
95-
'["jcd", "bionicman"]',
96-
'["jreyes", "amigo"]',
97-
'["ghermann", "zeitgeist"]'
98-
].join('\r\n') + '\r\n');
99-
assert.equal(result.created, 4);
100-
```
142+
const result = await collection.import(
143+
[
144+
["username", "password"],
145+
["jcd", "bionicman"],
146+
["jreyes", "amigo"],
147+
["ghermann", "zeitgeist"]
148+
],
149+
{ type: null } // required
150+
);

src/collection.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ export type IndexHandle =
2020
id?: string;
2121
};
2222

23+
export interface ImportOptions {
24+
type?: null | "auto" | "documents" | "array";
25+
fromPrefix?: string;
26+
toPrefix?: string;
27+
overwrite?: boolean;
28+
waitForSync?: boolean;
29+
onDuplicate?: "error" | "update" | "replace" | "ignore";
30+
complete?: boolean;
31+
details?: boolean;
32+
}
33+
34+
export interface ImportResult {
35+
error: false;
36+
created: number;
37+
errors: number;
38+
empty: number;
39+
updated: number;
40+
ignored: number;
41+
details?: string[];
42+
}
43+
2344
export function isArangoCollection(
2445
collection: any
2546
): collection is ArangoCollection {
@@ -500,14 +521,24 @@ export abstract class BaseCollection implements ArangoCollection {
500521
);
501522
}
502523

503-
import(data: any, opts?: any) {
524+
import(
525+
data: Buffer | Blob | string | any[],
526+
{ type = "auto", ...opts }: ImportOptions = {}
527+
): Promise<ImportResult> {
528+
if (Array.isArray(data)) {
529+
data = data.map(line => JSON.stringify(line)).join("\r\n") + "\r\n";
530+
}
504531
return this._connection.request(
505532
{
506533
method: "POST",
507534
path: "/_api/import",
508535
body: data,
509-
isJsonStream: Boolean(!opts || opts.type !== "array"),
510-
qs: { type: "auto", ...opts, collection: this.name }
536+
isBinary: true,
537+
qs: {
538+
type: type === null ? undefined : type,
539+
...opts,
540+
collection: this.name
541+
}
511542
},
512543
res => res.body
513544
);

src/connection.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export type RequestOptions = {
4242
body?: any;
4343
expectBinary?: boolean;
4444
isBinary?: boolean;
45-
isJsonStream?: boolean;
4645
headers?: { [key: string]: string };
4746
absolutePath?: boolean;
4847
basePath?: string;
@@ -272,7 +271,6 @@ export class Connection {
272271
body,
273272
expectBinary = false,
274273
isBinary = false,
275-
isJsonStream = false,
276274
headers,
277275
...urlInfo
278276
}: RequestOptions,
@@ -284,14 +282,8 @@ export class Connection {
284282
contentType = "application/octet-stream";
285283
} else if (body) {
286284
if (typeof body === "object") {
287-
if (isJsonStream) {
288-
body =
289-
body.map((obj: any) => JSON.stringify(obj)).join("\r\n") + "\r\n";
290-
contentType = "application/x-ldjson";
291-
} else {
292-
body = JSON.stringify(body);
293-
contentType = "application/json";
294-
}
285+
body = JSON.stringify(body);
286+
contentType = "application/json";
295287
} else {
296288
body = String(body);
297289
}

0 commit comments

Comments
 (0)