2716 lines
74 KiB
JavaScript
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);
|
|
});
|
|
} |