diff --git a/.eslintignore b/.eslintignore index 80e9d67..dedf96f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,10 +1,10 @@ -coverage/ -dist -node_modules/ -.eslintrc.js -jest.config.js -tsconfig.json -duckdb -addon -nodemon.json -examples +/coverage/ +/dist +/node_modules/ +/.eslintrc.js +/jest.config.js +/tsconfig.json +/duckdb +/addon +/nodemon.json +/examples diff --git a/.gitignore b/.gitignore index 1f6dd6d..1f83713 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ -node_modules -build -dist -coverage -duckdb -duckdb.tar.gz -prebuilds -temp -etc -yarn-error.log +/node_modules +/build +/dist +/coverage +/duckdb +/duckdb.tar.gz +/prebuilds +/temp +/etc +/yarn-error.log diff --git a/.npmignore b/.npmignore index 590f89c..56f91df 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,6 @@ -node_modules -coverage -build -duckdb -prebuilds -examples +/node_modules +/coverage +/build +/duckdb +/prebuilds +/examples diff --git a/.prettierignore b/.prettierignore index f174405..a6e7c04 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,7 @@ -coverage/ -node_modules/ -duckdb -addon -dist -tsconfig.json -examples +/coverage/ +/node_modules/ +/duckdb +/addon +/dist +/tsconfig.json +/examples diff --git a/docs/api/node-duckdb.connection.close.md b/docs/api/node-duckdb.connection.close.md index 48c6f31..c660723 100644 --- a/docs/api/node-duckdb.connection.close.md +++ b/docs/api/node-duckdb.connection.close.md @@ -4,7 +4,7 @@ ## Connection.close() method -Close the connection (also closes all [ResultStream](./node-duckdb.resultstream.md) or [ResultIterator](./node-duckdb.resultiterator.md) objects associated with this connection). +Close the connection (also closes all [Readable](https://nodejs.org/api/stream.html#stream_class_stream_readable) or [ResultIterator](./node-duckdb.resultiterator.md) objects associated with this connection). Signature: diff --git a/docs/api/node-duckdb.connection.execute.md b/docs/api/node-duckdb.connection.execute.md index 0548277..6737cc6 100644 --- a/docs/api/node-duckdb.connection.execute.md +++ b/docs/api/node-duckdb.connection.execute.md @@ -4,12 +4,12 @@ ## Connection.execute() method -Asynchronously executes the query and returns a node.js stream that wraps the result set. +Asynchronously executes the query and returns a [Readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) that wraps the result set. Signature: ```typescript -execute(command: string, options?: IExecuteOptions): Promise>; +execute(command: string, options?: IExecuteOptions): Promise; ``` ## Parameters @@ -21,7 +21,7 @@ execute(command: string, options?: IExecuteOptions): Promise> Returns: -Promise<[ResultStream](./node-duckdb.resultstream.md)<T>> +Promise<Readable> ## Example diff --git a/docs/api/node-duckdb.connection.md b/docs/api/node-duckdb.connection.md index 8875da1..4bc9dfc 100644 --- a/docs/api/node-duckdb.connection.md +++ b/docs/api/node-duckdb.connection.md @@ -30,8 +30,8 @@ A single db instance can have multiple connections. Having more than one connect ## Methods -| Method | Modifiers | Description | -| -------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [close()](./node-duckdb.connection.close.md) | | Close the connection (also closes all [ResultStream](./node-duckdb.resultstream.md) or [ResultIterator](./node-duckdb.resultiterator.md) objects associated with this connection). | -| [execute(command, options)](./node-duckdb.connection.execute.md) | | Asynchronously executes the query and returns a node.js stream that wraps the result set. | -| [executeIterator(command, options)](./node-duckdb.connection.executeiterator.md) | | Asynchronously executes the query and returns an iterator that points to the first result in the result set. | +| Method | Modifiers | Description | +| -------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [close()](./node-duckdb.connection.close.md) | | Close the connection (also closes all [Readable](https://nodejs.org/api/stream.html#stream_class_stream_readable) or [ResultIterator](./node-duckdb.resultiterator.md) objects associated with this connection). | +| [execute(command, options)](./node-duckdb.connection.execute.md) | | Asynchronously executes the query and returns a [Readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) that wraps the result set. | +| [executeIterator(command, options)](./node-duckdb.connection.executeiterator.md) | | Asynchronously executes the query and returns an iterator that points to the first result in the result set. | diff --git a/docs/api/node-duckdb.md b/docs/api/node-duckdb.md index 73d44fc..f058086 100644 --- a/docs/api/node-duckdb.md +++ b/docs/api/node-duckdb.md @@ -52,7 +52,6 @@ For more examples see [here](https://github.com/deepcrawl/node-duckdb/tree/featu | [Connection](./node-duckdb.connection.md) | Represents a DuckDB connection. | | [DuckDB](./node-duckdb.duckdb.md) | The DuckDB class represents a DuckDB database instance. | | [ResultIterator](./node-duckdb.resultiterator.md) | ResultIterator represents the result set of a DuckDB query. Instances of this class are returned by the [Connection.executeIterator](./node-duckdb.connection.executeiterator.md). | -| [ResultStream](./node-duckdb.resultstream.md) | This is a Readable stream that wrapps the ResultIterator. Instances of this class are returned by [Connection.execute](./node-duckdb.connection.execute.md). | ## Enumerations diff --git a/docs/api/node-duckdb.resultiterator._symbol.iterator_.md b/docs/api/node-duckdb.resultiterator._symbol.iterator_.md new file mode 100644 index 0000000..12d8cbb --- /dev/null +++ b/docs/api/node-duckdb.resultiterator._symbol.iterator_.md @@ -0,0 +1,15 @@ + + +[Node-DuckDB API](./node-duckdb.md) > [ResultIterator](./node-duckdb.resultiterator.md) > [\[Symbol.iterator\]](./node-duckdb.resultiterator._symbol.iterator_.md) + +## ResultIterator.\[Symbol.iterator\]() method + +Signature: + +```typescript +[Symbol.iterator](): this; +``` + +Returns: + +this diff --git a/docs/api/node-duckdb.resultiterator.md b/docs/api/node-duckdb.resultiterator.md index 1511883..e3c4af0 100644 --- a/docs/api/node-duckdb.resultiterator.md +++ b/docs/api/node-duckdb.resultiterator.md @@ -9,9 +9,11 @@ ResultIterator represents the result set of a DuckDB query. Instances of this cl Signature: ```typescript -export declare class ResultIterator +export declare class ResultIterator implements IterableIterator ``` +Implements: IterableIterator<T> + ## Remarks The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ResultIterator` class. @@ -25,9 +27,11 @@ The constructor for this class is marked as internal. Third-party code should no ## Methods -| Method | Modifiers | Description | -| -------------------------------------------------------------- | --------- | ------------------------------- | -| [close()](./node-duckdb.resultiterator.close.md) | | Close the ResultIterator | -| [describe()](./node-duckdb.resultiterator.describe.md) | | Describe the result set schema. | -| [fetchAllRows()](./node-duckdb.resultiterator.fetchallrows.md) | | Fetch all rows | -| [fetchRow()](./node-duckdb.resultiterator.fetchrow.md) | | Fetch the next row | +| Method | Modifiers | Description | +| -------------------------------------------------------------------------- | --------- | ------------------------------- | +| [\[Symbol.iterator\]()](./node-duckdb.resultiterator._symbol.iterator_.md) | | | +| [close()](./node-duckdb.resultiterator.close.md) | | Close the ResultIterator | +| [describe()](./node-duckdb.resultiterator.describe.md) | | Describe the result set schema. | +| [fetchAllRows()](./node-duckdb.resultiterator.fetchallrows.md) | | Fetch all rows | +| [fetchRow()](./node-duckdb.resultiterator.fetchrow.md) | | Fetch the next row | +| [next()](./node-duckdb.resultiterator.next.md) | | | diff --git a/docs/api/node-duckdb.resultiterator.next.md b/docs/api/node-duckdb.resultiterator.next.md new file mode 100644 index 0000000..61a6891 --- /dev/null +++ b/docs/api/node-duckdb.resultiterator.next.md @@ -0,0 +1,15 @@ + + +[Node-DuckDB API](./node-duckdb.md) > [ResultIterator](./node-duckdb.resultiterator.md) > [next](./node-duckdb.resultiterator.next.md) + +## ResultIterator.next() method + +Signature: + +```typescript +next(): IteratorResult; +``` + +Returns: + +IteratorResult<T> diff --git a/docs/api/node-duckdb.resultstream.md b/docs/api/node-duckdb.resultstream.md deleted file mode 100644 index d206280..0000000 --- a/docs/api/node-duckdb.resultstream.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Node-DuckDB API](./node-duckdb.md) > [ResultStream](./node-duckdb.resultstream.md) - -## ResultStream class - -This is a Readable stream that wrapps the ResultIterator. Instances of this class are returned by [Connection.execute](./node-duckdb.connection.execute.md). - -Signature: - -```typescript -export declare class ResultStream extends Readable -``` - -Extends: Readable - -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ResultStream` class. diff --git a/src/addon/connection.ts b/src/addon/connection.ts index cdff6ce..8cbbd14 100644 --- a/src/addon/connection.ts +++ b/src/addon/connection.ts @@ -1,40 +1,43 @@ -import { ConnectionBinding } from "../addon-bindings"; -import { ResultStream } from "./result-stream"; -import {DuckDB} from "./duckdb"; -import { ResultIterator } from "./result-iterator"; +import { Readable } from "stream"; + +import { ConnectionBinding } from "@addon-bindings"; import { IExecuteOptions } from "@addon-types"; +import { DuckDB } from "./duckdb"; +import { ResultIterator } from "./result-iterator"; +import { getResultStream } from "./result-stream"; + /** * Represents a DuckDB connection. - * + * * @remarks * A single db instance can have multiple connections. Having more than one connection instance is required when executing concurrent queries. - * + * * @public */ export class Connection { /** - * Connection constructor. - * @param duckdb - {@link DuckDB | DuckDB} instance to connect to. - * - * - * @example - * Initializing a connection: - * ```ts - * import { DuckDB } from "node-duckdb"; - * const db = new DuckDB(); - * const connection = new Connection(db); - * ``` - * - * @public - */ + * Connection constructor. + * @param duckdb - {@link DuckDB | DuckDB} instance to connect to. + * + * + * @example + * Initializing a connection: + * ```ts + * import { DuckDB } from "node-duckdb"; + * const db = new DuckDB(); + * const connection = new Connection(db); + * ``` + * + * @public + */ constructor(private duckdb: DuckDB) {} private connectionBinding = new ConnectionBinding(this.duckdb.db); /** - * Asynchronously executes the query and returns a node.js stream that wraps the result set. + * Asynchronously executes the query and returns a {@link https://nodejs.org/api/stream.html#stream_class_stream_readable | Readable stream} that wraps the result set. * @param command - SQL command to execute * @param options - optional options object of type {@link IExecuteOptions | IExecuteOptions} - * + * * @example * Streaming results of a DuckDB query into a CSV file: * ```ts @@ -64,15 +67,15 @@ export class Connection { * outputToFileAsCsv(); * ``` */ - public async execute(command: string, options?: IExecuteOptions): Promise> { + public async execute(command: string, options?: IExecuteOptions): Promise { const resultIteratorBinding = await this.connectionBinding.execute(command, options); - return new ResultStream(new ResultIterator(resultIteratorBinding)); + return getResultStream(new ResultIterator(resultIteratorBinding)); } /** * Asynchronously executes the query and returns an iterator that points to the first result in the result set. * @param command - SQL command to execute * @param options - optional options object of type {@link IExecuteOptions | IExecuteOptions} - * + * * @example * Printing rows: * ```ts @@ -83,7 +86,7 @@ export class Connection { * await connection.executeIterator("CREATE TABLE people(id INTEGER, name VARCHAR);"); * await connection.executeIterator("INSERT INTO people VALUES (1, 'Mark'), (2, 'Hannes'), (3, 'Bob');"); * const result = await connection.executeIterator("SELECT * FROM people;"); - * // print the first row + * // print the first row * console.log(result.fetchRow()); * // print the rest of the rows * console.log(result.fetchAllRows()); @@ -94,7 +97,7 @@ export class Connection { * } * queryDatabaseWithIterator(); * ``` - * + * * @example * Providing generics type: * ```ts @@ -108,7 +111,7 @@ export class Connection { return new ResultIterator(await this.connectionBinding.execute(command, options)); } /** - * Close the connection (also closes all {@link ResultStream | ResultStream} or {@link ResultIterator | ResultIterator} objects associated with this connection). + * Close the connection (also closes all {@link https://nodejs.org/api/stream.html#stream_class_stream_readable | Readable} or {@link ResultIterator | ResultIterator} objects associated with this connection). * @remarks * Even though GC will automatically destroy the Connection object at some point, DuckDB data is stored in the native address space, not the V8 heap, meaning you can easily have a Node.js process taking gigabytes of memory (more than the default heap size for Node.js) with V8 not triggering GC. So, definitely think about manually calling `close()`. */ diff --git a/src/addon/duckdb.ts b/src/addon/duckdb.ts index 6f099b2..82fbd39 100644 --- a/src/addon/duckdb.ts +++ b/src/addon/duckdb.ts @@ -1,141 +1,141 @@ -import { IDuckDBConfig } from "@addon-types"; -import { DuckDBBinding, DuckDBClass } from "../addon-bindings"; import { promises as fs } from "fs"; import { join } from "path"; -import { AccessMode, OrderType, OrderByNullType } from "@addon-types"; + +import { DuckDBBinding, DuckDBClass } from "@addon-bindings"; +import { IDuckDBConfig, AccessMode, OrderType, OrderByNullType } from "@addon-types"; /** * The DuckDB class represents a DuckDB database instance. * @public */ export class DuckDB { - /** - * Returns the current version of the node-duckdb package (from package.json) - * - * @remarks - * Useful for logging/debugging - * - * @public - */ - public static async getBindingsVersion(): Promise { - return JSON.parse(await fs.readFile(join(__dirname, "../../package.json"), {encoding: "utf-8"})).version; - } - private duckdb: DuckDBClass; - /** - * Represents a native instance of DuckDB. - * @param config - optional configuration object of type {@link IDuckDBConfig | IDuckDBConfig}. - * - * - * @example - * Initializing a duckdb database in memory: - * ```ts - * import { DuckDB } from "node-duckdb"; - * const db = new DuckDB(); - * ``` - * - * @example - * Initializing a duckdb database from file: - * ```ts - * import { DuckDB } from "node-duckdb"; - * const db = new DuckDB({ path: join(__dirname, "./mydb") }); - * ``` - * - * @example - * Initializing a duckdb database from file and setting some additional options: - * ```ts - * import { DuckDB, OrderType } from "node-duckdb"; - * const db = new DuckDB({ path: join(__dirname, "./mydb"), options: { defaultOrderType: OrderType.Descending, temporaryDirectory: false } }); - * ``` - * - * @public - */ - constructor(config: IDuckDBConfig = {}) { - this.duckdb = new DuckDBBinding(config); - } - /** - * Closes the underlying duckdb database, frees associated memory and renders it unusuable. - * @remarks - * Even though GC will automatically destroy the Database object at some point, DuckDB data is stored in the native address space, not the V8 heap, meaning you can easily have a Node.js process taking gigabytes of memory (more than the default heap size for Node.js) with V8 not triggering GC. So, definitely think about manually calling `close()`. - * @public - */ - public close(): void { - return this.duckdb.close(); - } - /** - * Returns underlying binding instance. - * @internal - */ - public get db() { - return this.duckdb; - } - /** - * Returns true if the underlying database has been closed, false otherwise. - * @public - */ - public get isClosed(): boolean { - return this.duckdb.isClosed; - } - /** - * Returns the {@link AccessMode | access mode} used by the database. - * @public - */ - public get accessMode() { - return this.duckdb.accessMode; - } - /** - * Returns the checkpoint write ahead log size used by the database. - * @public - */ - public get checkPointWALSize() { - return this.duckdb.checkPointWALSize; - } - /** - * Returns the maximum memory limit for the database. - * @public - */ - public get maximumMemory() { - return this.duckdb.maximumMemory; - } - /** - * Returns true if the database uses a temporary directory for storing data that does not fit into memory, false otherwise. - * @public - */ - public get useTemporaryDirectory() { - return this.duckdb.useTemporaryDirectory; - } - /** - * Returns the temporary directory location for the database. - * @public - */ - public get temporaryDirectory() { - return this.duckdb.temporaryDirectory; - } - /** - * Returns the collation used by the database. - * @public - */ - public get collation() { - return this.duckdb.collation; - } - /** - * Returns the default {@link OrderType | sort order}. - * @public - */ - public get defaultOrderType() { - return this.duckdb.defaultOrderType; - } - /** - * Returns the default {@link OrderByNullType | sort order for null values}. - * @public - */ - public get defaultNullOrder() { - return this.duckdb.defaultNullOrder; - } - /** - * Returns true of copying is enabled, false otherwise. - * @public - */ - public get enableCopy() { - return this.duckdb.enableCopy; - } + /** + * Returns the current version of the node-duckdb package (from package.json) + * + * @remarks + * Useful for logging/debugging + * + * @public + */ + public static async getBindingsVersion(): Promise { + return JSON.parse(await fs.readFile(join(__dirname, "../../package.json"), { encoding: "utf-8" })).version; + } + private duckdb: DuckDBClass; + /** + * Represents a native instance of DuckDB. + * @param config - optional configuration object of type {@link IDuckDBConfig | IDuckDBConfig}. + * + * + * @example + * Initializing a duckdb database in memory: + * ```ts + * import { DuckDB } from "node-duckdb"; + * const db = new DuckDB(); + * ``` + * + * @example + * Initializing a duckdb database from file: + * ```ts + * import { DuckDB } from "node-duckdb"; + * const db = new DuckDB({ path: join(__dirname, "./mydb") }); + * ``` + * + * @example + * Initializing a duckdb database from file and setting some additional options: + * ```ts + * import { DuckDB, OrderType } from "node-duckdb"; + * const db = new DuckDB({ path: join(__dirname, "./mydb"), options: { defaultOrderType: OrderType.Descending, temporaryDirectory: false } }); + * ``` + * + * @public + */ + constructor(config: IDuckDBConfig = {}) { + this.duckdb = new DuckDBBinding(config); + } + /** + * Closes the underlying duckdb database, frees associated memory and renders it unusuable. + * @remarks + * Even though GC will automatically destroy the Database object at some point, DuckDB data is stored in the native address space, not the V8 heap, meaning you can easily have a Node.js process taking gigabytes of memory (more than the default heap size for Node.js) with V8 not triggering GC. So, definitely think about manually calling `close()`. + * @public + */ + public close(): void { + return this.duckdb.close(); + } + /** + * Returns underlying binding instance. + * @internal + */ + public get db(): DuckDBClass { + return this.duckdb; + } + /** + * Returns true if the underlying database has been closed, false otherwise. + * @public + */ + public get isClosed(): boolean { + return this.duckdb.isClosed; + } + /** + * Returns the {@link AccessMode | access mode} used by the database. + * @public + */ + public get accessMode(): AccessMode { + return this.duckdb.accessMode; + } + /** + * Returns the checkpoint write ahead log size used by the database. + * @public + */ + public get checkPointWALSize(): number { + return this.duckdb.checkPointWALSize; + } + /** + * Returns the maximum memory limit for the database. + * @public + */ + public get maximumMemory(): number { + return this.duckdb.maximumMemory; + } + /** + * Returns true if the database uses a temporary directory for storing data that does not fit into memory, false otherwise. + * @public + */ + public get useTemporaryDirectory(): boolean { + return this.duckdb.useTemporaryDirectory; + } + /** + * Returns the temporary directory location for the database. + * @public + */ + public get temporaryDirectory(): string { + return this.duckdb.temporaryDirectory; + } + /** + * Returns the collation used by the database. + * @public + */ + public get collation(): string { + return this.duckdb.collation; + } + /** + * Returns the default {@link OrderType | sort order}. + * @public + */ + public get defaultOrderType(): OrderType { + return this.duckdb.defaultOrderType; + } + /** + * Returns the default {@link OrderByNullType | sort order for null values}. + * @public + */ + public get defaultNullOrder(): OrderByNullType { + return this.duckdb.defaultNullOrder; + } + /** + * Returns true of copying is enabled, false otherwise. + * @public + */ + public get enableCopy(): boolean { + return this.duckdb.enableCopy; + } } diff --git a/src/addon/index.ts b/src/addon/index.ts index c041760..00f79b5 100644 --- a/src/addon/index.ts +++ b/src/addon/index.ts @@ -1,4 +1,3 @@ -export {DuckDB} from "./duckdb"; -export {ResultIterator} from "./result-iterator"; -export {ResultStream} from "./result-stream"; -export {Connection} from "./connection"; +export { DuckDB } from "./duckdb"; +export { ResultIterator } from "./result-iterator"; +export { Connection } from "./connection"; diff --git a/src/addon/result-iterator.ts b/src/addon/result-iterator.ts index bb018cb..c40424a 100644 --- a/src/addon/result-iterator.ts +++ b/src/addon/result-iterator.ts @@ -1,63 +1,74 @@ +import { ResultIteratorClass } from "@addon-bindings"; import { ResultType } from "@addon-types"; -import { ResultIteratorClass } from "../addon-bindings"; /** * ResultIterator represents the result set of a DuckDB query. Instances of this class are returned by the {@link Connection.executeIterator | Connection.executeIterator}. - * + * * @public */ -export class ResultIterator { - /** - * - * @internal - */ - constructor(private resultInterator: ResultIteratorClass) {} - /** - * Fetch the next row - * - * @remarks - * First call returns the first row, when no more rows left `null` is returned. - */ - public fetchRow(): T { - return this.resultInterator.fetchRow(); - } - /** - * Fetch all rows - * - * @remarks - * Note, this may produce a `heap out of bounds` error in case when there is too much data. Either use the {@link ResultIterator.fetchRow | fetchRow} or the {@link Connection.execute | Connection.execute} method when there is a lot of data. - */ - public fetchAllRows(): T[] { - const allRows: T[] = []; - for (let element = this.fetchRow(); element !== null; element = this.fetchRow()) { - allRows.push(element); - } - return allRows; - } - /** - * Describe the result set schema. - */ - public describe(): string[][] { - return this.resultInterator.describe(); - } - /** - * Close the ResultIterator - * @remarks - * {@link Connection.close | Connection.close} automatically closes all associated ResultIterators. - */ - public close(): void { - return this.resultInterator.close(); - } - /** - * Get the {@link ResultType | ResultType} of the ResultIterator. This is specified by the {@link IExecuteOptions.forceMaterialized | options} argument on {@link Connection.executeIterator | executeIterator}. - */ - public get type(): ResultType { - return this.resultInterator.type; - } - /** - * Returns true if ResultIterator is closed, false otherwise. - */ - public get isClosed(): boolean { - return this.resultInterator.isClosed; +export class ResultIterator implements IterableIterator { + /** + * + * @internal + */ + constructor(private resultInterator: ResultIteratorClass) {} + /** + * Fetch the next row + * + * @remarks + * First call returns the first row, when no more rows left `null` is returned. + */ + public fetchRow(): T { + return this.resultInterator.fetchRow(); + } + /** + * Fetch all rows + * + * @remarks + * Note, this may produce a `heap out of bounds` error in case when there is too much data. Either use the {@link ResultIterator.fetchRow | fetchRow} or the {@link Connection.execute | Connection.execute} method when there is a lot of data. + */ + public fetchAllRows(): T[] { + return [...this]; + } + /** + * Describe the result set schema. + */ + public describe(): string[][] { + return this.resultInterator.describe(); + } + /** + * Close the ResultIterator + * @remarks + * {@link Connection.close | Connection.close} automatically closes all associated ResultIterators. + */ + public close(): void { + return this.resultInterator.close(); + } + /** + * Get the {@link ResultType | ResultType} of the ResultIterator. This is specified by the {@link IExecuteOptions.forceMaterialized | options} argument on {@link Connection.executeIterator | executeIterator}. + */ + public get type(): ResultType { + return this.resultInterator.type; + } + /** + * Returns true if ResultIterator is closed, false otherwise. + */ + public get isClosed(): boolean { + return this.resultInterator.isClosed; + } + + public next(): IteratorResult { + const row = this.fetchRow(); + if (row === null) { + // TS interface is incorrect: when `done` is true, there should be no `value` + return >{ done: true }; } + return { + value: row, + done: false, + }; + } + public [Symbol.iterator](): this { + return this; + } } diff --git a/src/addon/result-stream.ts b/src/addon/result-stream.ts index 41e6994..8225001 100644 --- a/src/addon/result-stream.ts +++ b/src/addon/result-stream.ts @@ -1,47 +1,11 @@ import { Readable } from "stream"; import { ResultIterator } from "./result-iterator"; -/** - * This is a Readable stream that wrapps the ResultIterator. Instances of this class are returned by {@link Connection.execute | Connection.execute}. - * @public - */ -export class ResultStream extends Readable { - /** - * @internal - */ - constructor(private resultIterator: ResultIterator) { - super({ objectMode: true }); - } - /** - * @internal - */ - public _read(): void { - try { - const element = this.resultIterator.fetchRow(); - this.push(element); - if (element === null) { - this.close(); - } - } catch (e) { - this.destroy(e); - } - } - /** - * - * @internal - */ - public _destroy(error: Error | null, callback: (error?: Error | null) => void): void { - this.close(); - callback(error); - } - - private close(): void { - this.resultIterator.close(); - if (!this.resultIterator.isClosed) { - this.emit("error", new Error("Close wasn't successful")); - return; - } - this.emit("close"); - } +export function getResultStream(iterator: ResultIterator): Readable { + return Readable.from(iterator, { + destroy() { + iterator.close(); + }, + }); } diff --git a/src/index.ts b/src/index.ts index 8951d0c..b452ccb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,5 +39,5 @@ * ``` * For more examples see {@link https://github.com/deepcrawl/node-duckdb/tree/feature/ODIN-423-welcome-page/examples | here}. */ -export { DuckDB, Connection, ResultIterator, ResultStream } from "./addon"; +export { DuckDB, Connection, ResultIterator } from "./addon"; export * from "./addon-types"; diff --git a/src/tests/result-iterator-protocol.test.ts b/src/tests/result-iterator-protocol.test.ts new file mode 100644 index 0000000..a8d4372 --- /dev/null +++ b/src/tests/result-iterator-protocol.test.ts @@ -0,0 +1,45 @@ +import { Connection, DuckDB } from "@addon"; +import { RowResultFormat } from "@addon-types"; + +describe("Result iterator as well formed JS iterator/iterable", () => { + let db: DuckDB; + let connection: Connection; + beforeEach(() => { + db = new DuckDB(); + connection = new Connection(db); + }); + + afterEach(() => { + connection.close(); + db.close(); + }); + + it("supports for/each", async () => { + const results = []; + const result = await connection.executeIterator( + "SELECT * FROM read_csv_auto('src/tests/test-fixtures/web_page.csv')", + { rowResultFormat: RowResultFormat.Array }, + ); + // eslint-disable-next-line no-loops/no-loops + for (const row of result) { + results.push(row); + } + expect(results[0]).toEqual([ + 1, + "AAAAAAAABAAAAAAA", + 873244800000, + null, + 2450810, + 2452620, + "Y", + 98539, + "http://www.foo.com", + "welcome", + 2531, + 8, + 3, + 4, + ]); + expect(results.length).toBe(60); + }); +}); diff --git a/src/tests/result-stream.test.ts b/src/tests/result-stream.test.ts index 72481c4..4e7b09c 100644 --- a/src/tests/result-stream.test.ts +++ b/src/tests/result-stream.test.ts @@ -1,11 +1,13 @@ -import { Connection, DuckDB, ResultStream } from "@addon"; +import { Readable } from "stream"; + +import { Connection, DuckDB } from "@addon"; import { IExecuteOptions, RowResultFormat } from "@addon-types"; const query = "SELECT * FROM read_csv_auto('src/tests/test-fixtures/web_page.csv')"; const executeOptions: IExecuteOptions = { rowResultFormat: RowResultFormat.Array }; -function readStream(rs: ResultStream): Promise { +function readStream(rs: Readable): Promise { return new Promise((resolve, reject) => { const elements: T[] = []; rs.on("data", (el: any) => elements.push(el)); @@ -71,25 +73,6 @@ describe("Result stream", () => { expect(hasClosedFired).toBe(true); }); - it("closes resource when all data has been read", async () => { - const rs = await connection.execute(query, executeOptions); - let hasClosedFired = false; - rs.on("close", () => (hasClosedFired = true)); - const elements = await readStream(rs); - expect(elements.length).toBe(60); - expect(hasClosedFired).toBe(true); - }); - - it("closes resource on manual destroy", async () => { - const rs1 = await connection.execute(query, executeOptions); - let hasClosedFired = false; - rs1.on("close", () => (hasClosedFired = true)); - void readStream(rs1); - expect(hasClosedFired).toBe(false); - rs1.destroy(); - expect(hasClosedFired).toBe(true); - }); - it("is able to read from two streams on separate connections to one database while interleaving", async () => { const connection1 = new Connection(db); const connection2 = new Connection(db);