node-query/node_modules/node-firebird/lib/index.js

2716 lines
74 KiB
JavaScript

var
net = require('net'),
os = require('os'),
Events = require('events'),
serialize = require('./serialize.js'),
XdrReader = serialize.XdrReader,
BlrReader = serialize.BlrReader,
XdrWriter = serialize.XdrWriter,
BlrWriter = serialize.BlrWriter,
messages = require('./messages.js');
if (typeof(setImmediate) === 'undefined') {
global.setImmediate = function(cb) {
process.nextTick(cb);
};
}
/**
* Parse date from string
* @return {Date}
*/
if (String.prototype.parseDate === undefined) {
String.prototype.parseDate = function() {
var self = this.trim();
var arr = self.indexOf(' ') === -1 ? self.split('T') : self.split(' ');
var index = arr[0].indexOf(':');
var length = arr[0].length;
if (index !== -1) {
var tmp = arr[1];
arr[1] = arr[0];
arr[0] = tmp;
}
if (arr[0] === undefined)
arr[0] = '';
var noTime = arr[1] === undefined ? true : arr[1].length === 0;
for (var i = 0; i < length; i++) {
var c = arr[0].charCodeAt(i);
if (c > 47 && c < 58)
continue;
if (c === 45 || c === 46)
continue;
if (noTime)
return new Date(self);
}
if (arr[1] === undefined)
arr[1] = '00:00:00';
var firstDay = arr[0].indexOf('-') === -1;
var date = (arr[0] || '').split(firstDay ? '.' : '-');
var time = (arr[1] || '').split(':');
var parsed = [];
if (date.length < 4 && time.length < 2)
return new Date(self);
index = (time[2] || '').indexOf('.');
// milliseconds
if (index !== -1) {
time[3] = time[2].substring(index + 1);
time[2] = time[2].substring(0, index);
} else
time[3] = '0';
parsed.push(parseInt(date[firstDay ? 2 : 0], 10)); // year
parsed.push(parseInt(date[1], 10)); // month
parsed.push(parseInt(date[firstDay ? 0 : 2], 10)); // day
parsed.push(parseInt(time[0], 10)); // hours
parsed.push(parseInt(time[1], 10)); // minutes
parsed.push(parseInt(time[2], 10)); // seconds
parsed.push(parseInt(time[3], 10)); // miliseconds
var def = new Date();
for (var i = 0, length = parsed.length; i < length; i++) {
if (isNaN(parsed[i]))
parsed[i] = 0;
var value = parsed[i];
if (value !== 0)
continue;
switch (i) {
case 0:
if (value <= 0)
parsed[i] = def.getFullYear();
break;
case 1:
if (value <= 0)
parsed[i] = def.getMonth() + 1;
break;
case 2:
if (value <= 0)
parsed[i] = def.getDate();
break;
}
}
return new Date(parsed[0], parsed[1] - 1, parsed[2], parsed[3], parsed[4], parsed[5]);
};
}
function noop() {}
const
op_void = 0, // Packet has been voided
op_connect = 1, // Connect to remote server
op_exit = 2, // Remote end has exitted
op_accept = 3, // Server accepts connection
op_reject = 4, // Server rejects connection
op_disconnect = 6, // Connect is going away
op_response = 9, // Generic response block
// Full context server operations
op_attach = 19, // Attach database
op_create = 20, // Create database
op_detach = 21, // Detach database
op_compile = 22, // Request based operations
op_start = 23,
op_start_and_send = 24,
op_send = 25,
op_receive = 26,
op_unwind = 27, // apparently unused, see protocol.cpp's case op_unwind
op_release = 28,
op_transaction = 29, // Transaction operations
op_commit = 30,
op_rollback = 31,
op_prepare = 32,
op_reconnect = 33,
op_create_blob = 34, // Blob operations
op_open_blob = 35,
op_get_segment = 36,
op_put_segment = 37,
op_cancel_blob = 38,
op_close_blob = 39,
op_info_database = 40, // Information services
op_info_request = 41,
op_info_transaction = 42,
op_info_blob = 43,
op_batch_segments = 44, // Put a bunch of blob segments
op_que_events = 48, // Que event notification request
op_cancel_events = 49, // Cancel event notification request
op_commit_retaining = 50, // Commit retaining (what else)
op_prepare2 = 51, // Message form of prepare
op_event = 52, // Completed event request (asynchronous)
op_connect_request = 53, // Request to establish connection
op_aux_connect = 54, // Establish auxiliary connection
op_ddl = 55, // DDL call
op_open_blob2 = 56,
op_create_blob2 = 57,
op_get_slice = 58,
op_put_slice = 59,
op_slice = 60, // Successful response to op_get_slice
op_seek_blob = 61, // Blob seek operation
// DSQL operations
op_allocate_statement = 62, // allocate a statment handle
op_execute = 63, // execute a prepared statement
op_exec_immediate = 64, // execute a statement
op_fetch = 65, // fetch a record
op_fetch_response = 66, // response for record fetch
op_free_statement = 67, // free a statement
op_prepare_statement = 68, // prepare a statement
op_set_cursor = 69, // set a cursor name
op_info_sql = 70,
op_dummy = 71, // dummy packet to detect loss of client
op_response_piggyback = 72, // response block for piggybacked messages
op_start_and_receive = 73,
op_start_send_and_receive = 74,
op_exec_immediate2 = 75, // execute an immediate statement with msgs
op_execute2 = 76, // execute a statement with msgs
op_insert = 77,
op_sql_response = 78, // response from execute, exec immed, insert
op_transact = 79,
op_transact_response = 80,
op_drop_database = 81,
op_service_attach = 82,
op_service_detach = 83,
op_service_info = 84,
op_service_start = 85,
op_rollback_retaining = 86,
op_partial = 89, // packet is not complete - delay processing
op_trusted_auth = 90,
op_cancel = 91,
op_cont_auth = 92,
op_ping = 93,
op_accept_data = 94, // Server accepts connection and returns some data to client
op_abort_aux_connection = 95, // Async operation - stop waiting for async connection to arrive
op_crypt = 96,
op_crypt_key_callback = 97,
op_cond_accept = 98; // Server accepts connection, returns some data to client
// and asks client to continue authentication before attach call
const
CONNECT_VERSION2 = 2;
ARCHITECTURE_GENERIC = 1;
const
// Protocol 10 includes support for warnings and removes the requirement for
// encoding and decoding status codes
PROTOCOL_VERSION10 = 10,
// Since protocol 11 we must be separated from Borland Interbase.
// Therefore always set highmost bit in protocol version to 1.
// For unsigned protocol version this does not break version's compare.
FB_PROTOCOL_FLAG = 0x8000,
// Protocol 11 has support for user authentication related
// operations (op_update_account_info, op_authenticate_user and
// op_trusted_auth). When specific operation is not supported,
// we say "sorry".
PROTOCOL_VERSION11 = (FB_PROTOCOL_FLAG | 11),
// Protocol 12 has support for asynchronous call op_cancel.
// Currently implemented asynchronously only for TCP/IP.
PROTOCOL_VERSION12 = (FB_PROTOCOL_FLAG | 12),
// Protocol 13 has support for authentication plugins (op_cont_auth).
PROTOCOL_VERSION13 = (FB_PROTOCOL_FLAG | 13);
const
DSQL_close = 1,
DSQL_drop = 2,
DSQL_unprepare = 4; // >= 2.5
const
ptype_batch_send = 3;
const
SQL_TEXT = 452, // Array of char
SQL_VARYING = 448,
SQL_SHORT = 500,
SQL_LONG = 496,
SQL_FLOAT = 482,
SQL_DOUBLE = 480,
SQL_D_FLOAT = 530,
SQL_TIMESTAMP = 510,
SQL_BLOB = 520,
SQL_ARRAY = 540,
SQL_QUAD = 550,
SQL_TYPE_TIME = 560,
SQL_TYPE_DATE = 570,
SQL_INT64 = 580,
SQL_BOOLEAN = 32764, // >= 3.0
SQL_NULL = 32766; // >= 2.5
/***********************/
/* ISC Error Codes */
/***********************/
const
isc_arg_end = 0, // end of argument list
isc_arg_gds = 1, // generic DSRI status value
isc_arg_string = 2, // string argument
isc_arg_cstring = 3, // count & string argument
isc_arg_number = 4, // numeric argument (long)
isc_arg_interpreted = 5, // interpreted status code (string)
isc_arg_unix = 7, // UNIX error code
isc_arg_next_mach = 15, // NeXT/Mach error code
isc_arg_win32 = 17, // Win32 error code
isc_arg_warning = 18, // warning argument
isc_arg_sql_state = 19; // SQLSTATE
const
isc_sqlerr = 335544436;
/**********************************/
/* Database parameter block stuff */
/**********************************/
const
isc_dpb_version1 = 1,
isc_dpb_version2 = 2, // >= FB30
isc_dpb_cdd_pathname = 1,
isc_dpb_allocation = 2,
isc_dpb_journal = 3,
isc_dpb_page_size = 4,
isc_dpb_num_buffers = 5,
isc_dpb_buffer_length = 6,
isc_dpb_debug = 7,
isc_dpb_garbage_collect = 8,
isc_dpb_verify = 9,
isc_dpb_sweep = 10,
isc_dpb_enable_journal = 11,
isc_dpb_disable_journal = 12,
isc_dpb_dbkey_scope = 13,
isc_dpb_number_of_users = 14,
isc_dpb_trace = 15,
isc_dpb_no_garbage_collect = 16,
isc_dpb_damaged = 17,
isc_dpb_license = 18,
isc_dpb_sys_user_name = 19,
isc_dpb_encrypt_key = 20,
isc_dpb_activate_shadow = 21,
isc_dpb_sweep_interval = 22,
isc_dpb_delete_shadow = 23,
isc_dpb_force_write = 24,
isc_dpb_begin_log = 25,
isc_dpb_quit_log = 26,
isc_dpb_no_reserve = 27,
isc_dpb_user_name = 28,
isc_dpb_password = 29,
isc_dpb_password_enc = 30,
isc_dpb_sys_user_name_enc = 31,
isc_dpb_interp = 32,
isc_dpb_online_dump = 33,
isc_dpb_old_file_size = 34,
isc_dpb_old_num_files = 35,
isc_dpb_old_file = 36,
isc_dpb_old_start_page = 37,
isc_dpb_old_start_seqno = 38,
isc_dpb_old_start_file = 39,
isc_dpb_old_dump_id = 41,
isc_dpb_lc_messages = 47,
isc_dpb_lc_ctype = 48,
isc_dpb_cache_manager = 49,
isc_dpb_shutdown = 50,
isc_dpb_online = 51,
isc_dpb_shutdown_delay = 52,
isc_dpb_reserved = 53,
isc_dpb_overwrite = 54,
isc_dpb_sec_attach = 55,
isc_dpb_connect_timeout = 57,
isc_dpb_dummy_packet_interval = 58,
isc_dpb_gbak_attach = 59,
isc_dpb_sql_role_name = 60,
isc_dpb_set_page_buffers = 61,
isc_dpb_working_directory = 62,
isc_dpb_sql_dialect = 63,
isc_dpb_set_db_readonly = 64,
isc_dpb_set_db_sql_dialect = 65,
isc_dpb_gfix_attach = 66,
isc_dpb_gstat_attach = 67,
isc_dpb_set_db_charset = 68,
isc_dpb_gsec_attach = 69,
isc_dpb_address_path = 70,
isc_dpb_process_id = 71,
isc_dpb_no_db_triggers = 72,
isc_dpb_trusted_auth = 73,
isc_dpb_process_name = 74,
isc_dpb_trusted_role = 75,
isc_dpb_org_filename = 76,
isc_dpb_utf8_filename = 77,
isc_dpb_ext_call_depth = 78;
/*************************************/
/* Transaction parameter block stuff */
/*************************************/
const
isc_tpb_version1 = 1,
isc_tpb_version3 = 3,
isc_tpb_consistency = 1,
isc_tpb_concurrency = 2,
isc_tpb_shared = 3, // < FB21
isc_tpb_protected = 4, // < FB21
isc_tpb_exclusive = 5, // < FB21
isc_tpb_wait = 6,
isc_tpb_nowait = 7,
isc_tpb_read = 8,
isc_tpb_write = 9,
isc_tpb_lock_read = 10,
isc_tpb_lock_write = 11,
isc_tpb_verb_time = 12,
isc_tpb_commit_time = 13,
isc_tpb_ignore_limbo = 14,
isc_tpb_read_committed = 15,
isc_tpb_autocommit = 16,
isc_tpb_rec_version = 17,
isc_tpb_no_rec_version = 18,
isc_tpb_restart_requests = 19,
isc_tpb_no_auto_undo = 20,
isc_tpb_lock_timeout = 21; // >= FB20
/****************************/
/* Common, structural codes */
/****************************/
const
isc_info_end = 1,
isc_info_truncated = 2,
isc_info_error = 3,
isc_info_data_not_ready = 4,
isc_info_length = 126,
isc_info_flag_end = 127;
/*************************/
/* SQL information items */
/*************************/
const
isc_info_sql_select = 4,
isc_info_sql_bind = 5,
isc_info_sql_num_variables = 6,
isc_info_sql_describe_vars = 7,
isc_info_sql_describe_end = 8,
isc_info_sql_sqlda_seq = 9,
isc_info_sql_message_seq = 10,
isc_info_sql_type = 11,
isc_info_sql_sub_type = 12,
isc_info_sql_scale = 13,
isc_info_sql_length = 14,
isc_info_sql_null_ind = 15,
isc_info_sql_field = 16,
isc_info_sql_relation = 17,
isc_info_sql_owner = 18,
isc_info_sql_alias = 19,
isc_info_sql_sqlda_start = 20,
isc_info_sql_stmt_type = 21,
isc_info_sql_get_plan = 22,
isc_info_sql_records = 23,
isc_info_sql_batch_fetch = 24,
isc_info_sql_relation_alias = 25, // >= 2.0
isc_info_sql_explain_plan = 26; // >= 3.0
/*******************/
/* Blr definitions */
/*******************/
const
blr_text = 14,
blr_text2 = 15,
blr_short = 7,
blr_long = 8,
blr_quad = 9,
blr_float = 10,
blr_double = 27,
blr_d_float = 11,
blr_timestamp = 35,
blr_varying = 37,
blr_varying2 = 38,
blr_blob = 261,
blr_cstring = 40,
blr_cstring2 = 41,
blr_blob_id = 45,
blr_sql_date = 12,
blr_sql_time = 13,
blr_int64 = 16,
blr_blob2 = 17, // >= 2.0
blr_domain_name = 18, // >= 2.1
blr_domain_name2 = 19, // >= 2.1
blr_not_nullable = 20, // >= 2.1
blr_column_name = 21, // >= 2.5
blr_column_name2 = 22, // >= 2.5
blr_bool = 23, // >= 3.0
blr_version4 = 4,
blr_version5 = 5, // dialect 3
blr_eoc = 76,
blr_end = 255,
blr_assignment = 1,
blr_begin = 2,
blr_dcl_variable = 3,
blr_message = 4;
const
isc_info_sql_stmt_select = 1,
isc_info_sql_stmt_insert = 2,
isc_info_sql_stmt_update = 3,
isc_info_sql_stmt_delete = 4,
isc_info_sql_stmt_ddl = 5,
isc_info_sql_stmt_get_segment = 6,
isc_info_sql_stmt_put_segment = 7,
isc_info_sql_stmt_exec_procedure = 8,
isc_info_sql_stmt_start_trans = 9,
isc_info_sql_stmt_commit = 10,
isc_info_sql_stmt_rollback = 11,
isc_info_sql_stmt_select_for_upd = 12,
isc_info_sql_stmt_set_generator = 13,
isc_info_sql_stmt_savepoint = 14;
const
isc_blob_text = 1;
const
DESCRIBE =
[isc_info_sql_stmt_type,
isc_info_sql_select,
isc_info_sql_describe_vars,
isc_info_sql_sqlda_seq,
isc_info_sql_type,
isc_info_sql_sub_type,
isc_info_sql_scale,
isc_info_sql_length,
isc_info_sql_field,
isc_info_sql_relation,
//isc_info_sql_owner,
isc_info_sql_alias,
isc_info_sql_describe_end,
isc_info_sql_bind,
isc_info_sql_describe_vars,
isc_info_sql_sqlda_seq,
isc_info_sql_type,
isc_info_sql_sub_type,
isc_info_sql_scale,
isc_info_sql_length,
isc_info_sql_describe_end];
const
ISOLATION_READ_UNCOMMITTED = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_rec_version],
ISOLATION_READ_COMMITED = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_no_rec_version],
ISOLATION_REPEATABLE_READ = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_concurrency],
ISOLATION_SERIALIZABLE = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_consistency],
ISOLATION_READ_COMMITED_READ_ONLY = [isc_tpb_version3, isc_tpb_read, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_no_rec_version];
const
DEFAULT_HOST = '127.0.0.1',
DEFAULT_PORT = 3050,
DEFAULT_USER = 'SYSDBA',
DEFAULT_PASSWORD = 'masterkey',
DEFAULT_PAGE_SIZE = 4096;
exports.ISOLATION_READ_UNCOMMITTED = ISOLATION_READ_UNCOMMITTED;
exports.ISOLATION_READ_COMMITED = ISOLATION_READ_COMMITED;
exports.ISOLATION_REPEATABLE_READ = ISOLATION_REPEATABLE_READ;
exports.ISOLATION_SERIALIZABLE = ISOLATION_SERIALIZABLE;
exports.ISOLATION_READ_COMMITED_READ_ONLY = ISOLATION_READ_COMMITED_READ_ONLY;
if (!String.prototype.padLeft) {
String.prototype.padLeft = function(max, c) {
var self = this;
return new Array(Math.max(0, max - self.length + 1)).join(c || ' ') + self;
};
}
/**
* Escape value
* @param {Object} value
* @return {String}
*/
exports.escape = function(value) {
if (value === null || value === undefined)
return 'NULL';
switch (typeof(value)) {
case 'boolean':
return value ? '1' : '0';
case 'number':
return value.toString();
case 'string':
return "'" + value.replace(/'/g, "''").replace(/\\/g, '\\\\') + "'";
}
if (value instanceof Date)
return "'" + value.getFullYear() + '-' + value.getMonth().toString().padLeft(2, '0') + '-' + value.getDate().toString().padLeft(2, '0') + ' ' + value.getHours().toString().padLeft(2, '0') + ':' + value.getMinutes().toString().padLeft(2, '0') + ':' + value.getSeconds().toString().padLeft(2, '0') + "'";
throw new Error('Escape supports only primitive values.');
};
const
DEFAULT_ENCODING = 'utf8';
DEFAULT_FETCHSIZE = 200;
const
MAX_INT = Math.pow(2, 31) - 1;
MIN_INT = - Math.pow(2, 31);
/***************************************
*
* SQLVar
*
***************************************/
const
ScaleDivisor = [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000, 100000000000,1000000000000,10000000000000,100000000000000,1000000000000000];
const
DateOffset = 40587,
TimeCoeff = 86400000;
MsPerMinute = 60000;
//------------------------------------------------------
function SQLVarText() {}
SQLVarText.prototype.decode = function(data) {
var ret;
if (this.subType > 1) {
ret = data.readText(this.length, DEFAULT_ENCODING);
} else {
ret = data.readBuffer(this.length);
}
if (!data.readInt()) {
return ret;
}
return null;
};
SQLVarText.prototype.calcBlr = function(blr) {
blr.addByte(blr_text);
blr.addWord(this.length);
};
//------------------------------------------------------
function SQLVarNull() {}
SQLVarNull.prototype = new SQLVarText();
SQLVarNull.prototype.constructor = SQLVarNull;
//------------------------------------------------------
function SQLVarString() {}
SQLVarString.prototype.decode = function(data) {
var ret;
if (this.subType > 1) {
ret = data.readString(DEFAULT_ENCODING)
} else {
ret = data.readBuffer()
}
if (!data.readInt()) {
return ret;
}
return null;
};
SQLVarString.prototype.calcBlr = function(blr) {
blr.addByte(blr_varying);
blr.addWord(this.length);
};
//------------------------------------------------------
function SQLVarQuad() {}
SQLVarQuad.prototype.decode = function(data) {
var ret = data.readQuad();
if (!data.readInt()) {
return ret;
}
return null;
};
SQLVarQuad.prototype.calcBlr = function(blr) {
blr.addByte(blr_quad);
blr.addShort(this.scale);
};
//------------------------------------------------------
function SQLVarBlob() {}
SQLVarBlob.prototype = new SQLVarQuad();
SQLVarBlob.prototype.constructor = SQLVarBlob;
SQLVarBlob.prototype.calcBlr = function(blr) {
blr.addByte(blr_quad);
blr.addShort(0);
};
//------------------------------------------------------
function SQLVarArray() {}
SQLVarArray.prototype = new SQLVarQuad();
SQLVarArray.prototype.constructor = SQLVarArray;
SQLVarArray.prototype.calcBlr = function(blr) {
blr.addByte(blr_quad);
blr.addShort(0);
};
//------------------------------------------------------
function SQLVarInt() {}
SQLVarInt.prototype.decode = function(data) {
var ret = data.readInt();
if (!data.readInt()) {
if (this.scale) {
ret = ret / ScaleDivisor[Math.abs(this.scale)];
}
return ret;
}
return null;
};
SQLVarInt.prototype.calcBlr = function(blr) {
blr.addByte(blr_long);
blr.addShort(this.scale);
};
//------------------------------------------------------
function SQLVarShort() {}
SQLVarShort.prototype = new SQLVarInt();
SQLVarShort.prototype.constructor = SQLVarShort;
SQLVarShort.prototype.calcBlr = function(blr) {
blr.addByte(blr_short);
blr.addShort(this.scale);
};
//------------------------------------------------------
function SQLVarInt64() {}
SQLVarInt64.prototype.decode = function(data) {
var ret = data.readInt64();
if (!data.readInt()) {
if (this.scale) {
ret = ret / ScaleDivisor[Math.abs(this.scale)];
}
return ret;
}
return null;
};
SQLVarInt64.prototype.calcBlr = function(blr) {
blr.addByte(blr_int64);
blr.addShort(this.scale);
};
//------------------------------------------------------
function SQLVarFloat() {}
SQLVarFloat.prototype.decode = function(data) {
var ret = data.readFloat();
if (!data.readInt()) {
return ret;
}
return null;
};
SQLVarFloat.prototype.calcBlr = function(blr) {
blr.addByte(blr_float);
};
//------------------------------------------------------
function SQLVarDouble() {}
SQLVarDouble.prototype.decode = function(data) {
var ret = data.readDouble();
if (!data.readInt()) {
return ret;
}
return null;
};
SQLVarDouble.prototype.calcBlr = function(blr) {
blr.addByte(blr_double);
};
//------------------------------------------------------
function SQLVarDate() {}
SQLVarDate.prototype.decode = function(data) {
var ret = data.readInt();
if (!data.readInt()) {
var d = new Date(0);
d.setMilliseconds((ret - DateOffset) * TimeCoeff + d.getTimezoneOffset() * MsPerMinute);
return d;
}
return null;
};
SQLVarDate.prototype.calcBlr = function(blr) {
blr.addByte(blr_sql_date);
};
//------------------------------------------------------
function SQLVarTime() {}
SQLVarTime.prototype.decode = function(data) {
var ret = data.readUInt();
if (!data.readInt()) {
var d = new Date(0);
d.setMilliseconds(Math.floor(ret / 10) + d.getTimezoneOffset() * MsPerMinute);
return d;
}
return null;
};
SQLVarTime.prototype.calcBlr = function(blr) {
blr.addByte(blr_sql_time);
};
//------------------------------------------------------
function SQLVarTimeStamp() {}
SQLVarTimeStamp.prototype.decode = function(data) {
var date = data.readInt();
var time = data.readUInt();
if (!data.readInt()) {
var d = new Date(0);
d.setMilliseconds((date - DateOffset) * TimeCoeff + Math.floor(time / 10) + d.getTimezoneOffset() * MsPerMinute);
return d;
}
return null;
};
SQLVarTimeStamp.prototype.calcBlr = function(blr) {
blr.addByte(blr_timestamp);
};
//------------------------------------------------------
// todo: test it
function SQLVarBoolean() {}
SQLVarBoolean.prototype.decode = function(data) {
var ret = data.readInt();
if (!data.readInt()) {
return Boolean(ret);
}
return null;
};
SQLVarBoolean.prototype.calcBlr = function(blr) {
blr.addByte(blr_bool);
};
//------------------------------------------------------
function SQLParamInt(value){
this.value = value;
}
SQLParamInt.prototype.calcBlr = function(blr) {
blr.addByte(blr_long);
blr.addShort(0);
};
SQLParamInt.prototype.encode = function(data) {
if (this.value != null) {
data.addInt(this.value);
data.addInt(0);
} else {
data.addInt(0);
data.addInt(1);
}
};
//------------------------------------------------------
function SQLParamInt64(value){
this.value = value;
}
SQLParamInt64.prototype.calcBlr = function(blr) {
blr.addByte(blr_int64);
blr.addShort(0);
};
SQLParamInt64.prototype.encode = function(data) {
if (this.value != null) {
data.addInt64(this.value);
data.addInt(0);
} else {
data.addInt64(0);
data.addInt(1);
}
};
//------------------------------------------------------
function SQLParamDouble(value) {
this.value = value;
}
SQLParamDouble.prototype.encode = function(data) {
if (this.value != null) {
data.addDouble(this.value);
data.addInt(0);
} else {
data.addDouble(0);
data.addInt(1);
}
};
SQLParamDouble.prototype.calcBlr = function(blr) {
blr.addByte(blr_double);
};
//------------------------------------------------------
function SQLParamString(value) {
this.value = value;
}
SQLParamString.prototype.encode = function(data) {
if (this.value != null) {
data.addText(this.value, DEFAULT_ENCODING);
data.addInt(0);
} else {
data.addInt(1);
}
};
SQLParamString.prototype.calcBlr = function(blr) {
blr.addByte(blr_text);
var len = this.value ? Buffer.byteLength(this.value, DEFAULT_ENCODING) : 0;
blr.addWord(len);
};
//------------------------------------------------------
function SQLParamQuad(value) {
this.value = value;
}
SQLParamQuad.prototype.encode = function(data) {
if (this.value != null) {
data.addInt(this.value.high);
data.addInt(this.value.low);
data.addInt(0);
} else {
data.addInt(0);
data.addInt(0);
data.addInt(1);
}
};
SQLParamQuad.prototype.calcBlr = function(blr) {
blr.addByte(blr_quad);
blr.addShort(0);
};
//------------------------------------------------------
function SQLParamDate(value) {
this.value = value;
}
SQLParamDate.prototype.encode = function(data) {
if (this.value != null) {
var value = this.value.getTime() - this.value.getTimezoneOffset() * MsPerMinute;
var time = value % TimeCoeff;
var date = (value - time) / TimeCoeff + DateOffset;
time *= 10;
data.addInt(date);
data.addUInt(time);
data.addInt(0);
} else {
data.addInt(0);
data.addUInt(0);
data.addInt(1);
}
};
SQLParamDate.prototype.calcBlr = function(blr) {
blr.addByte(blr_timestamp);
};
//------------------------------------------------------
function SQLParamBool(value) {
this.value = value;
}
SQLParamBool.prototype.encode = function(data) {
if (this.value != null) {
data.addInt(this.value ? 1 : 0);
data.addInt(0);
} else {
data.addInt(0);
data.addInt(1);
}
};
SQLParamBool.prototype.calcBlr = function(blr) {
blr.addByte(blr_short);
blr.addShort(0);
};
/***************************************
*
* Error handling
*
***************************************/
function isError(obj) {
return (obj instanceof Object && obj.status);
}
function doCallback(obj, callback) {
if (!callback)
return;
if (isError(obj)) {
callback(new Error(obj.message));
return;
}
callback(undefined, obj);
}
function doError(obj, callback) {
if (callback)
callback(obj)
}
/***************************************
*
* Statement
*
***************************************/
function Statement(connection) {
this.connection = connection;
}
Statement.prototype.close = function(callback) {
this.connection.closeStatement(this, callback);
};
Statement.prototype.drop = function(callback) {
this.connection.dropStatement(this, callback);
};
Statement.prototype.execute = function(transaction, params, callback, custom) {
if (params instanceof Function) {
custom = callback;
callback = params;
params = undefined;
}
this.custom = custom;
this.connection.executeStatement(transaction, this, params, callback, custom);
};
Statement.prototype.fetch = function(transaction, count, callback) {
this.connection.fetch(this, transaction, count, callback);
};
Statement.prototype.fetchAll = function(transaction, callback) {
this.connection.fetchAll(this, transaction, callback);
};
/***************************************
*
* Transaction
*
***************************************/
function Transaction(connection) {
this.connection = connection;
this.db = connection.db;
}
Transaction.prototype.newStatement = function(query, callback) {
var cnx = this.connection;
var self = this;
cnx.allocateStatement(function(err, statement) {
if (err) {
doError(err, callback);
return;
}
cnx.prepareStatement(self, statement, query, false, callback);
});
};
Transaction.prototype.execute = function(query, params, callback, custom) {
if (params instanceof Function) {
callback = params;
params = undefined;
}
var self = this;
this.newStatement(query, function(err, statement) {
if (err) {
doError(err, callback);
return;
}
function dropError(err) {
statement.drop();
doCallback(err, callback);
}
statement.execute(self, params, function(err) {
if (err) {
dropError(err);
return;
}
switch (statement.type) {
case isc_info_sql_stmt_select:
statement.fetchAll(self, function(err, ret) {
if (err) {
dropError(err);
return;
}
statement.drop();
if (callback)
callback(undefined, ret, statement.output, true);
});
break;
case isc_info_sql_stmt_exec_procedure:
if (statement.output.length) {
statement.fetch(self, 1, function(err, ret) {
if (err) {
dropError(err);
return;
}
statement.drop();
if (callback)
callback(undefined, ret.data[0], statement.output, false);
});
break;
}
// Fall through is normal
default:
statement.drop();
if (callback)
callback()
break;
}
}, custom);
});
};
Transaction.prototype.query = function(query, params, callback) {
if (params instanceof Function) {
callback = params;
params = undefined;
}
if (callback === undefined)
callback = noop;
this.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null });
};
Transaction.prototype.commit = function(callback) {
this.connection.commit(this, callback);
};
Transaction.prototype.rollback = function(callback) {
this.connection.rollback(this, callback);
};
Transaction.prototype.commitRetaining = function(callback) {
this.connection.commitRetaining(this, callback);
};
Transaction.prototype.rollbackRetaining = function(callback) {
this.connection.rollbackRetaining(this, callback);
};
/***************************************
*
* Database
*
***************************************/
function Database(connection) {
this.connection = connection;
connection.db = this;
}
Database.prototype.__proto__ = new Events.EventEmitter();
Database.prototype.escape = function(value) {
return exports.escape(value);
};
Database.prototype.detach = function(callback, force) {
var self = this;
if (!force && self.connection._pending.length > 0) {
self.connection._detachAuto = true;
self.connection._detachCallback = callback;
return self;
}
self.connection.detach(function(err, obj) {
self.connection.disconnect();
self.emit('detach', false);
if (callback)
callback(err, obj);
}, force);
return self;
};
Database.prototype.transaction = function(isolation, callback) {
return this.startTransaction(isolation, callback);
};
Database.prototype.startTransaction = function(isolation, callback) {
this.connection.startTransaction(isolation, callback);
return this;
};
Database.prototype.newStatement = function (query, callback) {
this.startTransaction(function(err, transaction) {
if (err) {
callback(err);
return;
}
transaction.newStatement(query, function(err, statement) {
if (err) {
callback(err);
return;
}
transaction.commit(function(err) {
callback(err, statement);
});
});
});
return this;
};
Database.prototype.execute = function(query, params, callback, custom) {
if (params instanceof Function) {
callback = params;
params = undefined;
}
var self = this;
self.connection.startTransaction(function(err, transaction) {
if (err) {
doError(err, callback);
return;
}
transaction.execute(query, params, function(err, result, meta, isSelect) {
if (err) {
transaction.rollback(function() {
doError(err, callback);
});
return;
}
transaction.commit(function(err) {
if (callback)
callback(err, result, meta, isSelect);
});
}, custom);
});
return self;
};
Database.prototype.sequentially = function(query, params, on, callback, asArray) {
if (params instanceof Function) {
asArray = callback;
callback = on;
on = params;
params = undefined;
}
if (on === undefined)
throw new Error('Expected "on" delegate.');
var self = this;
self.execute(query, params, callback, { asObject: !asArray, asStream: true, on: on });
return self;
};
Database.prototype.query = function(query, params, callback) {
if (params instanceof Function) {
callback = params;
params = undefined;
}
var self = this;
self.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null });
return self;
};
exports.attach = function(options, callback) {
var host = options.host || DEFAULT_HOST;
var port = options.port || DEFAULT_PORT;
var cnx = this.connection = new Connection(host, port, function(err) {
if (err) {
doError(err, callback);
return;
}
cnx.connect(options.database || options.filename, function(err) {
if (err)
doError(err, callback);
else
cnx.attach(options, callback);
});
}, options);
};
exports.create = function(options, callback) {
var host = options.host || DEFAULT_HOST;
var port = options.port || DEFAULT_PORT;
var cnx = this.connection = new Connection(host, port, function(err) {
cnx.connect(options.database || options.filename, function(err) {
if (err) {
self.db.emit('error', err);
doError(err, callback);
return;
}
cnx.createDatabase(options, callback);
});
}, options);
};
exports.attachOrCreate = function(options, callback) {
var host = options.host || DEFAULT_HOST;
var port = options.port || DEFAULT_PORT;
var cnx = this.connection = new Connection(host, port, function(err) {
var self = cnx;
if (err) {
callback({ error: err, message: "Connect error" });
return;
}
cnx.connect(options.database || options.filename, function(err) {
if (err) {
doError(err, callback);
return;
}
cnx.attach(options, function(err, ret) {
if (!err) {
if (self.db)
self.db.emit('connect', ret);
doCallback(ret, callback);
return;
}
cnx.createDatabase(options, callback);
});
});
}, options);
};
// Pooling
exports.pool = function(max, options, callback) {
var pool = new Pool();
options.isPool = true;
function create(max) {
exports.attach(options, function(err, db) {
if (err)
throw err;
max--;
pool.db.push(db);
poolEvents(db, pool);
if (max <= 0) {
pool.isReady = true;
pool.check();
if (callback)
callback(null, pool);
return;
}
create(max);
});
};
create(max);
return pool;
};
function poolEvents(db, pool) {
db.on('detach', function(is) {
if (!is)
return;
db.connection._queue = [];
db.connection._pending = [];
db.connection._isUsed = false;
setImmediate(function() {
pool.check();
});
});
}
/***************************************
*
* Simple Pooling
*
***************************************/
function Pool() {
this.db = [];
this.pending = [];
this.isReady = false;
this.isDestroy = false;
}
Pool.prototype.get = function(callback) {
var self = this;
if (self.isDestroy)
return self;
self.pending.push(callback);
self.check();
return self;
};
Pool.prototype.check = function() {
var self = this;
for (var i = 0, length = self.db.length; i < length; i++) {
var db = self.db[i];
if (db.connection._isUsed)
continue;
db.removeAllListeners();
poolEvents(db, self);
var cb = self.pending.shift();
if (cb) {
db.connection._isUsed = true;
cb(null, db);
}
return self;
}
return self;
};
Pool.prototype.detach = function() {
var self = this;
var count = self.db.length;
var fn = function() {
count--;
if (count > 0 || !self.isDestroy)
return;
self.db = null;
self.pending = null;
};
for (var i = 0; i < self.db.length; i++)
self.db[i].detach(fn, true);
return self;
};
Pool.prototype.destroy = function() {
var self = this;
self.detach();
self.isDestroy = true;
return self;
};
/***************************************
*
* Connection
*
***************************************/
var Connection = exports.Connection = function (host, port, callback, options, db) {
var self = this;
this.db = db;
this._msg = new XdrWriter(32);
this._blr = new BlrWriter(32);
this._queue = [];
this._detachTimeout;
this._detachCallback;
this._detachAuto;
this._socket = net.createConnection(port, host);
this._pending = [];
this._isClosed = false;
this._isDetach = false;
this._isUsed = false;
this.options = options;
this._bind_events(host, port, callback);
this.error;
};
exports.Connection.prototype._bind_events = function(host, port, callback) {
var self = this;
self._socket.on('close', function() {
self._isClosed = true;
if (self._isDetach)
return;
if (!self.db) {
if (callback)
callback(self.error);
return;
}
setImmediate(function() {
self._socket = null;
self._msg = null;
self._blr = null;
var ctx = new Connection(host, port, function(err) {
ctx.connect(self.options.filename, function(err) {
if (err) {
self.emit('error', err);
return;
}
ctx.attach(self.options, function(err) {
if (err) {
self.emit('error', err);
return;
}
ctx._queue = ctx._queue.concat(self._queue);
ctx._pending = ctx._pending.concat(self._pending);
self.db.emit('reconnect');
}, self.db);
});
}, self.options, self.db);
});
});
self._socket.on('error', function(e) {
self.error = e;
if (self.db)
self.db.emit('error', e)
if (callback)
callback(e);
});
self._socket.on('connect', function() {
self._isClosed = false;
self._isOpened = true;
if (callback)
callback();
});
self._socket.on('data', function(data) {
var obj, cb, pos, xdr, buf;
if (!self._xdr) {
xdr = new XdrReader(data);
} else {
xdr = self._xdr;
delete(self._xdr);
buf = new Buffer(data.length + xdr.buffer.length);
xdr.buffer.copy(buf);
data.copy(buf, xdr.buffer.length);
xdr.buffer = buf;
}
while (xdr.pos < xdr.buffer.length) {
pos = xdr.pos;
try {
cb = self._queue[0];
obj = decodeResponse(xdr, cb, self.db);
} catch(err) {
buf = new Buffer(xdr.buffer.length - pos);
xdr.buffer.copy(buf, 0, pos);
xdr.buffer = buf;
xdr.pos = 0;
self._xdr = xdr;
return;
}
self._queue.shift();
self._pending.shift();
if (obj && obj.status) {
messages.lookupMessages(obj.status, function(message) {
obj.message = message;
doCallback(obj, cb);
});
} else
doCallback(obj, cb);
}
if (!self._detachAuto || self._pending.length !== 0)
return;
clearTimeout(self._detachTimeout);
self._detachTimeout = setTimeout(function() {
self.db.detach(self._detachCallback);
self._detachAuto = false;
}, 100);
});
}
exports.Connection.prototype.disconnect = function() {
this._socket.end();
};
function decodeResponse(data, callback, db){
do {
var r = data.readInt();
} while (r === op_dummy);
var item, op;
switch (r) {
case op_response:
var response;
if (callback)
response = callback.response || {};
else
response = {};
response.handle = data.readInt();
var oid = data.readQuad();
if (oid.low || oid.high)
response.oid = oid
var buf = data.readArray();
if (buf)
response.buffer = buf;
var num;
while (true) {
op = data.readInt();
switch (op){
case isc_arg_end:
return response;
case isc_arg_gds:
num = data.readInt();
if (!num)
break;
item = { gdscode: num };
if (response.status)
response.status.push(item);
else
response.status = [item];
break;
case isc_arg_string:
case isc_arg_interpreted:
case isc_arg_sql_state:
if (item.params) {
var str = data.readString(DEFAULT_ENCODING);
item.params.push(str);
} else
item.params = [data.readString(DEFAULT_ENCODING)];
break;
case isc_arg_number:
num = data.readInt();
if (item.params)
item.params.push(num);
else
item.params = [num];
if (item.gdscode === isc_sqlerr)
response.sqlcode = num;
break;
default:
throw new Error('Unexpected: ' + op);
}
}
break;
case op_fetch_response:
var status = data.readInt();
var count = data.readInt();
var statement = callback.statement;
var output = statement.output;
var custom = statement.custom || {};
var cols = null;
var rows = custom.asStream ? null : [];
var index = 0;
if (custom.asObject) {
cols = [];
for (var i = 0, length = output.length; i < length; i++)
cols.push(output[i].alias.toLowerCase());
}
while (count && (status !== 100)) {
var row = custom.asObject ? {} : new Array(output.length);
for (var i = 0, length = output.length; i < length; i++) {
item = output[i];
var value = item.decode(data);
if (item.type === 580 && value !== null)
value = value.low_;
if (custom.asObject) {
if (item.type === SQL_BLOB)
value = fetch_blob_async(statement, value, cols[i]);
row[cols[i]] = value;
}
else {
if (item.type === SQL_BLOB)
value = fetch_blob_async(statement, value, i);
row[i] = value;
}
}
statement.connection.db.emit('row', row, index, custom.asObject);
op = data.readInt(); // ??
status = data.readInt();
count = data.readInt();
if (!custom.asStream)
rows.push(row);
if (custom.on)
custom.on(row, index);
index++;
}
statement.connection.db.emit('result', rows);
return { data: rows, fetched: Boolean(status === 100) };
case op_accept:
if (data.readInt() !== PROTOCOL_VERSION10 || data.readInt() !== ARCHITECTURE_GENERIC || data.readInt() !== ptype_batch_send)
throw new Error('Invalid connect result');
return {};
default:
throw new Error('Unexpected:' + r);
}
}
Connection.prototype._queueEvent = function(callback){
var self = this;
if (self._isClosed) {
if (callback)
callback(new Error('Connection is closed.'));
return;
}
self._queue.push(callback);
self._socket.write(self._msg.getData());
};
Connection.prototype.connect = function (database, callback) {
var msg = this._msg;
var blr = this._blr;
msg.pos = 0;
blr.pos = 0;
msg.addInt(op_connect);
msg.addInt(op_attach);
msg.addInt(CONNECT_VERSION2);
msg.addInt(ARCHITECTURE_GENERIC);
msg.addString(database || '', DEFAULT_ENCODING);
msg.addInt(1); // Protocol version understood count.
blr.addString(1, process.env['USER'] || process.env['USERNAME'] || 'Unknown', DEFAULT_ENCODING);
var hostname = os.hostname();
blr.addString(4, hostname, DEFAULT_ENCODING);
blr.addBytes([6, 0]);
msg.addBlr(this._blr);
msg.addInt(PROTOCOL_VERSION10);
msg.addInt(ARCHITECTURE_GENERIC);
msg.addInt(2); // Min type
msg.addInt(3); // Max type
msg.addInt(2); // Preference weight
this._queueEvent(callback);
};
Connection.prototype.attach = function (options, callback, db) {
var database = options.database || options.filename;
var user = options.user || DEFAULT_USER;
var password = options.password || DEFAULT_PASSWORD;
var role = options.role;
var self = this;
var msg = this._msg;
var blr = this._blr;
msg.pos = 0;
blr.pos = 0;
blr.addByte(1);
blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING);
blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING);
blr.addString(isc_dpb_password, password, DEFAULT_ENCODING);
if (role)
blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING);
msg.addInt(op_attach);
msg.addInt(0); // Database Object ID
msg.addString(database, DEFAULT_ENCODING);
msg.addBlr(this._blr);
var self = this;
function cb(err, ret) {
if (err) {
doError(err, callback);
return;
}
self.dbhandle = ret.handle;
if (callback)
callback(undefined, ret);
}
// For reconnect
if (db) {
db.connection = this;
cb.response = db;
} else {
cb.response = new Database(this);
cb.response.on('error', noop);
}
this._queueEvent(cb);
};
Connection.prototype.detach = function (callback, force) {
var self = this;
if (self._isClosed)
return;
if (self.options.isPool && !force) {
self._isUsed = false;
// self._queue = [];
// self._pending = [];
self.db.emit('detach', true);
return;
}
self._isUsed = false;
self._isDetach = true;
var msg = self._msg;
msg.pos = 0;
msg.addInt(op_detach);
msg.addInt(0); // Database Object ID
self._queueEvent(function(err, ret) {
delete(self.dbhandle);
if (callback)
callback(err, ret);
});
};
Connection.prototype.createDatabase = function (options, callback) {
var database = options.database || options.filename;
var user = options.user || DEFAULT_USER;
var password = options.password || DEFAULT_PASSWORD;
var pageSize = options.pageSize || DEFAULT_PAGE_SIZE;
var role = options.role;
var blr = this._blr;
blr.pos = 0;
blr.addByte(1);
blr.addString(isc_dpb_set_db_charset, 'UTF8', DEFAULT_ENCODING);
blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING);
blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING);
blr.addString(isc_dpb_password, password, DEFAULT_ENCODING);
if (role)
blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING);
blr.addNumeric(isc_dpb_sql_dialect, 3);
blr.addNumeric(isc_dpb_force_write, 1);
blr.addNumeric(isc_dpb_overwrite, 1);
blr.addNumeric(isc_dpb_page_size, pageSize);
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_create); // op_create
msg.addInt(0); // Database Object ID
msg.addString(database, DEFAULT_ENCODING);
msg.addBlr(blr);
var self = this;
function cb(err, ret) {
if (ret)
self.dbhandle = ret.handle;
setImmediate(function() {
if (self.db)
self.db.emit('attach', ret);
});
if (callback)
callback(err, ret);
}
cb.response = new Database(this);
this._queueEvent(cb);
};
Connection.prototype.throwClosed = function(callback) {
var err = new Error('Connection is closed.');
this.db.emit('error', err);
if (callback)
callback(err);
return this;
};
Connection.prototype.startTransaction = function(isolation, callback) {
if (typeof(isolation) === 'function') {
var tmp = isolation;
isolation = callback;
callback = tmp;
}
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('startTransaction');
var blr = this._blr;
var msg = this._msg;
blr.pos = 0;
msg.pos = 0;
if (isolation instanceof Function) {
callback = isolation;
isolation = null;
}
blr.addBytes(isolation || ISOLATION_REPEATABLE_READ);
msg.addInt(op_transaction);
msg.addInt(this.dbhandle);
msg.addBlr(blr);
callback.response = new Transaction(this);
this.db.emit('transaction', isolation);
this._queueEvent(callback);
};
Connection.prototype.commit = function (transaction, callback) {
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('commit');
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_commit);
msg.addInt(transaction.handle);
this.db.emit('commit');
this._queueEvent(callback);
};
Connection.prototype.rollback = function (transaction, callback) {
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('rollback');
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_rollback);
msg.addInt(transaction.handle);
this.db.emit('rollback');
this._queueEvent(callback);
};
Connection.prototype.commitRetaining = function (transaction, callback) {
if (this._isClosed)
throw new Error('Connection is closed.');
// for auto detach
this._pending.push('commitRetaining');
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_commit_retaining);
msg.addInt(transaction.handle);
this._queueEvent(callback);
};
Connection.prototype.rollbackRetaining = function (transaction, callback) {
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('rollbackRetaining');
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_rollback_retaining);
msg.addInt(transaction.handle);
this._queueEvent(callback);
};
Connection.prototype.allocateStatement = function (callback) {
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('allocateStatement');
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_allocate_statement);
msg.addInt(this.dbhandle);
callback.response = new Statement(this);
this._queueEvent(callback);
};
Connection.prototype.dropStatement = function (statement, callback) {
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('dropStatement');
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_free_statement);
msg.addInt(statement.handle);
msg.addInt(DSQL_drop);
this._queueEvent(callback);
};
Connection.prototype.closeStatement = function (statement, callback) {
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('closeStatement');
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_free_statement);
msg.addInt(statement.handle);
msg.addInt(DSQL_close);
this._queueEvent(callback);
};
function describe(ret, statement){
var br = new BlrReader(ret.buffer);
var parameters = null;
var type, param;
while (br.pos < br.buffer.length) {
switch (br.readByteCode()) {
case isc_info_sql_stmt_type:
statement.type = br.readInt();
break;
case isc_info_sql_get_plan:
statement.plan = br.readString(DEFAULT_ENCODING);
break;
case isc_info_sql_select:
statement.output = parameters = [];
break;
case isc_info_sql_bind:
statement.input = parameters = [];
break;
case isc_info_sql_num_variables:
br.readInt(); // eat int
break;
case isc_info_sql_describe_vars:
if (!parameters) {return}
br.readInt(); // eat int ?
var finishDescribe = false;
param = null;
while (!finishDescribe){
switch (br.readByteCode()) {
case isc_info_sql_describe_end:
break;
case isc_info_sql_sqlda_seq:
var num = br.readInt();
break;
case isc_info_sql_type:
type = br.readInt();
switch (type&~1) {
case SQL_VARYING: param = new SQLVarString(); break;
case SQL_NULL: param = new SQLVarNull(); break;
case SQL_TEXT: param = new SQLVarText(); break;
case SQL_DOUBLE: param = new SQLVarDouble(); break;
case SQL_FLOAT:
case SQL_D_FLOAT: param = new SQLVarFloat(); break;
case SQL_TYPE_DATE: param = new SQLVarDate(); break;
case SQL_TYPE_TIME: param = new SQLVarTime(); break;
case SQL_TIMESTAMP: param = new SQLVarTimeStamp(); break;
case SQL_BLOB: param = new SQLVarBlob(); break;
case SQL_ARRAY: param = new SQLVarArray(); break;
case SQL_QUAD: param = new SQLVarQuad(); break;
case SQL_LONG: param = new SQLVarInt(); break;
case SQL_SHORT: param = new SQLVarShort(); break;
case SQL_INT64: param = new SQLVarInt64(); break;
case SQL_BOOLEAN: param = new SQLVarBoolean(); break;
default:
throw new Error('Unexpected');
}
parameters[num-1] = param;
param.type = type;
param.nullable = Boolean(param.type & 1);
param.type &= ~1;
break;
case isc_info_sql_sub_type:
param.subType = br.readInt();
break;
case isc_info_sql_scale:
param.scale = br.readInt();
break;
case isc_info_sql_length:
param.length = br.readInt();
break;
case isc_info_sql_null_ind:
param.nullable = Boolean(br.readInt());
break;
case isc_info_sql_field:
param.field = br.readString(DEFAULT_ENCODING);
break;
case isc_info_sql_relation:
param.relation = br.readString(DEFAULT_ENCODING);
break;
case isc_info_sql_owner:
param.owner = br.readString(DEFAULT_ENCODING);
break;
case isc_info_sql_alias:
param.alias = br.readString(DEFAULT_ENCODING);
break;
case isc_info_sql_relation_alias:
param.relationAlias = br.readString(DEFAULT_ENCODING);
break;
case isc_info_truncated:
throw new Error('Truncated');
default:
finishDescribe = true;
br.pos--;
}
}
}
}
}
Connection.prototype.prepareStatement = function (transaction, statement, query, plan, callback) {
if (this._isClosed)
return this.throwClosed(callback);
var msg = this._msg;
var blr = this._blr;
msg.pos = 0;
blr.pos = 0;
if (plan instanceof Function) {
callback = plan;
plan = false;
}
blr.addBytes(DESCRIBE);
if (plan)
blr.addByte(isc_info_sql_get_plan);
msg.addInt(op_prepare_statement);
msg.addInt(transaction.handle);
msg.addInt(statement.handle);
msg.addInt(3); // dialect = 3
msg.addString(query, DEFAULT_ENCODING);
msg.addBlr(blr);
msg.addInt(65535); // buffer_length
var self = this;
this._queueEvent(function(err, ret) {
if (!err) {
describe(ret, statement);
statement.query = query;
self.db.emit('query', query);
ret = statement;
}
if (callback)
callback(err, ret);
});
};
function CalcBlr(blr, xsqlda) {
blr.addBytes([blr_version5, blr_begin, blr_message, 0]); // + message number
blr.addWord(xsqlda.length * 2);
for (var i = 0, length = xsqlda.length; i < length; i++) {
xsqlda[i].calcBlr(blr);
blr.addByte(blr_short);
blr.addByte(0);
}
blr.addByte(blr_end);
blr.addByte(blr_eoc);
}
Connection.prototype.executeStatement = function(transaction, statement, params, callback, custom) {
if (this._isClosed)
return this.throwClosed(callback);
// for auto detach
this._pending.push('executeStatement');
if (params instanceof Function) {
callback = params;
params = undefined;
}
var self = this;
function PrepareParams(params, input, callback) {
var value, meta;
var ret = new Array(params.length);
var wait = params.length;
function done() {
wait--;
if (wait === 0)
callback(ret);
}
function putBlobData(index, value, callback) {
self.createBlob2(transaction, function(err, blob) {
var b;
var isStream = value.Readable;
if (Buffer.isBuffer(value))
b = value;
else if (typeof(value) === 'string')
b = new Buffer(value, DEFAULT_ENCODING)
else if (!isStream)
b = new Buffer(JSON.stringify(value), DEFAULT_ENCODING)
if (Buffer.isBuffer(value)) {
bufferReader(b, 1024, function(b, next) {
self.batchSegments(blob, b, next);
}, function() {
ret[index] = new SQLParamQuad(blob.oid);
self.closeBlob(blob, callback);
});
return;
}
var isReading = false;
var isEnd = false;
value.on('data', function(chunk) {
value.pause();
isReading = true;
bufferReader(chunk, 1024, function(b, next) {
self.batchSegments(blob, b, next);
}, function() {
isReading = false;
if (isEnd) {
ret[index] = new SQLParamQuad(blob.oid);
self.closeBlob(blob, callback);
} else
value.resume();
});
});
value.on('end', function() {
isEnd = true;
if (isReading)
return;
ret[index] = new SQLParamQuad(blob.oid);
self.closeBlob(blob, callback);
});
});
}
for (var i = 0, length = params.length; i < length; i++) {
value = params[i];
meta = input[i];
if (value === null) {
switch (meta.type) {
case SQL_VARYING:
case SQL_NULL:
case SQL_TEXT:
ret[i] = new SQLParamString(null);
break;
case SQL_DOUBLE:
case SQL_FLOAT:
case SQL_D_FLOAT:
ret[i] = new SQLParamDouble(null);
break;
case SQL_TYPE_DATE:
case SQL_TYPE_TIME:
case SQL_TIMESTAMP:
ret[i] = new SQLParamDate(null);
break;
case SQL_BLOB:
case SQL_ARRAY:
case SQL_QUAD:
ret[i] = new SQLParamQuad(null);
break;
case SQL_LONG:
case SQL_SHORT:
case SQL_INT64:
case SQL_BOOLEAN:
ret[i] = new SQLParamInt(null);
break;
default:
ret[i] = null;
}
done();
} else {
switch (meta.type) {
case SQL_BLOB:
putBlobData(i, value, done);
break;
case SQL_TIMESTAMP:
case SQL_TYPE_DATE:
case SQL_TYPE_TIME:
if (value instanceof Date)
ret[i] = new SQLParamDate(value);
else if (typeof(value) === 'string')
ret[i] = new SQLParamDate(value.parseDate());
else
ret[i] = new SQLParamDate(new Date(value));
done();
break;
default:
switch (typeof value) {
case 'number':
if (value % 1 === 0) {
if (value >= MIN_INT && value <= MAX_INT)
ret[i] = new SQLParamInt(value);
else
ret[i] = new SQLParamInt64(value);
} else
ret[i] = new SQLParamDouble(value);
break;
case 'string':
ret[i] = new SQLParamString(value);
break;
case 'boolean':
ret[i] = new SQLParamBool(value);
break;
default:
throw new Error('Unexpected parametter');
}
done();
}
}
}
}
var input = statement.input;
if (input.length) {
if (!(params instanceof Array)) {
if (params !== undefined)
params = [params];
else
params = [];
}
if (params === undefined || params.length !== input.length)
throw new Error('Expected parameters: ' + input.length);
PrepareParams(params, input, function(prms) {
var msg = self._msg;
var blr = self._blr;
msg.pos = 0;
blr.pos = 0;
CalcBlr(blr, prms);
msg.addInt(op_execute);
msg.addInt(statement.handle);
msg.addInt(transaction.handle);
msg.addBlr(blr);
msg.addInt(0); // message number
msg.addInt(1); // param count
for(var i = 0, length = prms.length; i < length; i++)
prms[i].encode(msg);
self._queueEvent(callback);
});
return;
}
var msg = this._msg;
var blr = this._blr;
msg.pos = 0;
blr.pos = 0;
msg.addInt(op_execute);
msg.addInt(statement.handle);
msg.addInt(transaction.handle);
msg.addBlr(blr); // empty
msg.addInt(0); // message number
msg.addInt(0); // param count
this._queueEvent(callback);
};
function fetch_blob_async(statement, id, name) {
if (!id)
return null;
return function(callback) {
// callback(err, buffer, name);
statement.connection.startTransaction(ISOLATION_READ_UNCOMMITTED, function(err, transaction) {
if (err) {
callback(err);
return;
}
statement.connection._pending.push('openBlob');
statement.connection.openBlob(id, transaction, function(err, blob) {
var e = new Events.EventEmitter();
if (err) {
callback(err, name, e);
return;
}
function read() {
statement.connection.getSegment(blob, function(err, ret) {
if (err) {
e.emit('error', err);
return;
}
var blr = new BlrReader(ret.buffer);
var data = blr.readSegment();
e.emit('data', data);
if (ret.handle !== 2) {
read();
return;
}
e.emit('end');
e = null;
statement.connection.closeBlob(blob);
});
}
callback(err, name, e);
read();
});
});
};
}
Connection.prototype.fetch = function(statement, transaction, count, callback) {
var msg = this._msg;
var blr = this._blr;
msg.pos = 0;
blr.pos = 0;
if (count instanceof Function) {
callback = count;
count = DEFAULT_FETCHSIZE;
}
msg.addInt(op_fetch);
msg.addInt(statement.handle);
CalcBlr(blr, statement.output);
msg.addBlr(blr);
msg.addInt(0); // message number
msg.addInt(count || DEFAULT_FETCHSIZE); // fetch count
if (!transaction) {
callback.statement = statement;
this._queueEvent(callback);
return;
}
callback.statement = statement;
this._queueEvent(callback);
};
Connection.prototype.fetchAll = function(statement, transaction, callback) {
var self = this;
var data;
var loop = function(err, ret) {
if (err) {
callback(err);
return;
}
if (!data) {
data = ret.data;
} else {
for (var i = 0, length = ret.data.length; i < length; i++)
data.push(ret.data[i]);
}
if (ret.fetched)
callback(undefined, data);
else
self.fetch(statement, transaction, DEFAULT_FETCHSIZE, loop);
}
this.fetch(statement, transaction, DEFAULT_FETCHSIZE, loop);
};
Connection.prototype.openBlob = function(blob, transaction, callback) {
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_open_blob);
msg.addInt(transaction.handle);
msg.addQuad(blob);
this._queueEvent(callback);
};
Connection.prototype.closeBlob = function(blob, callback) {
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_close_blob);
msg.addInt(blob.handle);
this._queueEvent(callback);
};
Connection.prototype.getSegment = function(blob, callback) {
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_get_segment);
msg.addInt(blob.handle);
msg.addInt(1024); // buffer length
msg.addInt(0); // ???
this._queueEvent(callback);
};
Connection.prototype.createBlob2 = function (transaction, callback) {
var msg = this._msg;
msg.pos = 0;
msg.addInt(op_create_blob2);
msg.addInt(0);
msg.addInt(transaction.handle);
msg.addInt(0);
msg.addInt(0);
this._queueEvent(callback);
};
Connection.prototype.batchSegments = function(blob, buffer, callback){
var msg = this._msg;
var blr = this._blr;
msg.pos = 0;
blr.pos = 0;
msg.addInt(op_batch_segments);
msg.addInt(blob.handle);
msg.addInt(buffer.length + 2);
blr.addBuffer(buffer);
msg.addBlr(blr);
this._queueEvent(callback);
};
function bufferReader(buffer, max, writer, cb, beg, end) {
if (!beg)
beg = 0;
if (!end)
end = max;
if (end >= buffer.length)
end = undefined;
var b = buffer.slice(beg, end);
writer(b, function() {
if (end === undefined) {
cb();
return;
}
bufferReader(buffer, max, writer, cb, beg + max, end + max);
});
}