duckdb
Version:
DuckDB node.js API
764 lines (697 loc) • 18.7 kB
JavaScript
/**
* @module duckdb
* @summary DuckDB is an embeddable SQL OLAP Database Management System
*/
var duckdb = require('./duckdb-binding.js');
module.exports = exports = duckdb;
/**
* Check that errno attribute equals this to check for a duckdb error
* @constant {number}
*/
var ERROR = duckdb.ERROR;
/**
* Open database in readonly mode
* @constant {number}
*/
var OPEN_READONLY = duckdb.OPEN_READONLY;
/**
* Currently ignored
* @constant {number}
*/
var OPEN_READWRITE = duckdb.OPEN_READWRITE;
/**
* Currently ignored
* @constant {number}
*/
var OPEN_CREATE = duckdb.OPEN_CREATE;
/**
* Currently ignored
* @constant {number}
*/
var OPEN_FULLMUTEX = duckdb.OPEN_FULLMUTEX;
/**
* Currently ignored
* @constant {number}
*/
var OPEN_SHAREDCACHE = duckdb.OPEN_SHAREDCACHE;
/**
* Currently ignored
* @constant {number}
*/
var OPEN_PRIVATECACHE = duckdb.OPEN_PRIVATECACHE;
// some wrappers for compatibilities sake
/**
* Main database interface
* @arg path - path to database file or :memory: for in-memory database
* @arg access_mode - access mode
* @arg config - the configuration object
* @arg callback - callback function
*/
var Database = duckdb.Database;
/**
* @class
*/
var Connection = duckdb.Connection;
/**
* @class
*/
var Statement = duckdb.Statement;
/**
* @class
*/
var QueryResult = duckdb.QueryResult;
/**
* Types of tokens return by `tokenize`.
*/
var TokenType = duckdb.TokenType;
/**
* @method
* @return data chunk
*/
QueryResult.prototype.nextChunk;
/**
* Function to fetch the next result blob of an Arrow IPC Stream in a zero-copy way.
* (requires arrow extension to be loaded)
*
* @method
* @return data chunk
*/
QueryResult.prototype.nextIpcBuffer;
/**
* @name asyncIterator
* @memberof module:duckdb~QueryResult
* @method
* @instance
* @yields data chunks
*/
QueryResult.prototype[Symbol.asyncIterator] = async function*() {
let prefetch = this.nextChunk();
while (true) {
const chunk = await prefetch;
// Null chunk indicates end of stream
if (!chunk) {
return;
}
// Prefetch the next chunk while we're iterating
prefetch = this.nextChunk();
for (const row of chunk) {
yield row;
}
}
}
/**
* Run a SQL statement and trigger a callback when done
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Connection.prototype.run = function (sql) {
var statement = new Statement(this, sql);
return statement.run.apply(statement, arguments);
}
/**
* Run a SQL query and triggers the callback once for all result rows
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Connection.prototype.all = function (sql) {
var statement = new Statement(this, sql);
return statement.all.apply(statement, arguments);
}
// Utility class for streaming Apache Arrow IPC
class IpcResultStreamIterator {
constructor(stream_result_p) {
this._depleted = false;
this.stream_result = stream_result_p;
}
async next() {
if (this._depleted) {
return { done: true, value: null };
}
const ipc_raw = await this.stream_result.nextIpcBuffer();
const res = new Uint8Array(ipc_raw);
this._depleted = res.length == 0;
return {
done: this._depleted,
value: res,
};
}
[Symbol.asyncIterator]() {
return this;
}
// Materialize the IPC stream into a list of Uint8Arrays
async toArray () {
const retval = []
for await (const ipc_buf of this) {
retval.push(ipc_buf);
}
// Push EOS message containing 4 bytes of 0
retval.push(new Uint8Array([0,0,0,0]));
return retval;
}
}
/**
* Run a SQL query and serialize the result into the Apache Arrow IPC format (requires arrow extension to be loaded)
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Connection.prototype.arrowIPCAll = function (sql) {
const query = "SELECT * FROM to_arrow_ipc((" + sql + "));";
var statement = new Statement(this, query);
return statement.arrowIPCAll.apply(statement, arguments);
}
/**
* Run a SQL query, returns a IpcResultStreamIterator that allows streaming the result into the Apache Arrow IPC format
* (requires arrow extension to be loaded)
*
* @arg sql
* @param {...*} params
* @param callback
* @return Promise<IpcResultStreamIterator>
*/
Connection.prototype.arrowIPCStream = async function (sql) {
const query = "SELECT * FROM to_arrow_ipc((" + sql + "));";
const statement = new Statement(this, query);
return new IpcResultStreamIterator(await statement.stream.apply(statement, arguments));
}
/**
* Runs a SQL query and triggers the callback for each result row
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Connection.prototype.each = function (sql) {
var statement = new Statement(this, sql);
return statement.each.apply(statement, arguments);
}
/**
* @arg sql
* @param {...*} params
* @yields row chunks
*/
Connection.prototype.stream = async function* (sql) {
const statement = new Statement(this, sql);
const queryResult = await statement.stream.apply(statement, arguments);
for await (const result of queryResult) {
yield result;
}
}
/**
* Register a User Defined Function
*
* @arg name
* @arg return_type
* @arg fun
* @return {void}
* @note this follows the wasm udfs somewhat but is simpler because we can pass data much more cleanly
*/
Connection.prototype.register_udf = function (name, return_type, fun) {
// TODO what if this throws an error somewhere? do we need a try/catch?
return this.register_udf_bulk(name, return_type, function (desc) {
try {
// Build an argument resolver
const buildResolver = (arg) => {
let validity = arg.validity || null;
switch (arg.physicalType) {
case 'STRUCT': {
const tmp = {};
const children = [];
for (let j = 0; j < (arg.children.length || 0); ++j) {
const attr = arg.children[j];
const child = buildResolver(attr);
children.push((row) => {
tmp[attr.name] = child(row);
});
}
if (validity != null) {
return (row) => {
if (!validity[row]) {
return null;
}
for (const resolver of children) {
resolver(row);
}
return tmp;
};
} else {
return (row) => {
for (const resolver of children) {
resolver(row);
}
return tmp;
};
}
}
default: {
if (arg.data === undefined) {
throw new Error(
'malformed data view, expected data buffer for argument of type: ' + arg.physicalType,
);
}
const data = arg.data;
if (validity != null) {
return (row) => (!validity[row] ? null : data[row]);
} else {
return (row) => data[row];
}
}
}
};
// Translate argument data
const argResolvers = [];
for (let i = 0; i < desc.args.length; ++i) {
argResolvers.push(buildResolver(desc.args[i]));
}
const args = [];
for (let i = 0; i < desc.args.length; ++i) {
args.push(null);
}
// Return type
desc.ret.validity = new Uint8Array(desc.rows);
switch (desc.ret.physicalType) {
case 'INT8':
desc.ret.data = new Int8Array(desc.rows);
break;
case 'INT16':
desc.ret.data = new Int16Array(desc.rows);
break;
case 'INT32':
desc.ret.data = new Int32Array(desc.rows);
break;
case 'DOUBLE':
desc.ret.data = new Float64Array(desc.rows);
break;
case 'DATE64':
case 'TIME64':
case 'TIMESTAMP':
case 'INT64':
desc.ret.data = new BigInt64Array(desc.rows);
break;
case 'UINT64':
desc.ret.data = new BigUint64Array(desc.rows);
break;
case 'BLOB':
case 'VARCHAR':
desc.ret.data = new Array(desc.rows);
break;
}
// Call the function
for (let i = 0; i < desc.rows; ++i) {
for (let j = 0; j < desc.args.length; ++j) {
args[j] = argResolvers[j](i);
}
const res = fun(...args);
desc.ret.data[i] = res;
desc.ret.validity[i] = res === undefined || res === null ? 0 : 1;
}
} catch (error) { // work around recently fixed napi bug https://github.com/nodejs/node-addon-api/issues/912
msg = error;
if (typeof error == 'object' && 'message' in error) {
msg = error.message
}
throw { name: 'DuckDB-UDF-Exception', message: msg };
}
})
}
/**
* Prepare a SQL query for execution
* @method
* @arg sql
* @param {...*} params
* @param callback
* @return {Statement}
*/
Connection.prototype.prepare;
/**
* Execute a SQL query
* @method
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Connection.prototype.exec;
/**
* Register a User Defined Function
*
* @method
* @arg name
* @arg return_type
* @param callback
* @return {void}
*/
Connection.prototype.register_udf_bulk;
/**
* Unregister a User Defined Function
*
* @method
* @arg name
* @arg return_type
* @param callback
* @return {void}
*/
Connection.prototype.unregister_udf;
var default_connection = function (o) {
if (o.default_connection == undefined) {
o.default_connection = new duckdb.Connection(o);
}
return o.default_connection;
}
/**
* Register a Buffer to be scanned using the Apache Arrow IPC scanner
* (requires arrow extension to be loaded)
*
* @method
* @arg name
* @arg array
* @arg force
* @param callback
* @return {void}
*/
Connection.prototype.register_buffer;
/**
* Unregister the Buffer
*
* @method
* @arg name
* @param callback
* @return {void}
*/
Connection.prototype.unregister_buffer;
/**
* Closes connection
* @method
* @param callback
* @return {void}
*/
Connection.prototype.close;
/**
* Closes database instance
* @method
* @param callback
* @return {void}
*/
Database.prototype.close = function() {
if (this.default_connection) {
this.default_connection.close(); // this queues up a job in the internals, which blocks the below close call
this.default_connection = null;
}
this.close_internal.apply(this, arguments);
};
/**
* Internal method. Do not use, call Connection#close instead
* @method
* @param callback
* @return {void}
*/
Database.prototype.close_internal;
/**
* Triggers callback when all scheduled database tasks have completed.
* @method
* @param callback
* @return {void}
*/
Database.prototype.wait;
/**
* Currently a no-op. Provided for SQLite compatibility
* @method
* @param callback
* @return {void}
*/
Database.prototype.serialize;
/**
* Currently a no-op. Provided for SQLite compatibility
* @method
* @param callback
* @return {void}
*/
Database.prototype.parallelize;
/**
* Create a new database connection
* @method
* @arg path the database to connect to, either a file path, or `:memory:`
* @return {Connection}
*/
Database.prototype.connect;
/**
* Supposedly interrupt queries, but currently does not do anything.
* @method
* @param callback
* @return {void}
*/
Database.prototype.interrupt;
/**
* Prepare a SQL query for execution
* @arg sql
* @return {Statement}
*/
Database.prototype.prepare = function () {
return default_connection(this).prepare.apply(this.default_connection, arguments);
}
/**
* Convenience method for Connection#run using a built-in default connection
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Database.prototype.run = function () {
default_connection(this).run.apply(this.default_connection, arguments);
return this;
}
/**
* Convenience method for Connection#scanArrowIpc using a built-in default connection
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Database.prototype.scanArrowIpc = function () {
default_connection(this).scanArrowIpc.apply(this.default_connection, arguments);
return this;
}
/**
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Database.prototype.each = function () {
default_connection(this).each.apply(this.default_connection, arguments);
return this;
}
/**
* @arg sql
* @param {...*} params
* @yields row chunks
*/
Database.prototype.stream = function() {
return default_connection(this).stream.apply(this.default_connection, arguments);
}
/**
* Convenience method for Connection#apply using a built-in default connection
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Database.prototype.all = function () {
default_connection(this).all.apply(this.default_connection, arguments);
return this;
}
/**
* Convenience method for Connection#arrowIPCAll using a built-in default connection
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Database.prototype.arrowIPCAll = function () {
default_connection(this).arrowIPCAll.apply(this.default_connection, arguments);
return this;
}
/**
* Convenience method for Connection#arrowIPCStream using a built-in default connection
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Database.prototype.arrowIPCStream = function () {
return default_connection(this).arrowIPCStream.apply(this.default_connection, arguments);
}
/**
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Database.prototype.exec = function () {
default_connection(this).exec.apply(this.default_connection, arguments);
return this;
}
/**
* Register a User Defined Function
*
* Convenience method for Connection#register_udf
* @arg name
* @arg return_type
* @arg fun
* @return {this}
*/
Database.prototype.register_udf = function () {
default_connection(this).register_udf.apply(this.default_connection, arguments);
return this;
}
/**
* Register a buffer containing serialized data to be scanned from DuckDB.
*
* Convenience method for Connection#unregister_buffer
* @arg name
* @return {this}
*/
Database.prototype.register_buffer = function () {
default_connection(this).register_buffer.apply(this.default_connection, arguments);
return this;
}
/**
* Unregister a Buffer
*
* Convenience method for Connection#unregister_buffer
* @arg name
* @return {this}
*/
Database.prototype.unregister_buffer = function () {
default_connection(this).unregister_buffer.apply(this.default_connection, arguments);
return this;
}
/**
* Unregister a UDF
*
* Convenience method for Connection#unregister_udf
* @arg name
* @return {this}
*/
Database.prototype.unregister_udf = function () {
default_connection(this).unregister_udf.apply(this.default_connection, arguments);
return this;
}
/**
* Register a table replace scan function
* @method
* @arg fun Replacement scan function
* @return {this}
*/
Database.prototype.registerReplacementScan;
/**
* Return positions and types of tokens in given text
* @method
* @arg text
* @return {ScriptTokens}
*/
Database.prototype.tokenize;
/**
* Not implemented
*/
Database.prototype.get = function () {
throw "get() is not implemented because it's evil";
}
/**
* Not implemented
*/
Statement.prototype.get = function () {
throw "get() is not implemented because it's evil";
}
/**
* @method
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Statement.prototype.run;
/**
* @method
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Statement.prototype.all;
/**
* @method
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Statement.prototype.arrowIPCAll;
/**
* @method
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Statement.prototype.each;
/**
* @method
* @arg sql
* @param {...*} params
* @param callback
* @return {void}
*/
Statement.prototype.finalize
/**
* @method
* @arg sql
* @param {...*} params
* @yield callback
*/
Statement.prototype.stream;
/**
* @field
* @returns sql contained in statement
*/
Statement.prototype.sql;
/**
* @method
* @return {ColumnInfo[]} - Array of column names and types
*/
Statement.prototype.columns;
/**
* @typedef ColumnInfo
* @type {object}
* @property {string} name - Column name
* @property {TypeInfo} type - Column type
*/
/**
* @typedef TypeInfo
* @type {object}
* @property {string} id - Type ID
* @property {string} [alias] - SQL type alias
* @property {string} sql_type - SQL type name
*/
/**
* @typedef DuckDbError
* @type {object}
* @property {number} errno - -1 for DuckDB errors
* @property {string} message - Error message
* @property {string} code - 'DUCKDB_NODEJS_ERROR' for DuckDB errors
* @property {string} errorType - DuckDB error type code (eg, HTTP, IO, Catalog)
*/
/**
* @typedef HTTPError
* @type {object}
* @extends {DuckDbError}
* @property {number} statusCode - HTTP response status code
* @property {string} reason - HTTP response reason
* @property {string} response - HTTP response body
* @property {object} headers - HTTP headers
*/