From 20b778dd87d628597f398ebd3fd99fc5f5b8ef36 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 14:30:25 +0000 Subject: [PATCH 01/34] Add dependency on ignite (state machine). --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a334026b1..784d3b8b8 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "coffee-script": "1.1.3", + "ignite": "0.1.5", "sprintf": "0.1.1" }, "devDependencies": { From b5842623651144979286bbeeafa6ae66a353a8ad Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 16:20:13 +0000 Subject: [PATCH 02/34] Start of state machine GF for a connection. Enough to allow a simple diagram to be generated. --- lib/connection/connection-statemachine.coffee | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/connection/connection-statemachine.coffee diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee new file mode 100644 index 000000000..16c984076 --- /dev/null +++ b/lib/connection/connection-statemachine.coffee @@ -0,0 +1,58 @@ +connectionStateMachine = (fire) -> + @name = 'Connection - State Machine' + + @startState = 'Initial' + + @states = + Initial: + entry: -> + console.log('init') + 'SentPrelogin' + + SentPrelogin: + entry: -> + console.log('sent pl') + null + actions: + '.done': 'SentLogin7WithStandardLogin' + '.err': 'Final' + + ### + SentTlsNegotiation: + entry: -> + console.log('sent tls neg') + ### + + SentLogin7WithStandardLogin: + entry: -> + console.log('sent l7 with standard login') + null + + ### + SentLogin7WithSpNego: + entry: -> + console.log('sent l7 with spnego') + + LoggedIn: + entry: -> + console.log('logged in') + + SentClientRequest: + entry: -> + console.log('sent client request') + + SentAttention: + entry: -> + console.log('sent attention') + + RoutingComplete: + entry: -> + console.log('routing complete') + ### + + Final: + entry: -> + console.log('done') + '@exit' + +module.exports = connectionStateMachine; From 0c8f8c0e6e29f42289ceb5cf60988bf8273070f2 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 16:38:25 +0000 Subject: [PATCH 03/34] A script to generate the connection statemachine diagram. --- .gitignore | 1 + scripts/statemachine-generate-diagram | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100755 scripts/statemachine-generate-diagram diff --git a/.gitignore b/.gitignore index f7f80710e..4df51ef18 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ todo npm-debug.log coffee/* README.html +generated test/unit/perf-buffer.coffee diff --git a/scripts/statemachine-generate-diagram b/scripts/statemachine-generate-diagram new file mode 100755 index 000000000..eb4b1b298 --- /dev/null +++ b/scripts/statemachine-generate-diagram @@ -0,0 +1,15 @@ +#!/bin/sh + +COFFEE=node_modules/.bin/coffee +IGNITE=node_modules/.bin/ignite + +GENERATED_DIR=generated +COFFEE_OUTPUT_DIR=coffee + +DIAGRAM_FILE=$GENERATED_DIR/connection-statemachine.png +STATEMACHINE_FILE=$COFFEE_OUTPUT_DIR/lib/connection/connection-statemachine.js + +mkdir -p $GENERATED_DIR + +$COFFEE -b -o $COFFEE_OUTPUT_DIR lib/ +$IGNITE -m draw -T png -o $DIAGRAM_FILE $STATEMACHINE_FILE \ No newline at end of file From 3d26812ac334bd786af47f68af7c3a1a0250205d Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 16:49:14 +0000 Subject: [PATCH 04/34] Correct output directory for coffee generated js files. --- scripts/statemachine-generate-diagram | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/statemachine-generate-diagram b/scripts/statemachine-generate-diagram index eb4b1b298..928fbfcdf 100755 --- a/scripts/statemachine-generate-diagram +++ b/scripts/statemachine-generate-diagram @@ -4,10 +4,10 @@ COFFEE=node_modules/.bin/coffee IGNITE=node_modules/.bin/ignite GENERATED_DIR=generated -COFFEE_OUTPUT_DIR=coffee +COFFEE_OUTPUT_DIR=coffee/lib DIAGRAM_FILE=$GENERATED_DIR/connection-statemachine.png -STATEMACHINE_FILE=$COFFEE_OUTPUT_DIR/lib/connection/connection-statemachine.js +STATEMACHINE_FILE=$COFFEE_OUTPUT_DIR/connection/connection-statemachine.js mkdir -p $GENERATED_DIR From 3ac66d968d194bb415c5c276e937c2142190f8cc Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 20:31:58 +0000 Subject: [PATCH 05/34] Connect timeout implemented (after a fashion). --- lib/connection/connection-statemachine.coffee | 61 ++++++++++++++++--- .../connection-statemachine-test.coffee | 25 ++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 test/integration/connection-statemachine-test.coffee diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 16c984076..506af6c1b 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -1,18 +1,51 @@ -connectionStateMachine = (fire) -> +Socket = require('net').Socket + +KEEP_ALIVE_INITIAL_DELAY = 30 * 1000 +CONNECT_TIMEOUT_DEFAULT = 15 * 1000 +CLIENT_REQUEST_TIMEOUT_DEFAULT = 15 * 1000 +CANCEL_TIMEOUT_DEFAULT = 5 * 1000 + +connectionStateMachine = (fire, config) -> + # Used in diagram. @name = 'Connection - State Machine' - @startState = 'Initial' + connection = undefined + connectTimer = undefined + + @defaults = + actions: + 'connectTimeout': -> + console.log 'connect timeout' + if connection + connection.destroy() + + 'Final' + + @startState = 'Connecting' @states = - Initial: + Connecting: entry: -> - console.log('init') - 'SentPrelogin' + defaultConfig() + connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); + connection = connect() + fire.$regEmitter('connection', connection, true); + + null + + actions: + 'connection.connect': -> + 'SentPrelogin' + + 'connection.error': '@error' SentPrelogin: entry: -> - console.log('sent pl') + if connectTimer + clearTimeout(connectTimer) + null + actions: '.done': 'SentLogin7WithStandardLogin' '.err': 'Final' @@ -55,4 +88,18 @@ connectionStateMachine = (fire) -> console.log('done') '@exit' -module.exports = connectionStateMachine; + defaultConfig = -> + config.options ||= {} + config.options.port ||= 1433 + config.options.connectTimeout ||= CONNECT_TIMEOUT_DEFAULT + config.options.requestTimeout ||= CLIENT_REQUEST_TIMEOUT_DEFAULT + config.options.cancelTimeout ||= CANCEL_TIMEOUT_DEFAULT + + connect = -> + connection = new Socket({}) + connection.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) + connection.connect(config.options.port, config.server) + + connection + +module.exports = connectionStateMachine diff --git a/test/integration/connection-statemachine-test.coffee b/test/integration/connection-statemachine-test.coffee new file mode 100644 index 000000000..896493cdc --- /dev/null +++ b/test/integration/connection-statemachine-test.coffee @@ -0,0 +1,25 @@ +connectionGF = require('../../lib/connection/connection-statemachine') +ignite = require('ignite') +fs = require('fs') + +getConfig = -> + config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) + config.options.debug = + data: true + payload: true + token: true + + config + +exports.connect = (test) -> + imports = {} + options = {} + factory = new ignite.Factory(connectionGF, imports, options) + + config = getConfig() + config.options.port = -1 + config.options.connectTimeout = 200 + + factory.spawn(config) + + #test.done() From 77c3a222d9adec2c3a074954fd85a6d22e5ee327 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 21:14:15 +0000 Subject: [PATCH 06/34] Tests for connecting and failing to connect (due to bad port) working. (No login exchange yet, just socket connection.) --- lib/connection/connection-statemachine.coffee | 31 ++++++++------- lib/connection/connection.coffee | 16 ++++++++ test/integration/connection-new-test.coffee | 38 +++++++++++++++++++ .../connection-statemachine-test.coffee | 15 +++++++- 4 files changed, 82 insertions(+), 18 deletions(-) create mode 100644 lib/connection/connection.coffee create mode 100644 test/integration/connection-new-test.coffee diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 506af6c1b..186f81158 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -5,19 +5,17 @@ CONNECT_TIMEOUT_DEFAULT = 15 * 1000 CLIENT_REQUEST_TIMEOUT_DEFAULT = 15 * 1000 CANCEL_TIMEOUT_DEFAULT = 5 * 1000 -connectionStateMachine = (fire, config) -> +connectionStateMachine = (fire, client, config) -> # Used in diagram. @name = 'Connection - State Machine' - connection = undefined + socket = undefined connectTimer = undefined @defaults = actions: 'connectTimeout': -> - console.log 'connect timeout' - if connection - connection.destroy() + client.emit('connection', "timeout : failed to connect in #{config.options.connectTimeout}ms") 'Final' @@ -27,24 +25,25 @@ connectionStateMachine = (fire, config) -> Connecting: entry: -> defaultConfig() + connect() connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); - connection = connect() - fire.$regEmitter('connection', connection, true); + fire.$regEmitter('socket', socket, true); null actions: - 'connection.connect': -> + 'socket.connect': -> 'SentPrelogin' - 'connection.error': '@error' + 'socket.error': '@error' SentPrelogin: entry: -> if connectTimer clearTimeout(connectTimer) + client.emit('connection') - null + 'Final' actions: '.done': 'SentLogin7WithStandardLogin' @@ -85,7 +84,9 @@ connectionStateMachine = (fire, config) -> Final: entry: -> - console.log('done') + if socket + socket.destroy() + '@exit' defaultConfig = -> @@ -96,10 +97,8 @@ connectionStateMachine = (fire, config) -> config.options.cancelTimeout ||= CANCEL_TIMEOUT_DEFAULT connect = -> - connection = new Socket({}) - connection.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) - connection.connect(config.options.port, config.server) - - connection + socket = new Socket({}) + socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) + socket.connect(config.options.port, config.server) module.exports = connectionStateMachine diff --git a/lib/connection/connection.coffee b/lib/connection/connection.coffee new file mode 100644 index 000000000..7db82dfb3 --- /dev/null +++ b/lib/connection/connection.coffee @@ -0,0 +1,16 @@ +EventEmitter = require('events').EventEmitter +ignite = require('ignite') +connectionGF = require('../../lib/connection/connection-statemachine') + +class Connection extends EventEmitter + constructor: (@config) -> + imports = {} + options = {} + + if @config?.statemachine?.logLevel + options.logLevel = @config.statemachine.logLevel + + factory = new ignite.Factory(connectionGF, imports, options) + factory.spawn(@, @config) + +module.exports = Connection diff --git a/test/integration/connection-new-test.coffee b/test/integration/connection-new-test.coffee new file mode 100644 index 000000000..fa44b6f23 --- /dev/null +++ b/test/integration/connection-new-test.coffee @@ -0,0 +1,38 @@ +Connection = require('../../lib/connection/connection') +fs = require('fs') + +getConfig = -> + config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) + + config.options.debug = + data: true + payload: true + token: true + + config.statemachine = + logLevel: 5 + + config + +exports.badPort = (test) -> + config = getConfig() + + config.options.port = -1 + config.options.connectTimeout = 200 + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(err) + test.done() + ) + +exports.connect = (test) -> + config = getConfig() + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(!err) + test.done() + ) diff --git a/test/integration/connection-statemachine-test.coffee b/test/integration/connection-statemachine-test.coffee index 896493cdc..41467fd7f 100644 --- a/test/integration/connection-statemachine-test.coffee +++ b/test/integration/connection-statemachine-test.coffee @@ -11,15 +11,26 @@ getConfig = -> config -exports.connect = (test) -> +exports.badPort = (test) -> imports = {} options = {} factory = new ignite.Factory(connectionGF, imports, options) - config = getConfig() + config.options.port = -1 config.options.connectTimeout = 200 factory.spawn(config) #test.done() + +exports.connect = (test) -> + imports = {} + options = + logLevel: 8 + factory = new ignite.Factory(connectionGF, imports, options) + config = getConfig() + + factory.spawn(config) + + #test.done() From 7d298de02d3d4eef3e3b1fd1f08ba8c394435a1b Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 21:15:45 +0000 Subject: [PATCH 07/34] Remove unused tests. --- .../connection-statemachine-test.coffee | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 test/integration/connection-statemachine-test.coffee diff --git a/test/integration/connection-statemachine-test.coffee b/test/integration/connection-statemachine-test.coffee deleted file mode 100644 index 41467fd7f..000000000 --- a/test/integration/connection-statemachine-test.coffee +++ /dev/null @@ -1,36 +0,0 @@ -connectionGF = require('../../lib/connection/connection-statemachine') -ignite = require('ignite') -fs = require('fs') - -getConfig = -> - config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) - config.options.debug = - data: true - payload: true - token: true - - config - -exports.badPort = (test) -> - imports = {} - options = {} - factory = new ignite.Factory(connectionGF, imports, options) - config = getConfig() - - config.options.port = -1 - config.options.connectTimeout = 200 - - factory.spawn(config) - - #test.done() - -exports.connect = (test) -> - imports = {} - options = - logLevel: 8 - factory = new ignite.Factory(connectionGF, imports, options) - config = getConfig() - - factory.spawn(config) - - #test.done() From 9a9de3a130f7bc7981f7fe1d73b233855df9727d Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 22:02:57 +0000 Subject: [PATCH 08/34] Use a connection factory to create connections, which only creates a statemachine factory once. --- lib/connection/connection-factory.coffee | 20 +++++++++++++++++++ lib/connection/connection.coffee | 16 --------------- lib/tedious.js | 4 +++- ....coffee => connection-factory-test.coffee} | 10 +++++++--- 4 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 lib/connection/connection-factory.coffee delete mode 100644 lib/connection/connection.coffee rename test/integration/{connection-new-test.coffee => connection-factory-test.coffee} (67%) diff --git a/lib/connection/connection-factory.coffee b/lib/connection/connection-factory.coffee new file mode 100644 index 000000000..8698a7f0a --- /dev/null +++ b/lib/connection/connection-factory.coffee @@ -0,0 +1,20 @@ +EventEmitter = require('events').EventEmitter +ignite = require('ignite') +connectionGF = require('../../lib/connection/connection-statemachine') + +class ConnectionFactory + constructor: () -> + imports = {} + options = + logLevel: require('../tedious').statemachineLogLevel + + @factory = new ignite.Factory(connectionGF, imports, options) + + createConnection: (config) -> + new Connection(@factory, config) + +class Connection extends EventEmitter + constructor: (factory, config) -> + factory.spawn(@, config) + +module.exports = ConnectionFactory diff --git a/lib/connection/connection.coffee b/lib/connection/connection.coffee deleted file mode 100644 index 7db82dfb3..000000000 --- a/lib/connection/connection.coffee +++ /dev/null @@ -1,16 +0,0 @@ -EventEmitter = require('events').EventEmitter -ignite = require('ignite') -connectionGF = require('../../lib/connection/connection-statemachine') - -class Connection extends EventEmitter - constructor: (@config) -> - imports = {} - options = {} - - if @config?.statemachine?.logLevel - options.logLevel = @config.statemachine.logLevel - - factory = new ignite.Factory(connectionGF, imports, options) - factory.spawn(@, @config) - -module.exports = Connection diff --git a/lib/tedious.js b/lib/tedious.js index ee12fde5f..f8320c697 100644 --- a/lib/tedious.js +++ b/lib/tedious.js @@ -1,5 +1,7 @@ require('coffee-script') -exports.Connection = require('./connection') +exports.statemachineLogLevel = 0 + +exports.ConnectionFactory = require('./connection/connection-factory') exports.Request = require('./request') exports.library = require('./library') diff --git a/test/integration/connection-new-test.coffee b/test/integration/connection-factory-test.coffee similarity index 67% rename from test/integration/connection-new-test.coffee rename to test/integration/connection-factory-test.coffee index fa44b6f23..c71c88c23 100644 --- a/test/integration/connection-new-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -1,6 +1,10 @@ -Connection = require('../../lib/connection/connection') +ConnectionFactory = require('../../lib/connection/connection-factory') fs = require('fs') +#require('../../lib/tedious').statemachineLogLevel = 5 + +connectionFactory = new ConnectionFactory() + getConfig = -> config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) @@ -20,7 +24,7 @@ exports.badPort = (test) -> config.options.port = -1 config.options.connectTimeout = 200 - connection = new Connection(config) + connection = connectionFactory.createConnection(config) connection.on('connection', (err) -> test.ok(err) @@ -30,7 +34,7 @@ exports.badPort = (test) -> exports.connect = (test) -> config = getConfig() - connection = new Connection(config) + connection = connectionFactory.createConnection(config) connection.on('connection', (err) -> test.ok(!err) From 55f5fc5c2c9c8b04cf8efa7a1a9ef7813bed59e0 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 22:29:10 +0000 Subject: [PATCH 09/34] Add test for connecting to a server with a bad hostname. --- lib/connection/connection-statemachine.coffee | 16 +++++++++++++++- test/integration/connection-factory-test.coffee | 15 ++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 186f81158..2ec2c27b9 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -26,7 +26,11 @@ connectionStateMachine = (fire, client, config) -> entry: -> defaultConfig() connect() + socket.on('error', (error) -> + # Need this handler, or else the error action is not fired. Weird. + ) connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); + fire.$regEmitter('socket', socket, true); null @@ -35,7 +39,12 @@ connectionStateMachine = (fire, client, config) -> 'socket.connect': -> 'SentPrelogin' - 'socket.error': '@error' + 'socket.error': -> + client.emit('connection', "failed to connect") + + 'Final' + + #'socket.error': '@error' SentPrelogin: entry: -> @@ -84,9 +93,14 @@ connectionStateMachine = (fire, client, config) -> Final: entry: -> + if connectTimer + clearTimeout(connectTimer) + if socket socket.destroy() + client.emit('end') + '@exit' defaultConfig = -> diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee index c71c88c23..ede0d2e78 100644 --- a/test/integration/connection-factory-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -18,17 +18,26 @@ getConfig = -> config -exports.badPort = (test) -> +exports.badServer = (test) -> config = getConfig() + config.server = 'bad-server' + + connection = connectionFactory.createConnection(config) + connection.on('connection', (err) -> + test.ok(err) + test.done() + ) +exports.badPort = (test) -> + config = getConfig() config.options.port = -1 config.options.connectTimeout = 200 connection = connectionFactory.createConnection(config) connection.on('connection', (err) -> - test.ok(err) - test.done() + test.ok(err) + test.done() ) exports.connect = (test) -> From 8c872adab533cdec520e401228d4c88e6b168849 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 22:30:23 +0000 Subject: [PATCH 10/34] Correct indentation error. --- lib/connection/connection-statemachine.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 2ec2c27b9..5a951121e 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -99,7 +99,7 @@ connectionStateMachine = (fire, client, config) -> if socket socket.destroy() - client.emit('end') + client.emit('end') '@exit' From 392ab56b7c81a04257001f017d32c2bf719f84c1 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 22:57:52 +0000 Subject: [PATCH 11/34] Move common connection timeout action handling in to a common function. --- lib/connection/connection-statemachine.coffee | 31 ++++++++++++------- scripts/statemachine-generate-diagram | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 5a951121e..3b9fc4064 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -12,13 +12,6 @@ connectionStateMachine = (fire, client, config) -> socket = undefined connectTimer = undefined - @defaults = - actions: - 'connectTimeout': -> - client.emit('connection', "timeout : failed to connect in #{config.options.connectTimeout}ms") - - 'Final' - @startState = 'Connecting' @states = @@ -41,15 +34,16 @@ connectionStateMachine = (fire, client, config) -> 'socket.error': -> client.emit('connection', "failed to connect") - 'Final' + 'connectTimeout': -> + connectTimeout() + #'socket.error': '@error' SentPrelogin: entry: -> - if connectTimer - clearTimeout(connectTimer) + clearConnectTimer() client.emit('connection') 'Final' @@ -57,6 +51,8 @@ connectionStateMachine = (fire, client, config) -> actions: '.done': 'SentLogin7WithStandardLogin' '.err': 'Final' + 'connectTimeout': -> + connectTimeout() ### SentTlsNegotiation: @@ -69,6 +65,10 @@ connectionStateMachine = (fire, client, config) -> console.log('sent l7 with standard login') null + actions: + 'connectTimeout': -> + connectTimeout() + ### SentLogin7WithSpNego: entry: -> @@ -93,8 +93,7 @@ connectionStateMachine = (fire, client, config) -> Final: entry: -> - if connectTimer - clearTimeout(connectTimer) + clearConnectTimer() if socket socket.destroy() @@ -115,4 +114,12 @@ connectionStateMachine = (fire, client, config) -> socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) socket.connect(config.options.port, config.server) + connectTimeout = -> + client.emit('connection', "timeout : failed to connect in #{config.options.connectTimeout}ms") + 'Final' + + clearConnectTimer = -> + if connectTimer + clearTimeout(connectTimer) + module.exports = connectionStateMachine diff --git a/scripts/statemachine-generate-diagram b/scripts/statemachine-generate-diagram index 928fbfcdf..102768bae 100755 --- a/scripts/statemachine-generate-diagram +++ b/scripts/statemachine-generate-diagram @@ -12,4 +12,4 @@ STATEMACHINE_FILE=$COFFEE_OUTPUT_DIR/connection/connection-statemachine.js mkdir -p $GENERATED_DIR $COFFEE -b -o $COFFEE_OUTPUT_DIR lib/ -$IGNITE -m draw -T png -o $DIAGRAM_FILE $STATEMACHINE_FILE \ No newline at end of file +$IGNITE -m draw -T png --level 1 -o $DIAGRAM_FILE $STATEMACHINE_FILE \ No newline at end of file From cb8203221cbbc50af6c4d3ef1d2d155270485672 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 2 Jan 2012 23:01:22 +0000 Subject: [PATCH 12/34] Move socket listener for 'error' events out of the main state handling code. --- lib/connection/connection-statemachine.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 3b9fc4064..5f72f8fec 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -19,9 +19,6 @@ connectionStateMachine = (fire, client, config) -> entry: -> defaultConfig() connect() - socket.on('error', (error) -> - # Need this handler, or else the error action is not fired. Weird. - ) connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); fire.$regEmitter('socket', socket, true); @@ -114,6 +111,10 @@ connectionStateMachine = (fire, client, config) -> socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) socket.connect(config.options.port, config.server) + socket.on('error', (error) -> + # Need this listener, or else the error actions are not fired. Weird. + ) + connectTimeout = -> client.emit('connection', "timeout : failed to connect in #{config.options.connectTimeout}ms") 'Final' From e3d4af15f87c267e4d65aea2d1097be469bd2353 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 10:29:56 +0000 Subject: [PATCH 13/34] MessageIo emits a 'message' event when a complete message has been received. --- lib/message-io.coffee | 9 +++-- test/unit/message-io-test.coffee | 56 ++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/lib/message-io.coffee b/lib/message-io.coffee index eaa7c5fd4..946a1837b 100644 --- a/lib/message-io.coffee +++ b/lib/message-io.coffee @@ -4,12 +4,8 @@ isPacketComplete = require('./packet').isPacketComplete packetLength = require('./packet').packetLength Packet = require('./packet').Packet -DEFAULT_PACKET_SIZE = 4 * 1024 - class MessageIO extends EventEmitter - constructor: (@connection, @debug) -> - @_packetSize = DEFAULT_PACKET_SIZE - + constructor: (@connection, @_packetSize, @debug) -> @connection.addListener('data', @eventData) @packetBuffer = new Buffer(0) @@ -24,6 +20,9 @@ class MessageIO extends EventEmitter @logPacket('Received', packet); @emit('packet', packet) + if (packet.isLast()) + @emit('message') + @packetBuffer = new Buffer(@packetBuffer.slice(length)) packetSize: (packetSize) -> diff --git a/test/unit/message-io-test.coffee b/test/unit/message-io-test.coffee index a01f85312..8e62708d7 100644 --- a/test/unit/message-io-test.coffee +++ b/test/unit/message-io-test.coffee @@ -14,6 +14,7 @@ class Connection extends EventEmitter @emit('packet', packet) packetType = 2; +packetSize = 4 exports.sendSmallerThanOnePacket = (test) -> payload = new Buffer([1, 2, 3]) @@ -27,8 +28,7 @@ exports.sendSmallerThanOnePacket = (test) -> test.done() ) - io = new MessageIO(connection, new Debug()) - io.packetSize(4) + io = new MessageIO(connection, packetSize, new Debug()) io.sendMessage(packetType, payload) exports.sendExactlyPacket = (test) -> @@ -43,8 +43,7 @@ exports.sendExactlyPacket = (test) -> test.done() ) - io = new MessageIO(connection, new Debug()) - io.packetSize(4) + io = new MessageIO(connection, packetSize, new Debug()) io.sendMessage(packetType, payload) exports.sendOneLongerThanPacket = (test) -> @@ -70,20 +69,22 @@ exports.sendOneLongerThanPacket = (test) -> test.done() ) - io = new MessageIO(connection, new Debug()) - io.packetSize(4) + io = new MessageIO(connection, packetSize, new Debug()) io.sendMessage(packetType, payload) exports.receiveOnePacket = (test) -> + test.expect(2) + payload = new Buffer([1, 2, 3]) connection = new Connection() - io = new MessageIO(connection, new Debug()) + io = new MessageIO(connection, packetSize, new Debug()) io.on('packet', (packet) -> - test.strictEqual(packet.type(), packetType) - test.ok(packet.data().equals(payload)) - - test.done() + test.strictEqual(packet.type(), packetType) + test.ok(packet.data().equals(payload)) + ) + io.on('message', -> + test.done() ) packet = new Packet(packetType) @@ -92,15 +93,18 @@ exports.receiveOnePacket = (test) -> connection.emit('data', packet.buffer) exports.receiveOnePacketInTwoChunks = (test) -> + test.expect(2) + payload = new Buffer([1, 2, 3]) connection = new Connection() - io = new MessageIO(connection, new Debug()) + io = new MessageIO(connection, packetSize, new Debug()) io.on('packet', (packet) -> test.strictEqual(packet.type(), packetType) test.ok(packet.data().equals(payload)) - - test.done() + ) + io.on('message', -> + test.done() ) packet = new Packet(packetType) @@ -110,6 +114,8 @@ exports.receiveOnePacketInTwoChunks = (test) -> connection.emit('data', packet.buffer.slice(4)) exports.receiveTwoPackets = (test) -> + test.expect(4) + payload = new Buffer([1, 2, 3]) payload1 = payload.slice(0, 2) payload2 = payload.slice(2, 3) @@ -117,7 +123,7 @@ exports.receiveTwoPackets = (test) -> connection = new Connection() receivedPacketCount = 0 - io = new MessageIO(connection, new Debug()) + io = new MessageIO(connection, packetSize, new Debug()) io.on('packet', (packet) -> receivedPacketCount++ @@ -128,7 +134,9 @@ exports.receiveTwoPackets = (test) -> test.ok(packet.data().equals(payload1)) when 2 test.ok(packet.data().equals(payload2)) - test.done() + ) + io.on('message', -> + test.done() ) packet = new Packet(packetType) @@ -141,6 +149,8 @@ exports.receiveTwoPackets = (test) -> connection.emit('data', packet.buffer) exports.receiveTwoPacketsWithChunkSpanningPackets = (test) -> + test.expect(4) + payload = new Buffer([1, 2, 3, 4]) payload1 = payload.slice(0, 2) payload2 = payload.slice(2, 4) @@ -148,7 +158,7 @@ exports.receiveTwoPacketsWithChunkSpanningPackets = (test) -> connection = new Connection() receivedPacketCount = 0 - io = new MessageIO(connection, new Debug()) + io = new MessageIO(connection, packetSize, new Debug()) io.on('packet', (packet) -> receivedPacketCount++ @@ -159,7 +169,9 @@ exports.receiveTwoPacketsWithChunkSpanningPackets = (test) -> test.ok(packet.data().equals(payload1)) when 2 test.ok(packet.data().equals(payload2)) - test.done() + ) + io.on('message', -> + test.done() ) packet1 = new Packet(packetType) @@ -174,6 +186,8 @@ exports.receiveTwoPacketsWithChunkSpanningPackets = (test) -> connection.emit('data', packet2.buffer.slice(4)) exports.receiveMultiplePacketsWithMoreThanOnePacketFromOneChunk = (test) -> + test.expect(6) + payload = new Buffer([1, 2, 3, 4, 5, 6]) payload1 = payload.slice(0, 2) payload2 = payload.slice(2, 4) @@ -182,7 +196,7 @@ exports.receiveMultiplePacketsWithMoreThanOnePacketFromOneChunk = (test) -> connection = new Connection() receivedPacketCount = 0 - io = new MessageIO(connection, new Debug()) + io = new MessageIO(connection, packetSize, new Debug()) io.on('packet', (packet) -> receivedPacketCount++ @@ -195,7 +209,9 @@ exports.receiveMultiplePacketsWithMoreThanOnePacketFromOneChunk = (test) -> test.ok(packet.data().equals(payload2)) when 3 test.ok(packet.data().equals(payload3)) - test.done() + ) + io.on('message', -> + test.done() ) packet1 = new Packet(packetType) From e562907230e1e3427bb12cc40094ab154f977626 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 11:26:04 +0000 Subject: [PATCH 14/34] Send PreLogin request. Reinstate connection logging. --- lib/connection/connection-statemachine.coffee | 65 +++++++++++++------ lib/message-io.coffee | 24 ++++--- .../connection-factory-test.coffee | 6 ++ 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 5f72f8fec..7cde926f4 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -1,16 +1,28 @@ Socket = require('net').Socket +Debug = require('../debug') +TYPE = require('../packet').TYPE +PreloginPayload = require('../prelogin-payload') +Login7Payload = require('../login7-payload') +MessageIO = require('../message-io') KEEP_ALIVE_INITIAL_DELAY = 30 * 1000 -CONNECT_TIMEOUT_DEFAULT = 15 * 1000 -CLIENT_REQUEST_TIMEOUT_DEFAULT = 15 * 1000 -CANCEL_TIMEOUT_DEFAULT = 5 * 1000 +DEFAULT_CONNECT_TIMEOUT = 15 * 1000 +DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000 +DEFAULT_CANCEL_TIMEOUT = 5 * 1000 +DEFAULT_PACKET_SIZE = 4 * 1024 connectionStateMachine = (fire, client, config) -> # Used in diagram. @name = 'Connection - State Machine' - socket = undefined - connectTimer = undefined + state = + packetSize: DEFAULT_PACKET_SIZE + + #socket = undefined + #messageIo = undefined + #debug = undefined + #connectTimer = undefined + #packetSize = DEFAULT_PACKET_SIZE @startState = 'Connecting' @@ -18,15 +30,17 @@ connectionStateMachine = (fire, client, config) -> Connecting: entry: -> defaultConfig() + createDebug() connect() - connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); + state.connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); - fire.$regEmitter('socket', socket, true); + fire.$regEmitter('socket', state.socket, true); null actions: 'socket.connect': -> + sendPreLogin() 'SentPrelogin' 'socket.error': -> @@ -43,7 +57,7 @@ connectionStateMachine = (fire, client, config) -> clearConnectTimer() client.emit('connection') - 'Final' + null#'Final' actions: '.done': 'SentLogin7WithStandardLogin' @@ -92,8 +106,8 @@ connectionStateMachine = (fire, client, config) -> entry: -> clearConnectTimer() - if socket - socket.destroy() + if state.socket + state.socket.destroy() client.emit('end') @@ -102,25 +116,38 @@ connectionStateMachine = (fire, client, config) -> defaultConfig = -> config.options ||= {} config.options.port ||= 1433 - config.options.connectTimeout ||= CONNECT_TIMEOUT_DEFAULT - config.options.requestTimeout ||= CLIENT_REQUEST_TIMEOUT_DEFAULT - config.options.cancelTimeout ||= CANCEL_TIMEOUT_DEFAULT + config.options.connectTimeout ||= DEFAULT_CONNECT_TIMEOUT + config.options.requestTimeout ||= DEFAULT_CLIENT_REQUEST_TIMEOUT + config.options.cancelTimeout ||= DEFAULT_CANCEL_TIMEOUT + + createDebug = -> + state.debug = new Debug(config.options.debug) + state.debug.on('debug', (message) -> + client.emit('debug', message) + ) connect = -> - socket = new Socket({}) - socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) - socket.connect(config.options.port, config.server) + state.socket = new Socket({}) + state.socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) + state.socket.connect(config.options.port, config.server) - socket.on('error', (error) -> + state.socket.on('error', (error) -> # Need this listener, or else the error actions are not fired. Weird. ) + state.messageIo = new MessageIO(state.socket, state.packetSize, state.debug) + connectTimeout = -> client.emit('connection', "timeout : failed to connect in #{config.options.connectTimeout}ms") 'Final' clearConnectTimer = -> - if connectTimer - clearTimeout(connectTimer) + if state.connectTimer + clearTimeout(state.connectTimer) + + sendPreLogin = -> + payload = new PreloginPayload() + state.messageIo.sendMessage(TYPE.PRELOGIN, payload) + #state.debug.payload(payload.toString(' ')) module.exports = connectionStateMachine diff --git a/lib/message-io.coffee b/lib/message-io.coffee index 946a1837b..603fb15ce 100644 --- a/lib/message-io.coffee +++ b/lib/message-io.coffee @@ -5,8 +5,8 @@ packetLength = require('./packet').packetLength Packet = require('./packet').Packet class MessageIO extends EventEmitter - constructor: (@connection, @_packetSize, @debug) -> - @connection.addListener('data', @eventData) + constructor: (@socket, @packetSize, @debug) -> + @socket.addListener('data', @eventData) @packetBuffer = new Buffer(0) @payloadBuffer = new Buffer(0) @@ -27,21 +27,25 @@ class MessageIO extends EventEmitter packetSize: (packetSize) -> if arguments.length > 0 - @debug.log("Packet size changed from #{@_packetSize} to #{packetSize}") - @_packetSize = packetSize + @debug.log("Packet size changed from #{@packetSize} to #{packetSize}") + @packetSize = packetSize @_packetSize + # TODO listen for 'drain' event when socket.write returns false. sendMessage: (packetType, payload) -> - numberOfPackets = (Math.floor((payload.length - 1) / @_packetSize)) + 1 + @debug.payload(payload.toString(' ')) + + data = payload.data + numberOfPackets = (Math.floor((data.length - 1) / @packetSize)) + 1 for packetNumber in [0..numberOfPackets - 1] - payloadStart = packetNumber * @_packetSize + payloadStart = packetNumber * @packetSize if packetNumber < numberOfPackets - 1 - payloadEnd = payloadStart + @_packetSize + payloadEnd = payloadStart + @packetSize else - payloadEnd = payload.length - packetPayload = payload.slice(payloadStart, payloadEnd) + payloadEnd = data.length + packetPayload = data.slice(payloadStart, payloadEnd) packet = new Packet(packetType) packet.last(packetNumber == numberOfPackets - 1) @@ -52,7 +56,7 @@ class MessageIO extends EventEmitter sendPacket: (packet) => @logPacket('Sent', packet); - @connection.write(packet.buffer) + @socket.write(packet.buffer) logPacket: (direction, packet) -> @debug.packet(direction, packet) diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee index ede0d2e78..f19f734f7 100644 --- a/test/integration/connection-factory-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -9,6 +9,7 @@ getConfig = -> config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) config.options.debug = + packet: true data: true payload: true token: true @@ -28,6 +29,7 @@ exports.badServer = (test) -> test.ok(err) test.done() ) + exports.badPort = (test) -> config = getConfig() config.options.port = -1 @@ -49,3 +51,7 @@ exports.connect = (test) -> test.ok(!err) test.done() ) + + connection.on('debug', (text) -> + console.log(text) + ) From 344fac4729be180c6f2c412c7d7e30ac5289c2f4 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 11:43:27 +0000 Subject: [PATCH 15/34] Process Prelogin response. --- lib/connection/connection-statemachine.coffee | 28 +++++++++++-------- lib/message-io.coffee | 5 +--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 7cde926f4..f5f6de619 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -15,15 +15,10 @@ connectionStateMachine = (fire, client, config) -> # Used in diagram. @name = 'Connection - State Machine' + # All global connection state is mantained in this object. state = packetSize: DEFAULT_PACKET_SIZE - #socket = undefined - #messageIo = undefined - #debug = undefined - #connectTimer = undefined - #packetSize = DEFAULT_PACKET_SIZE - @startState = 'Connecting' @states = @@ -35,6 +30,7 @@ connectionStateMachine = (fire, client, config) -> state.connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); fire.$regEmitter('socket', state.socket, true); + fire.$regEmitter('messageIo', state.messageIo, true); null @@ -52,18 +48,26 @@ connectionStateMachine = (fire, client, config) -> #'socket.error': '@error' - SentPrelogin: + SentPrelogin: -> + responseBuffer = new Buffer(0) + entry: -> + # TODO move these 2 lines to the state where connection establishment really finished clearConnectTimer() client.emit('connection') - null#'Final' + null actions: - '.done': 'SentLogin7WithStandardLogin' - '.err': 'Final' 'connectTimeout': -> connectTimeout() + 'messageIo.packet': (packet) -> + responseBuffer = responseBuffer.concat(packet.data()) + null + 'messageIo.message': -> + preloginPayload = new PreloginPayload(responseBuffer) + state.debug.payload(preloginPayload.toString(' ')) + null ### SentTlsNegotiation: @@ -147,7 +151,7 @@ connectionStateMachine = (fire, client, config) -> sendPreLogin = -> payload = new PreloginPayload() - state.messageIo.sendMessage(TYPE.PRELOGIN, payload) - #state.debug.payload(payload.toString(' ')) + state.messageIo.sendMessage(TYPE.PRELOGIN, payload.data) + state.debug.payload(payload.toString(' ')) module.exports = connectionStateMachine diff --git a/lib/message-io.coffee b/lib/message-io.coffee index 603fb15ce..549af0b44 100644 --- a/lib/message-io.coffee +++ b/lib/message-io.coffee @@ -33,10 +33,7 @@ class MessageIO extends EventEmitter @_packetSize # TODO listen for 'drain' event when socket.write returns false. - sendMessage: (packetType, payload) -> - @debug.payload(payload.toString(' ')) - - data = payload.data + sendMessage: (packetType, data) -> numberOfPackets = (Math.floor((data.length - 1) / @packetSize)) + 1 for packetNumber in [0..numberOfPackets - 1] From 34241a5d4e64ca26b3960cb24b51f8275146d234 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 12:34:57 +0000 Subject: [PATCH 16/34] Process tokens in token streams. --- lib/connection/connection-statemachine.coffee | 90 ++++++++++++++----- .../connection-factory-test.coffee | 11 +-- 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index f5f6de619..ad320a705 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -4,6 +4,7 @@ TYPE = require('../packet').TYPE PreloginPayload = require('../prelogin-payload') Login7Payload = require('../login7-payload') MessageIO = require('../message-io') +TokenStreamParser = require('../token/token-stream-parser').Parser KEEP_ALIVE_INITIAL_DELAY = 30 * 1000 DEFAULT_CONNECT_TIMEOUT = 15 * 1000 @@ -16,21 +17,33 @@ connectionStateMachine = (fire, client, config) -> @name = 'Connection - State Machine' # All global connection state is mantained in this object. - state = + connection = packetSize: DEFAULT_PACKET_SIZE @startState = 'Connecting' + @defaults = + actions: + 'tokenStream.infoMessage': (token) -> + client.emit('infoMessage', token) + null + + 'tokenStream.errorMessage': (token) -> + client.emit('errorMessage', token) + null + @states = Connecting: entry: -> defaultConfig() createDebug() + createTokenStreamParser() connect() - state.connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout); + createConnectTimer() - fire.$regEmitter('socket', state.socket, true); - fire.$regEmitter('messageIo', state.messageIo, true); + fire.$regEmitter('socket', connection.socket, true); + fire.$regEmitter('messageIo', connection.messageIo, true); + fire.$regEmitter('tokenStream', connection.tokenStreamParser, true); null @@ -45,6 +58,7 @@ connectionStateMachine = (fire, client, config) -> 'connectTimeout': -> connectTimeout() + 'Final' #'socket.error': '@error' @@ -61,13 +75,18 @@ connectionStateMachine = (fire, client, config) -> actions: 'connectTimeout': -> connectTimeout() + 'Final' + 'messageIo.packet': (packet) -> responseBuffer = responseBuffer.concat(packet.data()) null + 'messageIo.message': -> preloginPayload = new PreloginPayload(responseBuffer) - state.debug.payload(preloginPayload.toString(' ')) - null + connection.debug.payload(preloginPayload.toString(' ')) + + sendLogin7Packet() + 'SentLogin7WithStandardLogin' ### SentTlsNegotiation: @@ -83,16 +102,24 @@ connectionStateMachine = (fire, client, config) -> actions: 'connectTimeout': -> connectTimeout() + 'Final' - ### - SentLogin7WithSpNego: - entry: -> - console.log('sent l7 with spnego') + 'messageIo.packet': (packet) -> + connection.tokenStreamParser.addBuffer(packet.data()) + null + + 'messageIo.message': -> + 'LoggedIn' LoggedIn: entry: -> console.log('logged in') + ### + SentLogin7WithSpNego: + entry: -> + console.log('sent l7 with spnego') + SentClientRequest: entry: -> console.log('sent client request') @@ -110,8 +137,8 @@ connectionStateMachine = (fire, client, config) -> entry: -> clearConnectTimer() - if state.socket - state.socket.destroy() + if connection.socket + connection.socket.destroy() client.emit('end') @@ -125,33 +152,48 @@ connectionStateMachine = (fire, client, config) -> config.options.cancelTimeout ||= DEFAULT_CANCEL_TIMEOUT createDebug = -> - state.debug = new Debug(config.options.debug) - state.debug.on('debug', (message) -> + connection.debug = new Debug(config.options.debug) + connection.debug.on('debug', (message) -> client.emit('debug', message) ) + createTokenStreamParser = -> + connection.tokenStreamParser = new TokenStreamParser(connection.debug) + connect = -> - state.socket = new Socket({}) - state.socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) - state.socket.connect(config.options.port, config.server) + connection.socket = new Socket({}) + connection.socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) + connection.socket.connect(config.options.port, config.server) - state.socket.on('error', (error) -> + connection.socket.on('error', (error) -> # Need this listener, or else the error actions are not fired. Weird. ) - state.messageIo = new MessageIO(state.socket, state.packetSize, state.debug) + connection.messageIo = new MessageIO(connection.socket, connection.packetSize, connection.debug) + + createConnectTimer = -> + connection.connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout) connectTimeout = -> client.emit('connection', "timeout : failed to connect in #{config.options.connectTimeout}ms") - 'Final' clearConnectTimer = -> - if state.connectTimer - clearTimeout(state.connectTimer) + if connection.connectTimer + clearTimeout(connection.connectTimer) sendPreLogin = -> payload = new PreloginPayload() - state.messageIo.sendMessage(TYPE.PRELOGIN, payload.data) - state.debug.payload(payload.toString(' ')) + connection.messageIo.sendMessage(TYPE.PRELOGIN, payload.data) + connection.debug.payload(payload.toString(' ')) + + sendLogin7Packet = -> + loginData = + userName: config.userName + password: config.password + database: config.options.database + + payload = new Login7Payload(loginData) + connection.messageIo.sendMessage(TYPE.LOGIN7, payload.data) + connection.debug.payload(payload.toString(' ')) module.exports = connectionStateMachine diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee index f19f734f7..81c07069d 100644 --- a/test/integration/connection-factory-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -1,7 +1,7 @@ ConnectionFactory = require('../../lib/connection/connection-factory') fs = require('fs') -#require('../../lib/tedious').statemachineLogLevel = 5 +#require('../../lib/tedious').statemachineLogLevel = 4 connectionFactory = new ConnectionFactory() @@ -12,10 +12,7 @@ getConfig = -> packet: true data: true payload: true - token: true - - config.statemachine = - logLevel: 5 + token: false config @@ -52,6 +49,10 @@ exports.connect = (test) -> test.done() ) + connection.on('infoMessage', (info) -> + console.log("#{info.number} : #{info.message}") + ) + connection.on('debug', (text) -> console.log(text) ) From 7c14f895c6985acd421ad971e2cd7d236e8abf35 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 13:01:40 +0000 Subject: [PATCH 17/34] Emit 'connection' event on successful connection. --- lib/connection/connection-factory.coffee | 3 ++ lib/connection/connection-statemachine.coffee | 31 +++++++++++-------- .../connection-factory-test.coffee | 27 +++++++++++----- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/lib/connection/connection-factory.coffee b/lib/connection/connection-factory.coffee index 8698a7f0a..17ff2413f 100644 --- a/lib/connection/connection-factory.coffee +++ b/lib/connection/connection-factory.coffee @@ -17,4 +17,7 @@ class Connection extends EventEmitter constructor: (factory, config) -> factory.spawn(@, config) + close: -> + @emit('close') + module.exports = ConnectionFactory diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index ad320a705..1f5c9f8e9 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -32,6 +32,9 @@ connectionStateMachine = (fire, client, config) -> client.emit('errorMessage', token) null + 'client.close': -> + 'Final' + @states = Connecting: entry: -> @@ -41,6 +44,7 @@ connectionStateMachine = (fire, client, config) -> connect() createConnectTimer() + fire.$regEmitter('client', client, true); fire.$regEmitter('socket', connection.socket, true); fire.$regEmitter('messageIo', connection.messageIo, true); fire.$regEmitter('tokenStream', connection.tokenStreamParser, true); @@ -65,13 +69,6 @@ connectionStateMachine = (fire, client, config) -> SentPrelogin: -> responseBuffer = new Buffer(0) - entry: -> - # TODO move these 2 lines to the state where connection establishment really finished - clearConnectTimer() - client.emit('connection') - - null - actions: 'connectTimeout': -> connectTimeout() @@ -94,10 +91,8 @@ connectionStateMachine = (fire, client, config) -> console.log('sent tls neg') ### - SentLogin7WithStandardLogin: - entry: -> - console.log('sent l7 with standard login') - null + SentLogin7WithStandardLogin: -> + loggedIn = false actions: 'connectTimeout': -> @@ -109,11 +104,21 @@ connectionStateMachine = (fire, client, config) -> null 'messageIo.message': -> - 'LoggedIn' + if loggedIn + clearConnectTimer() + client.emit('connection') + 'LoggedIn' + else + client.emit('connection', 'Login failed; one or more errorMessage events should have been emitted') + 'Final' + + 'tokenStream.loginack': (token) -> + loggedIn = true + null LoggedIn: entry: -> - console.log('logged in') + #console.log('logged in') ### SentLogin7WithSpNego: diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee index 81c07069d..b7a1e3fbb 100644 --- a/test/integration/connection-factory-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -23,8 +23,11 @@ exports.badServer = (test) -> connection = connectionFactory.createConnection(config) connection.on('connection', (err) -> - test.ok(err) - test.done() + test.ok(err) + ) + + connection.on('end', (info) -> + test.done() ) exports.badPort = (test) -> @@ -35,8 +38,11 @@ exports.badPort = (test) -> connection = connectionFactory.createConnection(config) connection.on('connection', (err) -> - test.ok(err) - test.done() + test.ok(err) + ) + + connection.on('end', (info) -> + test.done() ) exports.connect = (test) -> @@ -45,14 +51,19 @@ exports.connect = (test) -> connection = connectionFactory.createConnection(config) connection.on('connection', (err) -> - test.ok(!err) - test.done() + test.ok(!err) + + connection.close() + ) + + connection.on('end', (info) -> + test.done() ) connection.on('infoMessage', (info) -> - console.log("#{info.number} : #{info.message}") + #console.log("#{info.number} : #{info.message}") ) connection.on('debug', (text) -> - console.log(text) + #console.log(text) ) From 8e8d19f1b369af3af8fa95e7ca3c6f2c659910ff Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 14:03:29 +0000 Subject: [PATCH 18/34] When login fails, emit 'connection' event with error. Add test for login failure with bad credential.. --- lib/connection/connection-statemachine.coffee | 19 +++++++---- .../connection-factory-test.coffee | 33 ++++++++++++++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 1f5c9f8e9..9b4cef959 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -33,7 +33,10 @@ connectionStateMachine = (fire, client, config) -> null 'client.close': -> - 'Final' + if (!connection.closed) + 'Final' + else + null @states = Connecting: @@ -119,6 +122,7 @@ connectionStateMachine = (fire, client, config) -> LoggedIn: entry: -> #console.log('logged in') + null ### SentLogin7WithSpNego: @@ -140,14 +144,17 @@ connectionStateMachine = (fire, client, config) -> Final: entry: -> - clearConnectTimer() + if !connection.closed + connection.closed = true + + clearConnectTimer() - if connection.socket - connection.socket.destroy() + if connection.socket + connection.socket.destroy() - client.emit('end') + client.emit('end') - '@exit' + '@exit' defaultConfig = -> config.options ||= {} diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee index b7a1e3fbb..555b7d0c9 100644 --- a/test/integration/connection-factory-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -1,7 +1,7 @@ ConnectionFactory = require('../../lib/connection/connection-factory') fs = require('fs') -#require('../../lib/tedious').statemachineLogLevel = 4 +#require('../../lib/tedious').statemachineLogLevel = 8 connectionFactory = new ConnectionFactory() @@ -45,6 +45,37 @@ exports.badPort = (test) -> test.done() ) +exports.badCredentials = (test) -> + config = getConfig() + config.password = 'bad-password' + + test.expect(2) + + connection = connectionFactory.createConnection(config) + + connection.on('connection', (err) -> + test.ok(err) + + connection.close() + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('errorMessage', (error) -> + #console.log("#{error.number} : #{error.message}") + test.ok(~error.message.indexOf('failed')) + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + exports.connect = (test) -> config = getConfig() From 7dc31f4d10632ab0680f254468cedd26c6751706 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 14:24:40 +0000 Subject: [PATCH 19/34] Support configurable packet size. --- lib/connection/connection-statemachine.coffee | 11 ++++++++--- lib/login7-payload.coffee | 4 +--- lib/message-io.coffee | 12 ++++++------ test/integration/connection-factory-test.coffee | 1 + 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index 9b4cef959..a68512c9a 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -17,8 +17,7 @@ connectionStateMachine = (fire, client, config) -> @name = 'Connection - State Machine' # All global connection state is mantained in this object. - connection = - packetSize: DEFAULT_PACKET_SIZE + connection = {} @startState = 'Connecting' @@ -119,6 +118,10 @@ connectionStateMachine = (fire, client, config) -> loggedIn = true null + 'tokenStream.packetSizeChange': (token) -> + connection.messageIo.packetSize(token.newValue) + null + LoggedIn: entry: -> #console.log('logged in') @@ -162,6 +165,7 @@ connectionStateMachine = (fire, client, config) -> config.options.connectTimeout ||= DEFAULT_CONNECT_TIMEOUT config.options.requestTimeout ||= DEFAULT_CLIENT_REQUEST_TIMEOUT config.options.cancelTimeout ||= DEFAULT_CANCEL_TIMEOUT + config.options.packetSize ||= DEFAULT_PACKET_SIZE createDebug = -> connection.debug = new Debug(config.options.debug) @@ -181,7 +185,7 @@ connectionStateMachine = (fire, client, config) -> # Need this listener, or else the error actions are not fired. Weird. ) - connection.messageIo = new MessageIO(connection.socket, connection.packetSize, connection.debug) + connection.messageIo = new MessageIO(connection.socket, config.options.packetSize, connection.debug) createConnectTimer = -> connection.connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout) @@ -203,6 +207,7 @@ connectionStateMachine = (fire, client, config) -> userName: config.userName password: config.password database: config.options.database + packetSize: config.options.packetSize payload = new Login7Payload(loginData) connection.messageIo.sendMessage(TYPE.LOGIN7, payload.data) diff --git a/lib/login7-payload.coffee b/lib/login7-payload.coffee index ff882865c..d5de65a8b 100644 --- a/lib/login7-payload.coffee +++ b/lib/login7-payload.coffee @@ -68,8 +68,6 @@ FLAGS_3 = DEFAULT_TDS_VERSION = versions['7_2'] -DEFAULT_PACKET_SIZE = 4 * 1024 - ### s2.2.6.3 ### @@ -90,7 +88,7 @@ class Login7Payload createFixedData: -> @tdsVersion = DEFAULT_TDS_VERSION - @packetSize = DEFAULT_PACKET_SIZE + @packetSize = @loginData.packetSize @clientProgVer = 0 @clientPid = process.pid @connectionId = 0 diff --git a/lib/message-io.coffee b/lib/message-io.coffee index 549af0b44..7da1f1368 100644 --- a/lib/message-io.coffee +++ b/lib/message-io.coffee @@ -5,7 +5,7 @@ packetLength = require('./packet').packetLength Packet = require('./packet').Packet class MessageIO extends EventEmitter - constructor: (@socket, @packetSize, @debug) -> + constructor: (@socket, @_packetSize, @debug) -> @socket.addListener('data', @eventData) @packetBuffer = new Buffer(0) @@ -27,19 +27,19 @@ class MessageIO extends EventEmitter packetSize: (packetSize) -> if arguments.length > 0 - @debug.log("Packet size changed from #{@packetSize} to #{packetSize}") - @packetSize = packetSize + @debug.log("Packet size changed from #{@_packetSize} to #{packetSize}") + @_packetSize = packetSize @_packetSize # TODO listen for 'drain' event when socket.write returns false. sendMessage: (packetType, data) -> - numberOfPackets = (Math.floor((data.length - 1) / @packetSize)) + 1 + numberOfPackets = (Math.floor((data.length - 1) / @_packetSize)) + 1 for packetNumber in [0..numberOfPackets - 1] - payloadStart = packetNumber * @packetSize + payloadStart = packetNumber * @_packetSize if packetNumber < numberOfPackets - 1 - payloadEnd = payloadStart + @packetSize + payloadEnd = payloadStart + @_packetSize else payloadEnd = data.length packetPayload = data.slice(payloadStart, payloadEnd) diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee index 555b7d0c9..bf6cd0776 100644 --- a/test/integration/connection-factory-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -13,6 +13,7 @@ getConfig = -> data: true payload: true token: false + log: true config From 39674f19471c65c22dbb0d04d2c93721ef2f2d9d Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 14:31:47 +0000 Subject: [PATCH 20/34] Emit events for databaseChange, languageChange and charsetChange. --- lib/connection/connection-statemachine.coffee | 12 ++++++++++++ test/integration/connection-factory-test.coffee | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee index a68512c9a..f953549f9 100644 --- a/lib/connection/connection-statemachine.coffee +++ b/lib/connection/connection-statemachine.coffee @@ -31,6 +31,18 @@ connectionStateMachine = (fire, client, config) -> client.emit('errorMessage', token) null + 'tokenStream.databaseChange': (token) -> + client.emit('databaseChange', token.newValue) + null + + 'tokenStream.languageChange': (token) -> + client.emit('languageChange', token.newValue) + null + + 'tokenStream.charsetChange': (token) -> + client.emit('charsetChange', token.newValue) + null + 'client.close': -> if (!connection.closed) 'Final' diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee index bf6cd0776..3cca94032 100644 --- a/test/integration/connection-factory-test.coffee +++ b/test/integration/connection-factory-test.coffee @@ -47,11 +47,11 @@ exports.badPort = (test) -> ) exports.badCredentials = (test) -> + test.expect(2) + config = getConfig() config.password = 'bad-password' - test.expect(2) - connection = connectionFactory.createConnection(config) connection.on('connection', (err) -> @@ -78,6 +78,8 @@ exports.badCredentials = (test) -> ) exports.connect = (test) -> + test.expect(2) + config = getConfig() connection = connectionFactory.createConnection(config) @@ -92,6 +94,10 @@ exports.connect = (test) -> test.done() ) + connection.on('databaseChange', (database) -> + test.strictEqual(database, config.options.database) + ) + connection.on('infoMessage', (info) -> #console.log("#{info.number} : #{info.message}") ) From e737ecbfa4e66fa08ed90ec4d9926f17a3cbeccf Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 19:57:54 +0000 Subject: [PATCH 21/34] Implemented a (somewhat crude) state machine for connection management, so that Ignite will not need to be used. --- lib/connection.coffee | 3 +- lib/connection2.coffee | 215 +++++++++++++++++++++++ test/integration/connection2-test.coffee | 111 ++++++++++++ 3 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 lib/connection2.coffee create mode 100644 test/integration/connection2-test.coffee diff --git a/lib/connection.coffee b/lib/connection.coffee index 6e8ebb837..2d1954007 100644 --- a/lib/connection.coffee +++ b/lib/connection.coffee @@ -48,7 +48,7 @@ class Connection extends EventEmitter @connection.addListener('error', @eventError) @connection.addListener('timeout', @eventTimeout) - @messageIo = new MessageIO(@connection, @debug) + @messageIo = new MessageIO(@connection, 4096, @debug) @messageIo.on('packet', @eventPacket) @startRequest('connect/login', callback) @@ -187,6 +187,7 @@ class Connection extends EventEmitter userName: @userName, password: @password, database: @options.database + packetSize: 4096 payload = new Login7Payload(loginData) @messageIo.sendMessage(TYPE.LOGIN7, payload.data) diff --git a/lib/connection2.coffee b/lib/connection2.coffee new file mode 100644 index 000000000..39beae81f --- /dev/null +++ b/lib/connection2.coffee @@ -0,0 +1,215 @@ +require('./buffertools') +Debug = require('./debug') +EventEmitter = require('events').EventEmitter +TYPE = require('./packet').TYPE +PreloginPayload = require('./prelogin-payload') +Login7Payload = require('./login7-payload') +SqlBatchPayload = require('./sqlbatch-payload') +MessageIO = require('./message-io') +Socket = require('net').Socket +TokenStreamParser = require('./token/token-stream-parser').Parser + +# A rather basic state machine for managing a connection. +# Implements something approximating s3.2.1. + +KEEP_ALIVE_INITIAL_DELAY = 30 * 1000 +DEFAULT_CONNECT_TIMEOUT = 15 * 1000 +DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000 +DEFAULT_CANCEL_TIMEOUT = 5 * 1000 +DEFAULT_PACKET_SIZE = 4 * 1024 +DEFAULT_PORT = 1433 + +class Connection extends EventEmitter + STATE: + CONNECTING: + name: 'Connecting' + enter: -> + @initialiseConnection() + events: + socketError: (error) -> + @transitionTo(@STATE.FINAL) + connectTimeout: -> + @transitionTo(@STATE.FINAL) + socketConnect: -> + @sendPreLogin() + @transitionTo(@STATE.SENT_PRELOGIN) + + SENT_PRELOGIN: + name: 'SentPrelogin' + enter: -> + @emptyMessageBuffer() + events: + packet: (packet) -> + @addToMessageBuffer(packet) + message: -> + @processPreLoginResponse() + @sendLogin7Packet() + @transitionTo(@STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN) + SENT_LOGIN7_WITH_STANDARD_LOGIN: + name: 'SentLogin7WithStandardLogin' + events: + packet: (packet) -> + @sendPacketToTokenStreamParser(packet) + message: -> + if @loggedIn + @clearConnectTimer() + @emit('connection') + @transitionTo(@STATE.LOGGED_IN) + else + @emit('connection', 'Login failed; one or more errorMessage events should have been emitted') + @transitionTo(@STATE.FINAL) + LOGGED_IN: + name: 'LoggedIn' + FINAL: + name: 'Final' + enter: -> + if !@closed + @clearConnectTimer() + @closeConnection() + @emit('end') + @closed = true + + constructor: (@config) -> + @defaultConfig() + @createDebug() + @createTokenStreamParser() + + @transitionTo(@STATE.CONNECTING) + + close: -> + @transitionTo(@STATE.FINAL) + + initialiseConnection: -> + @connect() + @createConnectTimer() + + defaultConfig: -> + @config.options ||= {} + @config.options.port ||= DEFAULT_PORT + @config.options.connectTimeout ||= DEFAULT_CONNECT_TIMEOUT + @config.options.requestTimeout ||= DEFAULT_CLIENT_REQUEST_TIMEOUT + @config.options.cancelTimeout ||= DEFAULT_CANCEL_TIMEOUT + @config.options.packetSize ||= DEFAULT_PACKET_SIZE + + createDebug: -> + @debug = new Debug(@config.options.debug) + @debug.on('debug', (message) => + @emit('debug', message) + ) + + createTokenStreamParser: -> + @tokenStreamParser = new TokenStreamParser(@debug) + @tokenStreamParser.on('infoMessage', (token) => + @emit('infoMessage', token) + ) + @tokenStreamParser.on('errorMessage', (token) => + @emit('errorMessage', token) + ) + @tokenStreamParser.on('databaseChange', (token) => + @emit('databaseChange', token.newValue) + ) + @tokenStreamParser.on('languageChange', (token) => + @emit('languageChange', token.newValue) + ) + @tokenStreamParser.on('charsetChange', (token) => + @emit('charsetChange', token.newValue) + ) + @tokenStreamParser.on('loginack', (token) => + @loggedIn = true + ) + @tokenStreamParser.on('packetSizeChange', (token) => + @messageIo.packetSize(token.newValue) + ) + + connect: -> + @socket = new Socket({}) + @socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) + @socket.connect(@config.options.port, @config.server) + @socket.on('error', @socketError) + @socket.on('connect', @socketConnect) + + @messageIo = new MessageIO(@socket, @config.options.packetSize, @debug) + @messageIo.on('packet', @packetReceived) + @messageIo.on('message', @messageReceived) + + closeConnection: -> + @socket.destroy() + + createConnectTimer: -> + @connectTimer = setTimeout(@connectTimeout, @config.options.connectTimeout) + + connectTimeout: => + message = "timeout : failed to connect to #{@config.server}:#{@config.options.port} in #{@config.options.connectTimeout}ms" + + @debug.log(message) + @emit('connection', message) + @connectTimer = undefined + @dispatchEvent('connectTimeout') + + clearConnectTimer: -> + if @connectTimer + clearTimeout(@connectTimer) + + transitionTo: (newState) -> + if @state?.exit + @state.exit.apply(@) + + @debug.log("State change: #{@state?.name} -> #{newState.name}") + @state = newState + + if @state.enter + @state.enter.apply(@) + + dispatchEvent: (eventName, args...) -> + if @state.events && @state.events.hasOwnProperty(eventName) + eventFunction = @state.events[eventName].apply(@, args) + else + throw new Error("No event '#{eventName}' in state '#{@state.name}'") + + socketError: (error) => + message = "connection to #{@config.server}:#{@config.options.port} failed" + + @debug.log(message) + @emit('connection', message) + @dispatchEvent('socketError', error) + + socketConnect: => + @debug.log("connected to #{@config.server}:#{@config.options.port}") + @dispatchEvent('socketConnect') + + packetReceived: (packet) => + @dispatchEvent('packet', packet) + + messageReceived: => + @dispatchEvent('message') + + sendPreLogin: -> + payload = new PreloginPayload() + @messageIo.sendMessage(TYPE.PRELOGIN, payload.data) + @debug.payload(payload.toString(' ')) + + emptyMessageBuffer: -> + @messageBuffer = new Buffer(0) + + addToMessageBuffer: (packet) -> + @messageBuffer = @messageBuffer.concat(packet.data()) + + processPreLoginResponse: -> + preloginPayload = new PreloginPayload(@messageBuffer) + @debug.payload(preloginPayload.toString(' ')) + + sendLogin7Packet: -> + loginData = + userName: @config.userName + password: @config.password + database: @config.options.database + packetSize: @config.options.packetSize + + payload = new Login7Payload(loginData) + @messageIo.sendMessage(TYPE.LOGIN7, payload.data) + @debug.payload(payload.toString(' ')) + + sendPacketToTokenStreamParser: (packet) -> + @tokenStreamParser.addBuffer(packet.data()) + +module.exports = Connection diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee new file mode 100644 index 000000000..7e0e22039 --- /dev/null +++ b/test/integration/connection2-test.coffee @@ -0,0 +1,111 @@ +Connection = require('../../lib/connection2') +fs = require('fs') + +getConfig = -> + config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) + + config.options.debug = + packet: true + data: true + payload: true + token: false + log: true + + config + +exports.badServer = (test) -> + config = getConfig() + config.server = 'bad-server' + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(err) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + +exports.badPort = (test) -> + config = getConfig() + config.options.port = -1 + config.options.connectTimeout = 200 + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(err) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + +exports.badCredentials = (test) -> + test.expect(2) + + config = getConfig() + config.password = 'bad-password' + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(err) + + connection.close() + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('errorMessage', (error) -> + #console.log("#{error.number} : #{error.message}") + test.ok(~error.message.indexOf('failed')) + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + +exports.connect = (test) -> + test.expect(2) + + config = getConfig() + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(!err) + + connection.close() + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('databaseChange', (database) -> + test.strictEqual(database, config.options.database) + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('debug', (text) -> + #console.log(text) + ) From 91d04e8fe22e59c94fb61a6300cc8f0be0506bb4 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 20:05:40 +0000 Subject: [PATCH 22/34] Make connection state machine a little easier to read; remove single line functions, and create functions to wrap functionality. --- lib/connection2.coffee | 44 +++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/connection2.coffee b/lib/connection2.coffee index 39beae81f..3a811492f 100644 --- a/lib/connection2.coffee +++ b/lib/connection2.coffee @@ -51,23 +51,13 @@ class Connection extends EventEmitter packet: (packet) -> @sendPacketToTokenStreamParser(packet) message: -> - if @loggedIn - @clearConnectTimer() - @emit('connection') - @transitionTo(@STATE.LOGGED_IN) - else - @emit('connection', 'Login failed; one or more errorMessage events should have been emitted') - @transitionTo(@STATE.FINAL) + @processLogin7Response() LOGGED_IN: name: 'LoggedIn' FINAL: name: 'Final' enter: -> - if !@closed - @clearConnectTimer() - @closeConnection() - @emit('end') - @closed = true + @cleanupConnection() constructor: (@config) -> @defaultConfig() @@ -83,6 +73,13 @@ class Connection extends EventEmitter @connect() @createConnectTimer() + cleanupConnection: -> + if !@closed + @clearConnectTimer() + @closeConnection() + @emit('end') + @closed = true + defaultConfig: -> @config.options ||= {} @config.options.port ||= DEFAULT_PORT @@ -129,8 +126,12 @@ class Connection extends EventEmitter @socket.on('connect', @socketConnect) @messageIo = new MessageIO(@socket, @config.options.packetSize, @debug) - @messageIo.on('packet', @packetReceived) - @messageIo.on('message', @messageReceived) + @messageIo.on('packet', (packet) => + @dispatchEvent('packet', packet) + ) + @messageIo.on('message', => + @dispatchEvent('message') + ) closeConnection: -> @socket.destroy() @@ -177,12 +178,6 @@ class Connection extends EventEmitter @debug.log("connected to #{@config.server}:#{@config.options.port}") @dispatchEvent('socketConnect') - packetReceived: (packet) => - @dispatchEvent('packet', packet) - - messageReceived: => - @dispatchEvent('message') - sendPreLogin: -> payload = new PreloginPayload() @messageIo.sendMessage(TYPE.PRELOGIN, payload.data) @@ -212,4 +207,13 @@ class Connection extends EventEmitter sendPacketToTokenStreamParser: (packet) -> @tokenStreamParser.addBuffer(packet.data()) + processLogin7Response: -> + if @loggedIn + @clearConnectTimer() + @emit('connection') + @transitionTo(@STATE.LOGGED_IN) + else + @emit('connection', 'Login failed; one or more errorMessage events should have been emitted') + @transitionTo(@STATE.FINAL) + module.exports = Connection From 3dfcbdcb15394c5e07621521fb588ed3357ed122 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 20:58:19 +0000 Subject: [PATCH 23/34] Ability to make a sqlRequest to the new connection state machine, with the expected events fired. --- lib/connection2.coffee | 39 +++++++++++++++++- test/integration/connection2-test.coffee | 52 +++++++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/lib/connection2.coffee b/lib/connection2.coffee index 3a811492f..50389efaf 100644 --- a/lib/connection2.coffee +++ b/lib/connection2.coffee @@ -54,6 +54,14 @@ class Connection extends EventEmitter @processLogin7Response() LOGGED_IN: name: 'LoggedIn' + SENT_CLIENT_REQUEST: + name: 'SentClientRequest' + events: + packet: (packet) -> + @sendPacketToTokenStreamParser(packet) + message: -> + @sqlRequest.callback(@sqlRequest.error) + @sqlRequest = undefined FINAL: name: 'Final' enter: -> @@ -101,6 +109,8 @@ class Connection extends EventEmitter ) @tokenStreamParser.on('errorMessage', (token) => @emit('errorMessage', token) + if @sqlRequest + @sqlRequest.error = token.message ) @tokenStreamParser.on('databaseChange', (token) => @emit('databaseChange', token.newValue) @@ -117,6 +127,18 @@ class Connection extends EventEmitter @tokenStreamParser.on('packetSizeChange', (token) => @messageIo.packetSize(token.newValue) ) + @tokenStreamParser.on('columnMetadata', (token) => + if @sqlRequest + @sqlRequest.emit('columnMetadata', token.columns) + ) + @tokenStreamParser.on('row', (token) => + if @sqlRequest + @sqlRequest.emit('row', token.columns) + ) + @tokenStreamParser.on('done', (token) => + if @sqlRequest + @sqlRequest.emit('done', token.rowCount) + ) connect: -> @socket = new Socket({}) @@ -210,10 +232,25 @@ class Connection extends EventEmitter processLogin7Response: -> if @loggedIn @clearConnectTimer() - @emit('connection') @transitionTo(@STATE.LOGGED_IN) + @emit('connection') else @emit('connection', 'Login failed; one or more errorMessage events should have been emitted') @transitionTo(@STATE.FINAL) + execSql: (request) -> + if @state != @STATE.LOGGED_IN + message = "Invalid state; requests can only be made in the #{@STATE.LOGGED_IN.name} state, not the #{@state.name} state" + + @debug.log(message) + request.callback(message) + else + @sqlRequest = request + + payload = new SqlBatchPayload(request.sqlText) + @messageIo.sendMessage(TYPE.SQL_BATCH, payload.data) + @debug.payload(payload.toString(' ')) + + @transitionTo(@STATE.SENT_CLIENT_REQUEST) + module.exports = Connection diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee index 7e0e22039..dfc4e9200 100644 --- a/test/integration/connection2-test.coffee +++ b/test/integration/connection2-test.coffee @@ -1,4 +1,5 @@ Connection = require('../../lib/connection2') +Request = require('../../lib/request') fs = require('fs') getConfig = -> @@ -99,7 +100,7 @@ exports.connect = (test) -> ) connection.on('databaseChange', (database) -> - test.strictEqual(database, config.options.database) + test.strictEqual(database, config.options.database) ) connection.on('infoMessage', (info) -> @@ -109,3 +110,52 @@ exports.connect = (test) -> connection.on('debug', (text) -> #console.log(text) ) + +exports.execSimpleSql = (test) -> + test.expect(8) + + config = getConfig() + + request = new Request('select 8 as C1', (err) -> + test.ok(!err) + + connection.close() + ) + + request.on('done', (rowCount) -> + test.strictEqual(rowCount, 1) + ) + + request.on('columnMetadata', (columnsMetadata) -> + test.strictEqual(columnsMetadata.length, 1) + ) + + request.on('row', (columns) -> + test.strictEqual(columns.length, 1) + + test.strictEqual(columns[0].value, 8) + + test.strictEqual(columns[0].isNull, false) + + test.strictEqual(columns.byName().C1.value, 8) + ) + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(!err) + + connection.execSql(request) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('debug', (text) -> + console.log(text) + ) From 993328c6684d74f29078709a001d538cd5ddddb5 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Tue, 3 Jan 2012 21:02:41 +0000 Subject: [PATCH 24/34] Add more error checking to the connection state machine. --- lib/connection2.coffee | 4 ++++ test/integration/connection2-test.coffee | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/connection2.coffee b/lib/connection2.coffee index 50389efaf..d024787c4 100644 --- a/lib/connection2.coffee +++ b/lib/connection2.coffee @@ -130,10 +130,14 @@ class Connection extends EventEmitter @tokenStreamParser.on('columnMetadata', (token) => if @sqlRequest @sqlRequest.emit('columnMetadata', token.columns) + else + throw new Error("Received 'columnMetadata' when no sqlRequest is in progress") ) @tokenStreamParser.on('row', (token) => if @sqlRequest @sqlRequest.emit('row', token.columns) + else + throw new Error("Received 'row' when no sqlRequest is in progress") ) @tokenStreamParser.on('done', (token) => if @sqlRequest diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee index dfc4e9200..6c1595aa2 100644 --- a/test/integration/connection2-test.coffee +++ b/test/integration/connection2-test.coffee @@ -157,5 +157,5 @@ exports.execSimpleSql = (test) -> ) connection.on('debug', (text) -> - console.log(text) + #console.log(text) ) From 25ab6c6eed8277044ada02b382b03878d1b1ef28 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Wed, 4 Jan 2012 21:21:38 +0000 Subject: [PATCH 25/34] Add a test for a SQL Batch request that results in more than one set of results. --- test/integration/connection2-test.coffee | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee index 6c1595aa2..a40a85742 100644 --- a/test/integration/connection2-test.coffee +++ b/test/integration/connection2-test.coffee @@ -159,3 +159,47 @@ exports.execSimpleSql = (test) -> connection.on('debug', (text) -> #console.log(text) ) + +exports.sqlWithMultipleResultSets = (test) -> + test.expect(8) + + config = getConfig() + row = 0 + + request = new Request('select 1; select 2;', (err) -> + test.ok(!err) + + connection.close() + ) + + request.on('done', (rowCount) -> + test.strictEqual(rowCount, 1) + ) + + request.on('columnMetadata', (columnsMetadata) -> + test.strictEqual(columnsMetadata.length, 1) + ) + + request.on('row', (columns) -> + test.strictEqual(columns[0].value, ++row) + ) + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(!err) + + connection.execSql(request) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('debug', (text) -> + #console.log(text) + ) From 21a265880e2c8d422b568facf5b4994da3b90db4 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Wed, 4 Jan 2012 21:29:15 +0000 Subject: [PATCH 26/34] Remove unnecessary empty lines. --- test/integration/connection2-test.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee index a40a85742..883928343 100644 --- a/test/integration/connection2-test.coffee +++ b/test/integration/connection2-test.coffee @@ -132,11 +132,8 @@ exports.execSimpleSql = (test) -> request.on('row', (columns) -> test.strictEqual(columns.length, 1) - test.strictEqual(columns[0].value, 8) - test.strictEqual(columns[0].isNull, false) - test.strictEqual(columns.byName().C1.value, 8) ) From 15c95523b251500bacceb543d167582e0ed1869e Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Wed, 4 Jan 2012 21:38:34 +0000 Subject: [PATCH 27/34] Add a test for execution of a bad SQL statement. --- test/integration/connection2-test.coffee | 42 ++++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee index 883928343..995182d35 100644 --- a/test/integration/connection2-test.coffee +++ b/test/integration/connection2-test.coffee @@ -112,7 +112,7 @@ exports.connect = (test) -> ) exports.execSimpleSql = (test) -> - test.expect(8) + test.expect(7) config = getConfig() @@ -140,8 +140,6 @@ exports.execSimpleSql = (test) -> connection = new Connection(config) connection.on('connection', (err) -> - test.ok(!err) - connection.execSql(request) ) @@ -157,8 +155,42 @@ exports.execSimpleSql = (test) -> #console.log(text) ) +exports.execBadSql = (test) -> + test.expect(3) + + config = getConfig() + + request = new Request('bad syntax here', (err) -> + test.ok(err) + + connection.close() + ) + + request.on('done', (rowCount) -> + test.ok(!rowCount) + ) + + connection = new Connection(config) + + connection.on('connection', (err) -> + connection.execSql(request) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('errorMessage', (error) -> + #console.log("#{info.number} : #{info.message}") + test.ok(error) + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + exports.sqlWithMultipleResultSets = (test) -> - test.expect(8) + test.expect(7) config = getConfig() row = 0 @@ -184,8 +216,6 @@ exports.sqlWithMultipleResultSets = (test) -> connection = new Connection(config) connection.on('connection', (err) -> - test.ok(!err) - connection.execSql(request) ) From 6f6e5069b51a3916350841fd9d3ef31e1b06c3fc Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Wed, 4 Jan 2012 22:28:58 +0000 Subject: [PATCH 28/34] Add events for reflecting the progress and status of executed stored procedures. Add tests for the execution of stored procedures. --- lib/connection2.coffee | 18 +++- test/integration/connection2-test.coffee | 112 +++++++++++++++++++++-- 2 files changed, 120 insertions(+), 10 deletions(-) diff --git a/lib/connection2.coffee b/lib/connection2.coffee index d024787c4..4a00ae1fe 100644 --- a/lib/connection2.coffee +++ b/lib/connection2.coffee @@ -139,9 +139,23 @@ class Connection extends EventEmitter else throw new Error("Received 'row' when no sqlRequest is in progress") ) - @tokenStreamParser.on('done', (token) => + @tokenStreamParser.on('returnStatus', (token) => + if @sqlRequest + # Keep value for passing in 'doneProc' event. + @procReturnStatusValue = token.value + ) + @tokenStreamParser.on('doneProc', (token) => if @sqlRequest - @sqlRequest.emit('done', token.rowCount) + @sqlRequest.emit('doneProc', token.rowCount, token.more, @procReturnStatusValue) + @procReturnStatusValue = undefined + ) + @tokenStreamParser.on('doneInProc', (token) => + if @sqlRequest + @sqlRequest.emit('doneInProc', token.rowCount, token.more) + ) + @tokenStreamParser.on('done', (token) => + if @sqlRequest + @sqlRequest.emit('done', token.rowCount, token.more) ) connect: -> diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee index 995182d35..20eec0f96 100644 --- a/test/integration/connection2-test.coffee +++ b/test/integration/connection2-test.coffee @@ -111,8 +111,8 @@ exports.connect = (test) -> #console.log(text) ) -exports.execSimpleSql = (test) -> - test.expect(7) +exports.execSql = (test) -> + test.expect(8) config = getConfig() @@ -122,7 +122,8 @@ exports.execSimpleSql = (test) -> connection.close() ) - request.on('done', (rowCount) -> + request.on('done', (rowCount, more) -> + test.ok(!more) test.strictEqual(rowCount, 1) ) @@ -156,7 +157,7 @@ exports.execSimpleSql = (test) -> ) exports.execBadSql = (test) -> - test.expect(3) + test.expect(4) config = getConfig() @@ -166,7 +167,8 @@ exports.execBadSql = (test) -> connection.close() ) - request.on('done', (rowCount) -> + request.on('done', (rowCount, more) -> + test.ok(!more) test.ok(!rowCount) ) @@ -181,7 +183,7 @@ exports.execBadSql = (test) -> ) connection.on('errorMessage', (error) -> - #console.log("#{info.number} : #{info.message}") + #console.log("#{error.number} : #{error.message}") test.ok(error) ) @@ -190,7 +192,7 @@ exports.execBadSql = (test) -> ) exports.sqlWithMultipleResultSets = (test) -> - test.expect(7) + test.expect(9) config = getConfig() row = 0 @@ -201,7 +203,12 @@ exports.sqlWithMultipleResultSets = (test) -> connection.close() ) - request.on('done', (rowCount) -> + request.on('done', (rowCount, more) -> + switch row + when 1 + test.ok(more) + when 2 + test.ok(!more) test.strictEqual(rowCount, 1) ) @@ -230,3 +237,92 @@ exports.sqlWithMultipleResultSets = (test) -> connection.on('debug', (text) -> #console.log(text) ) + +exports.execProc = (test) -> + test.expect(5) + + config = getConfig() + + request = new Request('exec sp_help int', (err) -> + test.ok(!err) + + connection.close() + ) + + request.on('doneProc', (rowCount, more, returnStatus) -> + test.ok(!more) + test.strictEqual(returnStatus, 0) + ) + + request.on('doneInProc', (rowCount, more) -> + test.ok(more) + ) + + request.on('row', (columns) -> + test.ok(true) + ) + + connection = new Connection(config) + + connection.on('connection', (err) -> + connection.execSql(request) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + +exports.execFailedProc = (test) -> + test.expect(5) + + config = getConfig() + + request = new Request('exec sp_help bad_object_name', (err) -> + test.ok(err) + + connection.close() + ) + + request.on('doneProc', (rowCount, more, returnStatus) -> + test.ok(!more) + test.strictEqual(returnStatus, 1) # Non-zero indicates a failure. + ) + + request.on('doneInProc', (rowCount, more) -> + test.ok(more) + ) + + request.on('row', (columns) -> + test.ok(false) + ) + + connection = new Connection(config) + + connection.on('connection', (err) -> + connection.execSql(request) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('errorMessage', (error) -> + #console.log("#{error.number} : #{error.message}") + test.ok(error) + ) + + connection.on('debug', (text) -> + #console.log(text) + ) From 2f77ae19e1401aaed7ca743253f60be5a62ac90d Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Thu, 5 Jan 2012 20:09:36 +0000 Subject: [PATCH 29/34] Fix broken unit tests. --- lib/tedious.js | 2 +- test/unit/login7-payload-test.coffee | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/tedious.js b/lib/tedious.js index f8320c697..cfb0e7f45 100644 --- a/lib/tedious.js +++ b/lib/tedious.js @@ -2,6 +2,6 @@ require('coffee-script') exports.statemachineLogLevel = 0 -exports.ConnectionFactory = require('./connection/connection-factory') +exports.Connection = require('./connection2') exports.Request = require('./request') exports.library = require('./library') diff --git a/test/unit/login7-payload-test.coffee b/test/unit/login7-payload-test.coffee index 4d9f531cd..1ab951c47 100644 --- a/test/unit/login7-payload-test.coffee +++ b/test/unit/login7-payload-test.coffee @@ -3,12 +3,13 @@ Login7Payload = require('../../lib/login7-payload') exports.create = (test) -> loginData = - userName: 'user', - password: 'pw', - appName: 'app', - serverName: 'server', - language: 'lang', + userName: 'user' + password: 'pw' + appName: 'app' + serverName: 'server' + language: 'lang' database: 'db' + packetSize: 1024 #start = new Date().getTime() #for c in [1..1000] From f926f7751c62afc6af446ee9d15e83e6c05c83a0 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Thu, 5 Jan 2012 21:00:27 +0000 Subject: [PATCH 30/34] Fix more broken unit tests. --- test/integration/connection-test.coffee | 357 ++++++++++++------ test/integration/connection2-test.coffee | 328 ---------------- .../datatypes-in-results-test.coffee | 25 +- 3 files changed, 259 insertions(+), 451 deletions(-) delete mode 100644 test/integration/connection2-test.coffee diff --git a/test/integration/connection-test.coffee b/test/integration/connection-test.coffee index 49bfff4ca..20eec0f96 100644 --- a/test/integration/connection-test.coffee +++ b/test/integration/connection-test.coffee @@ -1,19 +1,101 @@ -Connection = require('../../lib/connection') +Connection = require('../../lib/connection2') Request = require('../../lib/request') fs = require('fs') -config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) -config.options.debug = - data: true - payload: true - token: true +getConfig = -> + config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) + + config.options.debug = + packet: true + data: true + payload: true + token: false + log: true + + config + +exports.badServer = (test) -> + config = getConfig() + config.server = 'bad-server' + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(err) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + +exports.badPort = (test) -> + config = getConfig() + config.options.port = -1 + config.options.connectTimeout = 200 + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(err) + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('debug', (text) -> + #console.log(text) + ) + +exports.badCredentials = (test) -> + test.expect(2) + + config = getConfig() + config.password = 'bad-password' + + connection = new Connection(config) + + connection.on('connection', (err) -> + test.ok(err) + + connection.close() + ) + + connection.on('end', (info) -> + test.done() + ) + + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) + + connection.on('errorMessage', (error) -> + #console.log("#{error.number} : #{error.message}") + test.ok(~error.message.indexOf('failed')) + ) + + connection.on('debug', (text) -> + #console.log(text) + ) exports.connect = (test) -> - connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) -> + test.expect(2) + + config = getConfig() + + connection = new Connection(config) + + connection.on('connection', (err) -> test.ok(!err) - test.ok(loggedIn) - test.strictEqual(connection.database(), config.options.database) + connection.close() + ) + + connection.on('end', (info) -> test.done() ) @@ -21,179 +103,226 @@ exports.connect = (test) -> test.strictEqual(database, config.options.database) ) - connection.on('debug', (message) -> - #console.log(message) + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") ) -exports.execSimpleSql = (test) -> - test.expect(9) + connection.on('debug', (text) -> + #console.log(text) + ) + +exports.execSql = (test) -> + test.expect(8) + + config = getConfig() - request = new Request('select 8 as C1', (err, rowCount) -> + request = new Request('select 8 as C1', (err) -> test.ok(!err) + + connection.close() + ) + + request.on('done', (rowCount, more) -> + test.ok(!more) test.strictEqual(rowCount, 1) - test.done() ) request.on('columnMetadata', (columnsMetadata) -> test.strictEqual(columnsMetadata.length, 1) ) - + request.on('row', (columns) -> test.strictEqual(columns.length, 1) - test.strictEqual(columns[0].value, 8) - test.strictEqual(columns[0].isNull, false) - - byName = columns.byName() - test.strictEqual(byName.C1.value, 8) + test.strictEqual(columns.byName().C1.value, 8) ) - connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) -> - test.ok(!err) - test.ok(loggedIn) + connection = new Connection(config) + connection.on('connection', (err) -> connection.execSql(request) ) - - connection.on('debug', (message) -> - #console.log(message) + + connection.on('end', (info) -> + test.done() ) -exports.execSqlWithLotsOfRowsReturned = (test) -> - numberOfRows = 1000 - rowsReceived = 0 + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) - test.expect(6) + connection.on('debug', (text) -> + #console.log(text) + ) - request = new Request("select top #{numberOfRows} object_id, name from sys.all_columns", (err, rowCount) -> - test.ok(!err) - test.strictEqual(rowCount, numberOfRows) - test.strictEqual(rowsReceived, numberOfRows) - test.done() +exports.execBadSql = (test) -> + test.expect(4) + + config = getConfig() + + request = new Request('bad syntax here', (err) -> + test.ok(err) + + connection.close() ) - request.on('columnMetadata', (columnsMetadata) -> - test.strictEqual(columnsMetadata.length, 2) + request.on('done', (rowCount, more) -> + test.ok(!more) + test.ok(!rowCount) ) - - request.on('row', (columns) -> - rowsReceived++ + + connection = new Connection(config) + + connection.on('connection', (err) -> + connection.execSql(request) ) - connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) -> - test.ok(!err) - test.ok(loggedIn) - - connection.execSql(request) + connection.on('end', (info) -> + test.done() ) - - connection.on('debug', (message) -> - #console.log(message) + + connection.on('errorMessage', (error) -> + #console.log("#{error.number} : #{error.message}") + test.ok(error) ) -exports.execBadSql = (test) -> - test.expect(6) + connection.on('debug', (text) -> + #console.log(text) + ) - request = new Request('select bad syntax here', (err, rowCount) -> - test.ok(err) - test.strictEqual(rowCount, undefined) - test.done() +exports.sqlWithMultipleResultSets = (test) -> + test.expect(9) + + config = getConfig() + row = 0 + + request = new Request('select 1; select 2;', (err) -> + test.ok(!err) + + connection.close() + ) + + request.on('done', (rowCount, more) -> + switch row + when 1 + test.ok(more) + when 2 + test.ok(!more) + test.strictEqual(rowCount, 1) + ) + + request.on('columnMetadata', (columnsMetadata) -> + test.strictEqual(columnsMetadata.length, 1) ) request.on('row', (columns) -> - test.ok(false) + test.strictEqual(columns[0].value, ++row) ) - connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) -> - test.ok(!err) - test.ok(loggedIn) - - connection.execSql(request) + connection = new Connection(config) + + connection.on('connection', (err) -> + connection.execSql(request) ) - - connection.on('errorMessage', (error) -> - test.ok(error.message.indexOf('syntax')) - test.strictEqual(error.number, 102) + + connection.on('end', (info) -> + test.done() ) - connection.on('debug', (message) -> - #console.log(message) + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") ) -exports.execSqlProc = (test) -> - rows = 0 + connection.on('debug', (text) -> + #console.log(text) + ) - request = new Request('exec sp_who2', (err, returnStatus) -> - test.ok(!err) - test.strictEqual(returnStatus, 0) - test.ok(rows > 0) - test.done() +exports.execProc = (test) -> + test.expect(5) + + config = getConfig() + + request = new Request('exec sp_help int', (err) -> + test.ok(!err) + + connection.close() ) - request.on('columnMetadata', (columnsMetadata) -> - test.strictEqual(columnsMetadata.length, 13) + request.on('doneProc', (rowCount, more, returnStatus) -> + test.ok(!more) + test.strictEqual(returnStatus, 0) + ) + + request.on('doneInProc', (rowCount, more) -> + test.ok(more) ) request.on('row', (columns) -> - rows++ - test.strictEqual(columns.length, 13) + test.ok(true) ) - - connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) -> - test.ok(!err) - test.ok(loggedIn) - - connection.execSql(request) + + connection = new Connection(config) + + connection.on('connection', (err) -> + connection.execSql(request) ) - - connection.on('debug', (message) -> - #console.log(message) + + connection.on('end', (info) -> + test.done() ) -exports.badCredentials = (test) -> - test.expect(4) + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") + ) - connection = new Connection(config.server, config.userName, 'bad-password', config.options, (err, loggedIn) -> - test.ok(err) - test.ok(!loggedIn) + connection.on('debug', (text) -> + #console.log(text) + ) - test.done() +exports.execFailedProc = (test) -> + test.expect(5) + + config = getConfig() + + request = new Request('exec sp_help bad_object_name', (err) -> + test.ok(err) + + connection.close() ) - connection.on('errorMessage', (error) -> - test.ok(error.message.indexOf('failed')) - test.strictEqual(error.number, 18456) + request.on('doneProc', (rowCount, more, returnStatus) -> + test.ok(!more) + test.strictEqual(returnStatus, 1) # Non-zero indicates a failure. ) - connection.on('debug', (message) -> - #console.log(message) + request.on('doneInProc', (rowCount, more) -> + test.ok(more) ) -exports.badServer = (test) -> - connection = new Connection('bad-server', config.userName, config.password, config.options, (err, loggedIn) -> - test.ok(false) + request.on('row', (columns) -> + test.ok(false) ) - connection.on('fatal', (error) -> - test.done() + connection = new Connection(config) + + connection.on('connection', (err) -> + connection.execSql(request) ) - connection.on('debug', (message) -> - #console.log(message) + connection.on('end', (info) -> + test.done() ) -exports.badPort = (test) -> - config.options.port = -1 - config.options.timeout = 50 # Fail fast, to stop the tests taking too long. - connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) -> - test.ok(false) + connection.on('infoMessage', (info) -> + #console.log("#{info.number} : #{info.message}") ) - connection.on('fatal', (error) -> - test.done() + connection.on('errorMessage', (error) -> + #console.log("#{error.number} : #{error.message}") + test.ok(error) ) - connection.on('debug', (message) -> - #console.log(message) + connection.on('debug', (text) -> + #console.log(text) ) diff --git a/test/integration/connection2-test.coffee b/test/integration/connection2-test.coffee deleted file mode 100644 index 20eec0f96..000000000 --- a/test/integration/connection2-test.coffee +++ /dev/null @@ -1,328 +0,0 @@ -Connection = require('../../lib/connection2') -Request = require('../../lib/request') -fs = require('fs') - -getConfig = -> - config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) - - config.options.debug = - packet: true - data: true - payload: true - token: false - log: true - - config - -exports.badServer = (test) -> - config = getConfig() - config.server = 'bad-server' - - connection = new Connection(config) - - connection.on('connection', (err) -> - test.ok(err) - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.badPort = (test) -> - config = getConfig() - config.options.port = -1 - config.options.connectTimeout = 200 - - connection = new Connection(config) - - connection.on('connection', (err) -> - test.ok(err) - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.badCredentials = (test) -> - test.expect(2) - - config = getConfig() - config.password = 'bad-password' - - connection = new Connection(config) - - connection.on('connection', (err) -> - test.ok(err) - - connection.close() - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('errorMessage', (error) -> - #console.log("#{error.number} : #{error.message}") - test.ok(~error.message.indexOf('failed')) - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.connect = (test) -> - test.expect(2) - - config = getConfig() - - connection = new Connection(config) - - connection.on('connection', (err) -> - test.ok(!err) - - connection.close() - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('databaseChange', (database) -> - test.strictEqual(database, config.options.database) - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.execSql = (test) -> - test.expect(8) - - config = getConfig() - - request = new Request('select 8 as C1', (err) -> - test.ok(!err) - - connection.close() - ) - - request.on('done', (rowCount, more) -> - test.ok(!more) - test.strictEqual(rowCount, 1) - ) - - request.on('columnMetadata', (columnsMetadata) -> - test.strictEqual(columnsMetadata.length, 1) - ) - - request.on('row', (columns) -> - test.strictEqual(columns.length, 1) - test.strictEqual(columns[0].value, 8) - test.strictEqual(columns[0].isNull, false) - test.strictEqual(columns.byName().C1.value, 8) - ) - - connection = new Connection(config) - - connection.on('connection', (err) -> - connection.execSql(request) - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.execBadSql = (test) -> - test.expect(4) - - config = getConfig() - - request = new Request('bad syntax here', (err) -> - test.ok(err) - - connection.close() - ) - - request.on('done', (rowCount, more) -> - test.ok(!more) - test.ok(!rowCount) - ) - - connection = new Connection(config) - - connection.on('connection', (err) -> - connection.execSql(request) - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('errorMessage', (error) -> - #console.log("#{error.number} : #{error.message}") - test.ok(error) - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.sqlWithMultipleResultSets = (test) -> - test.expect(9) - - config = getConfig() - row = 0 - - request = new Request('select 1; select 2;', (err) -> - test.ok(!err) - - connection.close() - ) - - request.on('done', (rowCount, more) -> - switch row - when 1 - test.ok(more) - when 2 - test.ok(!more) - test.strictEqual(rowCount, 1) - ) - - request.on('columnMetadata', (columnsMetadata) -> - test.strictEqual(columnsMetadata.length, 1) - ) - - request.on('row', (columns) -> - test.strictEqual(columns[0].value, ++row) - ) - - connection = new Connection(config) - - connection.on('connection', (err) -> - connection.execSql(request) - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.execProc = (test) -> - test.expect(5) - - config = getConfig() - - request = new Request('exec sp_help int', (err) -> - test.ok(!err) - - connection.close() - ) - - request.on('doneProc', (rowCount, more, returnStatus) -> - test.ok(!more) - test.strictEqual(returnStatus, 0) - ) - - request.on('doneInProc', (rowCount, more) -> - test.ok(more) - ) - - request.on('row', (columns) -> - test.ok(true) - ) - - connection = new Connection(config) - - connection.on('connection', (err) -> - connection.execSql(request) - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.execFailedProc = (test) -> - test.expect(5) - - config = getConfig() - - request = new Request('exec sp_help bad_object_name', (err) -> - test.ok(err) - - connection.close() - ) - - request.on('doneProc', (rowCount, more, returnStatus) -> - test.ok(!more) - test.strictEqual(returnStatus, 1) # Non-zero indicates a failure. - ) - - request.on('doneInProc', (rowCount, more) -> - test.ok(more) - ) - - request.on('row', (columns) -> - test.ok(false) - ) - - connection = new Connection(config) - - connection.on('connection', (err) -> - connection.execSql(request) - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('errorMessage', (error) -> - #console.log("#{error.number} : #{error.message}") - test.ok(error) - ) - - connection.on('debug', (text) -> - #console.log(text) - ) diff --git a/test/integration/datatypes-in-results-test.coffee b/test/integration/datatypes-in-results-test.coffee index fa489eefe..7100f5227 100644 --- a/test/integration/datatypes-in-results-test.coffee +++ b/test/integration/datatypes-in-results-test.coffee @@ -1,12 +1,14 @@ -Connection = require('../../lib/connection') +Connection = require('../../lib/connection2') Request = require('../../lib/request') fs = require('fs') config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) config.options.debug = + packet: true data: true payload: true - token: true + token: false + log: true exports.null = (test) -> execSql(test, 'select null', null) @@ -121,11 +123,12 @@ exports.ncharNull = (test) -> execSql = (test, sql, expectedValue) -> - test.expect(3) + test.expect(2) - request = new Request(sql, (err, rowCount) -> + request = new Request(sql, (err) -> test.ok(!err) - test.done() + + connection.close() ) request.on('row', (columns) -> @@ -139,14 +142,18 @@ execSql = (test, sql, expectedValue) -> test.strictEqual(columns[0].value, expectedValue) ) - connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) -> - test.ok(!err) + connection = new Connection(config) + connection.on('connection', (err) -> connection.execSql(request) ) - connection.on('error', (message) -> - console.log(message) + connection.on('end', (info) -> + test.done() + ) + + connection.on('errorMessage', (error) -> + #console.log("#{error.number} : #{error.message}") ) connection.on('debug', (message) -> From 886cfd52499f53322fa244d6aedb314ec7cfa820 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Thu, 5 Jan 2012 21:13:10 +0000 Subject: [PATCH 31/34] Handle socket closed events. --- lib/connection2.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/connection2.coffee b/lib/connection2.coffee index 4a00ae1fe..70f24dbbd 100644 --- a/lib/connection2.coffee +++ b/lib/connection2.coffee @@ -164,6 +164,8 @@ class Connection extends EventEmitter @socket.connect(@config.options.port, @config.server) @socket.on('error', @socketError) @socket.on('connect', @socketConnect) + @socket.on('close', @socketClose) + @socket.on('end', @socketClose) @messageIo = new MessageIO(@socket, @config.options.packetSize, @debug) @messageIo.on('packet', (packet) => @@ -218,6 +220,10 @@ class Connection extends EventEmitter @debug.log("connected to #{@config.server}:#{@config.options.port}") @dispatchEvent('socketConnect') + socketClose: => + @debug.log("connection to #{@config.server}:#{@config.options.port} closed") + @transitionTo(@STATE.FINAL) + sendPreLogin: -> payload = new PreloginPayload() @messageIo.sendMessage(TYPE.PRELOGIN, payload.data) From 4724d39dcd2b19b78911eb9ee159a40e19da8197 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Thu, 5 Jan 2012 21:18:12 +0000 Subject: [PATCH 32/34] Dump the old connection implementation, and only use the new (state machine based) one. --- lib/connection.coffee | 369 ++++++++++-------- lib/connection2.coffee | 280 ------------- lib/tedious.js | 2 +- test/integration/connection-test.coffee | 2 +- .../datatypes-in-results-test.coffee | 2 +- 5 files changed, 220 insertions(+), 435 deletions(-) delete mode 100644 lib/connection2.coffee diff --git a/lib/connection.coffee b/lib/connection.coffee index 2d1954007..70f24dbbd 100644 --- a/lib/connection.coffee +++ b/lib/connection.coffee @@ -9,207 +9,272 @@ MessageIO = require('./message-io') Socket = require('net').Socket TokenStreamParser = require('./token/token-stream-parser').Parser -# s3.2.1 -STATE = - INITIAL: 0, - SENT_PRELOGIN: 1, - SENT_LOGIN7: 2, - LOGGED_IN: 3, - SENT_CLIENT_REQUEST: 4, - SENT_ATTENTION: 5, - FINAL: 6 +# A rather basic state machine for managing a connection. +# Implements something approximating s3.2.1. +KEEP_ALIVE_INITIAL_DELAY = 30 * 1000 +DEFAULT_CONNECT_TIMEOUT = 15 * 1000 +DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000 +DEFAULT_CANCEL_TIMEOUT = 5 * 1000 +DEFAULT_PACKET_SIZE = 4 * 1024 +DEFAULT_PORT = 1433 class Connection extends EventEmitter - constructor: (@server, @userName, @password, @options, callback) -> - @options ||= {} - @options.port ||= 1433 - @options.timeout ||= 10 * 1000 + STATE: + CONNECTING: + name: 'Connecting' + enter: -> + @initialiseConnection() + events: + socketError: (error) -> + @transitionTo(@STATE.FINAL) + connectTimeout: -> + @transitionTo(@STATE.FINAL) + socketConnect: -> + @sendPreLogin() + @transitionTo(@STATE.SENT_PRELOGIN) + + SENT_PRELOGIN: + name: 'SentPrelogin' + enter: -> + @emptyMessageBuffer() + events: + packet: (packet) -> + @addToMessageBuffer(packet) + message: -> + @processPreLoginResponse() + @sendLogin7Packet() + @transitionTo(@STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN) + SENT_LOGIN7_WITH_STANDARD_LOGIN: + name: 'SentLogin7WithStandardLogin' + events: + packet: (packet) -> + @sendPacketToTokenStreamParser(packet) + message: -> + @processLogin7Response() + LOGGED_IN: + name: 'LoggedIn' + SENT_CLIENT_REQUEST: + name: 'SentClientRequest' + events: + packet: (packet) -> + @sendPacketToTokenStreamParser(packet) + message: -> + @sqlRequest.callback(@sqlRequest.error) + @sqlRequest = undefined + FINAL: + name: 'Final' + enter: -> + @cleanupConnection() + + constructor: (@config) -> + @defaultConfig() + @createDebug() + @createTokenStreamParser() - @loggedIn = false - @state = STATE.INITIAL + @transitionTo(@STATE.CONNECTING) - @debug = new Debug(@options.debug) + close: -> + @transitionTo(@STATE.FINAL) + + initialiseConnection: -> + @connect() + @createConnectTimer() + + cleanupConnection: -> + if !@closed + @clearConnectTimer() + @closeConnection() + @emit('end') + @closed = true + + defaultConfig: -> + @config.options ||= {} + @config.options.port ||= DEFAULT_PORT + @config.options.connectTimeout ||= DEFAULT_CONNECT_TIMEOUT + @config.options.requestTimeout ||= DEFAULT_CLIENT_REQUEST_TIMEOUT + @config.options.cancelTimeout ||= DEFAULT_CANCEL_TIMEOUT + @config.options.packetSize ||= DEFAULT_PACKET_SIZE + + createDebug: -> + @debug = new Debug(@config.options.debug) @debug.on('debug', (message) => - if @state != STATE.FINAL @emit('debug', message) ) - @messagePayloadBuffer = new Buffer(0) - @createTokenStreamParser() - - @connection = new Socket({}) - @connection.setTimeout(options.timeout) - @connection.connect(@options.port, @server) - - @connection.addListener('close', @eventClose) - @connection.addListener('connect', @eventConnect) - @connection.addListener('end', @eventEnd) - @connection.addListener('error', @eventError) - @connection.addListener('timeout', @eventTimeout) - - @messageIo = new MessageIO(@connection, 4096, @debug) - @messageIo.on('packet', @eventPacket) - - @startRequest('connect/login', callback) - - @packetBuffer = new Buffer(0) - - execSql: (request) -> - @sqlRequest = request - @startRequest('execSql', request.callback) - - payload = new SqlBatchPayload(request.sqlText) - - @state = STATE.SENT_CLIENT_REQUEST - @messageIo.sendMessage(TYPE.SQL_BATCH, payload.data) - @debug.payload(payload.toString(' ')) - - eventClose: (hadError) => - @emit('closed') - @debug.log("connection close, hadError:#{hadError}") - - eventConnect: => - @debug.log('connected') - @sendPreLoginPacket() - - eventEnd: => - @debug.log('end') - - eventError: (exception) => - @fatalError(exception) - @connection.destroy() - - eventTimeout: => - @emit('timeout') - @fatalError('timeout') - @connection.destroy() - - eventPacket: (packet) => - switch @state - when STATE.SENT_PRELOGIN - @buildMessage(packet, @processPreloginResponse) - when STATE.SENT_LOGIN7, STATE.SENT_CLIENT_REQUEST - @tokenStreamParser.addBuffer(packet.data()) - else - @fatalError("Unexpected packet in state #{@state}: packet type #{packet.type()}") - - # Accumulates packet payloads into a buffer until all of the packets - # for a message have been received. - # - # Only used during some states, when we want to process the complete message. - # For other states the payloads are processed for each packet as they arrive. - buildMessage: (packet, payloadProcessFunction) -> - @messagePayloadBuffer = new Buffer(@messagePayloadBuffer.concat(packet.data())) - - if (packet.isLast()) - payloadProcessFunction.call(@) - @messagePayloadBuffer = new Buffer(0) - - processPreloginResponse: -> - preloginPayload = new PreloginPayload(@messagePayloadBuffer) - @debug.payload(preloginPayload.toString(' ')) - - @sendLogin7Packet() - - # s2.2.2.2 createTokenStreamParser: -> @tokenStreamParser = new TokenStreamParser(@debug) - - @tokenStreamParser.on('loginack', (token) => - @loggedIn = true - ) @tokenStreamParser.on('infoMessage', (token) => @emit('infoMessage', token) ) @tokenStreamParser.on('errorMessage', (token) => - @activeRequest.error = token.message @emit('errorMessage', token) - ) - @tokenStreamParser.on('packetSizeChange', (token) => - @messageIo.packetSize(token.newValue) + if @sqlRequest + @sqlRequest.error = token.message ) @tokenStreamParser.on('databaseChange', (token) => - @_database = token.newValue - @emit('databaseChange', @_database) + @emit('databaseChange', token.newValue) ) @tokenStreamParser.on('languageChange', (token) => - @_language = token.newValue - @emit('languageChange', @_language) + @emit('languageChange', token.newValue) ) @tokenStreamParser.on('charsetChange', (token) => - @_charset = token.newValue - @emit('charsetChange', @_charset) + @emit('charsetChange', token.newValue) + ) + @tokenStreamParser.on('loginack', (token) => + @loggedIn = true + ) + @tokenStreamParser.on('packetSizeChange', (token) => + @messageIo.packetSize(token.newValue) ) @tokenStreamParser.on('columnMetadata', (token) => - @sqlRequest.emit('columnMetadata', token.columns) + if @sqlRequest + @sqlRequest.emit('columnMetadata', token.columns) + else + throw new Error("Received 'columnMetadata' when no sqlRequest is in progress") ) @tokenStreamParser.on('row', (token) => - @sqlRequest.emit('row', token.columns) + if @sqlRequest + @sqlRequest.emit('row', token.columns) + else + throw new Error("Received 'row' when no sqlRequest is in progress") ) @tokenStreamParser.on('returnStatus', (token) => - @procReturnStatusValue = token.value + if @sqlRequest + # Keep value for passing in 'doneProc' event. + @procReturnStatusValue = token.value + ) + @tokenStreamParser.on('doneProc', (token) => + if @sqlRequest + @sqlRequest.emit('doneProc', token.rowCount, token.more, @procReturnStatusValue) + @procReturnStatusValue = undefined + ) + @tokenStreamParser.on('doneInProc', (token) => + if @sqlRequest + @sqlRequest.emit('doneInProc', token.rowCount, token.more) ) @tokenStreamParser.on('done', (token) => - state = @state - - if @loggedIn - @state = STATE.LOGGED_IN + if @sqlRequest + @sqlRequest.emit('done', token.rowCount, token.more) + ) - if state == STATE.SENT_LOGIN7 - @activeRequest.callback(@activeRequest.error, @loggedIn) - else - @activeRequest.callback(@activeRequest.error, token.rowCount) + connect: -> + @socket = new Socket({}) + @socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) + @socket.connect(@config.options.port, @config.server) + @socket.on('error', @socketError) + @socket.on('connect', @socketConnect) + @socket.on('close', @socketClose) + @socket.on('end', @socketClose) + + @messageIo = new MessageIO(@socket, @config.options.packetSize, @debug) + @messageIo.on('packet', (packet) => + @dispatchEvent('packet', packet) ) - @tokenStreamParser.on('doneProc', (token) => - @state = STATE.LOGGED_IN - @activeRequest.callback(@activeRequest.error, @procReturnStatusValue) - @procReturnStatusValue = undefined + @messageIo.on('message', => + @dispatchEvent('message') ) - startRequest: (requestName, callback) => - @activeRequest = - requestName: requestName - info: - infos: [] - errors: [] - envChanges: [] - callback: callback + closeConnection: -> + @socket.destroy() + + createConnectTimer: -> + @connectTimer = setTimeout(@connectTimeout, @config.options.connectTimeout) + + connectTimeout: => + message = "timeout : failed to connect to #{@config.server}:#{@config.options.port} in #{@config.options.connectTimeout}ms" + + @debug.log(message) + @emit('connection', message) + @connectTimer = undefined + @dispatchEvent('connectTimeout') + + clearConnectTimer: -> + if @connectTimer + clearTimeout(@connectTimer) - sendPreLoginPacket: -> + transitionTo: (newState) -> + if @state?.exit + @state.exit.apply(@) + + @debug.log("State change: #{@state?.name} -> #{newState.name}") + @state = newState + + if @state.enter + @state.enter.apply(@) + + dispatchEvent: (eventName, args...) -> + if @state.events && @state.events.hasOwnProperty(eventName) + eventFunction = @state.events[eventName].apply(@, args) + else + throw new Error("No event '#{eventName}' in state '#{@state.name}'") + + socketError: (error) => + message = "connection to #{@config.server}:#{@config.options.port} failed" + + @debug.log(message) + @emit('connection', message) + @dispatchEvent('socketError', error) + + socketConnect: => + @debug.log("connected to #{@config.server}:#{@config.options.port}") + @dispatchEvent('socketConnect') + + socketClose: => + @debug.log("connection to #{@config.server}:#{@config.options.port} closed") + @transitionTo(@STATE.FINAL) + + sendPreLogin: -> payload = new PreloginPayload() @messageIo.sendMessage(TYPE.PRELOGIN, payload.data) @debug.payload(payload.toString(' ')) - @state = STATE.SENT_PRELOGIN + + emptyMessageBuffer: -> + @messageBuffer = new Buffer(0) + + addToMessageBuffer: (packet) -> + @messageBuffer = @messageBuffer.concat(packet.data()) + + processPreLoginResponse: -> + preloginPayload = new PreloginPayload(@messageBuffer) + @debug.payload(preloginPayload.toString(' ')) sendLogin7Packet: -> loginData = - userName: @userName, - password: @password, - database: @options.database - packetSize: 4096 + userName: @config.userName + password: @config.password + database: @config.options.database + packetSize: @config.options.packetSize payload = new Login7Payload(loginData) @messageIo.sendMessage(TYPE.LOGIN7, payload.data) @debug.payload(payload.toString(' ')) - @state = STATE.SENT_LOGIN7 - fatalError: (message) -> - @debug.log("FATAL ERROR: #{message}") - @close() - @emit('fatal', message) + sendPacketToTokenStreamParser: (packet) -> + @tokenStreamParser.addBuffer(packet.data()) - database: -> - @_database + processLogin7Response: -> + if @loggedIn + @clearConnectTimer() + @transitionTo(@STATE.LOGGED_IN) + @emit('connection') + else + @emit('connection', 'Login failed; one or more errorMessage events should have been emitted') + @transitionTo(@STATE.FINAL) - language: -> - @_language + execSql: (request) -> + if @state != @STATE.LOGGED_IN + message = "Invalid state; requests can only be made in the #{@STATE.LOGGED_IN.name} state, not the #{@state.name} state" - charset: -> - @_charset + @debug.log(message) + request.callback(message) + else + @sqlRequest = request - close: -> - @connection.end() - @state = STATE.FINAL + payload = new SqlBatchPayload(request.sqlText) + @messageIo.sendMessage(TYPE.SQL_BATCH, payload.data) + @debug.payload(payload.toString(' ')) + + @transitionTo(@STATE.SENT_CLIENT_REQUEST) module.exports = Connection diff --git a/lib/connection2.coffee b/lib/connection2.coffee deleted file mode 100644 index 70f24dbbd..000000000 --- a/lib/connection2.coffee +++ /dev/null @@ -1,280 +0,0 @@ -require('./buffertools') -Debug = require('./debug') -EventEmitter = require('events').EventEmitter -TYPE = require('./packet').TYPE -PreloginPayload = require('./prelogin-payload') -Login7Payload = require('./login7-payload') -SqlBatchPayload = require('./sqlbatch-payload') -MessageIO = require('./message-io') -Socket = require('net').Socket -TokenStreamParser = require('./token/token-stream-parser').Parser - -# A rather basic state machine for managing a connection. -# Implements something approximating s3.2.1. - -KEEP_ALIVE_INITIAL_DELAY = 30 * 1000 -DEFAULT_CONNECT_TIMEOUT = 15 * 1000 -DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000 -DEFAULT_CANCEL_TIMEOUT = 5 * 1000 -DEFAULT_PACKET_SIZE = 4 * 1024 -DEFAULT_PORT = 1433 - -class Connection extends EventEmitter - STATE: - CONNECTING: - name: 'Connecting' - enter: -> - @initialiseConnection() - events: - socketError: (error) -> - @transitionTo(@STATE.FINAL) - connectTimeout: -> - @transitionTo(@STATE.FINAL) - socketConnect: -> - @sendPreLogin() - @transitionTo(@STATE.SENT_PRELOGIN) - - SENT_PRELOGIN: - name: 'SentPrelogin' - enter: -> - @emptyMessageBuffer() - events: - packet: (packet) -> - @addToMessageBuffer(packet) - message: -> - @processPreLoginResponse() - @sendLogin7Packet() - @transitionTo(@STATE.SENT_LOGIN7_WITH_STANDARD_LOGIN) - SENT_LOGIN7_WITH_STANDARD_LOGIN: - name: 'SentLogin7WithStandardLogin' - events: - packet: (packet) -> - @sendPacketToTokenStreamParser(packet) - message: -> - @processLogin7Response() - LOGGED_IN: - name: 'LoggedIn' - SENT_CLIENT_REQUEST: - name: 'SentClientRequest' - events: - packet: (packet) -> - @sendPacketToTokenStreamParser(packet) - message: -> - @sqlRequest.callback(@sqlRequest.error) - @sqlRequest = undefined - FINAL: - name: 'Final' - enter: -> - @cleanupConnection() - - constructor: (@config) -> - @defaultConfig() - @createDebug() - @createTokenStreamParser() - - @transitionTo(@STATE.CONNECTING) - - close: -> - @transitionTo(@STATE.FINAL) - - initialiseConnection: -> - @connect() - @createConnectTimer() - - cleanupConnection: -> - if !@closed - @clearConnectTimer() - @closeConnection() - @emit('end') - @closed = true - - defaultConfig: -> - @config.options ||= {} - @config.options.port ||= DEFAULT_PORT - @config.options.connectTimeout ||= DEFAULT_CONNECT_TIMEOUT - @config.options.requestTimeout ||= DEFAULT_CLIENT_REQUEST_TIMEOUT - @config.options.cancelTimeout ||= DEFAULT_CANCEL_TIMEOUT - @config.options.packetSize ||= DEFAULT_PACKET_SIZE - - createDebug: -> - @debug = new Debug(@config.options.debug) - @debug.on('debug', (message) => - @emit('debug', message) - ) - - createTokenStreamParser: -> - @tokenStreamParser = new TokenStreamParser(@debug) - @tokenStreamParser.on('infoMessage', (token) => - @emit('infoMessage', token) - ) - @tokenStreamParser.on('errorMessage', (token) => - @emit('errorMessage', token) - if @sqlRequest - @sqlRequest.error = token.message - ) - @tokenStreamParser.on('databaseChange', (token) => - @emit('databaseChange', token.newValue) - ) - @tokenStreamParser.on('languageChange', (token) => - @emit('languageChange', token.newValue) - ) - @tokenStreamParser.on('charsetChange', (token) => - @emit('charsetChange', token.newValue) - ) - @tokenStreamParser.on('loginack', (token) => - @loggedIn = true - ) - @tokenStreamParser.on('packetSizeChange', (token) => - @messageIo.packetSize(token.newValue) - ) - @tokenStreamParser.on('columnMetadata', (token) => - if @sqlRequest - @sqlRequest.emit('columnMetadata', token.columns) - else - throw new Error("Received 'columnMetadata' when no sqlRequest is in progress") - ) - @tokenStreamParser.on('row', (token) => - if @sqlRequest - @sqlRequest.emit('row', token.columns) - else - throw new Error("Received 'row' when no sqlRequest is in progress") - ) - @tokenStreamParser.on('returnStatus', (token) => - if @sqlRequest - # Keep value for passing in 'doneProc' event. - @procReturnStatusValue = token.value - ) - @tokenStreamParser.on('doneProc', (token) => - if @sqlRequest - @sqlRequest.emit('doneProc', token.rowCount, token.more, @procReturnStatusValue) - @procReturnStatusValue = undefined - ) - @tokenStreamParser.on('doneInProc', (token) => - if @sqlRequest - @sqlRequest.emit('doneInProc', token.rowCount, token.more) - ) - @tokenStreamParser.on('done', (token) => - if @sqlRequest - @sqlRequest.emit('done', token.rowCount, token.more) - ) - - connect: -> - @socket = new Socket({}) - @socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) - @socket.connect(@config.options.port, @config.server) - @socket.on('error', @socketError) - @socket.on('connect', @socketConnect) - @socket.on('close', @socketClose) - @socket.on('end', @socketClose) - - @messageIo = new MessageIO(@socket, @config.options.packetSize, @debug) - @messageIo.on('packet', (packet) => - @dispatchEvent('packet', packet) - ) - @messageIo.on('message', => - @dispatchEvent('message') - ) - - closeConnection: -> - @socket.destroy() - - createConnectTimer: -> - @connectTimer = setTimeout(@connectTimeout, @config.options.connectTimeout) - - connectTimeout: => - message = "timeout : failed to connect to #{@config.server}:#{@config.options.port} in #{@config.options.connectTimeout}ms" - - @debug.log(message) - @emit('connection', message) - @connectTimer = undefined - @dispatchEvent('connectTimeout') - - clearConnectTimer: -> - if @connectTimer - clearTimeout(@connectTimer) - - transitionTo: (newState) -> - if @state?.exit - @state.exit.apply(@) - - @debug.log("State change: #{@state?.name} -> #{newState.name}") - @state = newState - - if @state.enter - @state.enter.apply(@) - - dispatchEvent: (eventName, args...) -> - if @state.events && @state.events.hasOwnProperty(eventName) - eventFunction = @state.events[eventName].apply(@, args) - else - throw new Error("No event '#{eventName}' in state '#{@state.name}'") - - socketError: (error) => - message = "connection to #{@config.server}:#{@config.options.port} failed" - - @debug.log(message) - @emit('connection', message) - @dispatchEvent('socketError', error) - - socketConnect: => - @debug.log("connected to #{@config.server}:#{@config.options.port}") - @dispatchEvent('socketConnect') - - socketClose: => - @debug.log("connection to #{@config.server}:#{@config.options.port} closed") - @transitionTo(@STATE.FINAL) - - sendPreLogin: -> - payload = new PreloginPayload() - @messageIo.sendMessage(TYPE.PRELOGIN, payload.data) - @debug.payload(payload.toString(' ')) - - emptyMessageBuffer: -> - @messageBuffer = new Buffer(0) - - addToMessageBuffer: (packet) -> - @messageBuffer = @messageBuffer.concat(packet.data()) - - processPreLoginResponse: -> - preloginPayload = new PreloginPayload(@messageBuffer) - @debug.payload(preloginPayload.toString(' ')) - - sendLogin7Packet: -> - loginData = - userName: @config.userName - password: @config.password - database: @config.options.database - packetSize: @config.options.packetSize - - payload = new Login7Payload(loginData) - @messageIo.sendMessage(TYPE.LOGIN7, payload.data) - @debug.payload(payload.toString(' ')) - - sendPacketToTokenStreamParser: (packet) -> - @tokenStreamParser.addBuffer(packet.data()) - - processLogin7Response: -> - if @loggedIn - @clearConnectTimer() - @transitionTo(@STATE.LOGGED_IN) - @emit('connection') - else - @emit('connection', 'Login failed; one or more errorMessage events should have been emitted') - @transitionTo(@STATE.FINAL) - - execSql: (request) -> - if @state != @STATE.LOGGED_IN - message = "Invalid state; requests can only be made in the #{@STATE.LOGGED_IN.name} state, not the #{@state.name} state" - - @debug.log(message) - request.callback(message) - else - @sqlRequest = request - - payload = new SqlBatchPayload(request.sqlText) - @messageIo.sendMessage(TYPE.SQL_BATCH, payload.data) - @debug.payload(payload.toString(' ')) - - @transitionTo(@STATE.SENT_CLIENT_REQUEST) - -module.exports = Connection diff --git a/lib/tedious.js b/lib/tedious.js index cfb0e7f45..38648fd12 100644 --- a/lib/tedious.js +++ b/lib/tedious.js @@ -2,6 +2,6 @@ require('coffee-script') exports.statemachineLogLevel = 0 -exports.Connection = require('./connection2') +exports.Connection = require('./connection') exports.Request = require('./request') exports.library = require('./library') diff --git a/test/integration/connection-test.coffee b/test/integration/connection-test.coffee index 20eec0f96..fd228d775 100644 --- a/test/integration/connection-test.coffee +++ b/test/integration/connection-test.coffee @@ -1,4 +1,4 @@ -Connection = require('../../lib/connection2') +Connection = require('../../lib/connection') Request = require('../../lib/request') fs = require('fs') diff --git a/test/integration/datatypes-in-results-test.coffee b/test/integration/datatypes-in-results-test.coffee index 7100f5227..6b94b8811 100644 --- a/test/integration/datatypes-in-results-test.coffee +++ b/test/integration/datatypes-in-results-test.coffee @@ -1,4 +1,4 @@ -Connection = require('../../lib/connection2') +Connection = require('../../lib/connection') Request = require('../../lib/request') fs = require('fs') From 5b9f6c00f2405aee6f7cb886ebc8d167da0c0ab0 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Thu, 5 Jan 2012 21:22:56 +0000 Subject: [PATCH 33/34] Remove ignite (state machine package) and references to it. --- lib/connection/connection-factory.coffee | 23 -- lib/connection/connection-statemachine.coffee | 228 ------------------ package.json | 1 - scripts/statemachine-generate-diagram | 15 -- 4 files changed, 267 deletions(-) delete mode 100644 lib/connection/connection-factory.coffee delete mode 100644 lib/connection/connection-statemachine.coffee delete mode 100755 scripts/statemachine-generate-diagram diff --git a/lib/connection/connection-factory.coffee b/lib/connection/connection-factory.coffee deleted file mode 100644 index 17ff2413f..000000000 --- a/lib/connection/connection-factory.coffee +++ /dev/null @@ -1,23 +0,0 @@ -EventEmitter = require('events').EventEmitter -ignite = require('ignite') -connectionGF = require('../../lib/connection/connection-statemachine') - -class ConnectionFactory - constructor: () -> - imports = {} - options = - logLevel: require('../tedious').statemachineLogLevel - - @factory = new ignite.Factory(connectionGF, imports, options) - - createConnection: (config) -> - new Connection(@factory, config) - -class Connection extends EventEmitter - constructor: (factory, config) -> - factory.spawn(@, config) - - close: -> - @emit('close') - -module.exports = ConnectionFactory diff --git a/lib/connection/connection-statemachine.coffee b/lib/connection/connection-statemachine.coffee deleted file mode 100644 index f953549f9..000000000 --- a/lib/connection/connection-statemachine.coffee +++ /dev/null @@ -1,228 +0,0 @@ -Socket = require('net').Socket -Debug = require('../debug') -TYPE = require('../packet').TYPE -PreloginPayload = require('../prelogin-payload') -Login7Payload = require('../login7-payload') -MessageIO = require('../message-io') -TokenStreamParser = require('../token/token-stream-parser').Parser - -KEEP_ALIVE_INITIAL_DELAY = 30 * 1000 -DEFAULT_CONNECT_TIMEOUT = 15 * 1000 -DEFAULT_CLIENT_REQUEST_TIMEOUT = 15 * 1000 -DEFAULT_CANCEL_TIMEOUT = 5 * 1000 -DEFAULT_PACKET_SIZE = 4 * 1024 - -connectionStateMachine = (fire, client, config) -> - # Used in diagram. - @name = 'Connection - State Machine' - - # All global connection state is mantained in this object. - connection = {} - - @startState = 'Connecting' - - @defaults = - actions: - 'tokenStream.infoMessage': (token) -> - client.emit('infoMessage', token) - null - - 'tokenStream.errorMessage': (token) -> - client.emit('errorMessage', token) - null - - 'tokenStream.databaseChange': (token) -> - client.emit('databaseChange', token.newValue) - null - - 'tokenStream.languageChange': (token) -> - client.emit('languageChange', token.newValue) - null - - 'tokenStream.charsetChange': (token) -> - client.emit('charsetChange', token.newValue) - null - - 'client.close': -> - if (!connection.closed) - 'Final' - else - null - - @states = - Connecting: - entry: -> - defaultConfig() - createDebug() - createTokenStreamParser() - connect() - createConnectTimer() - - fire.$regEmitter('client', client, true); - fire.$regEmitter('socket', connection.socket, true); - fire.$regEmitter('messageIo', connection.messageIo, true); - fire.$regEmitter('tokenStream', connection.tokenStreamParser, true); - - null - - actions: - 'socket.connect': -> - sendPreLogin() - 'SentPrelogin' - - 'socket.error': -> - client.emit('connection', "failed to connect") - 'Final' - - 'connectTimeout': -> - connectTimeout() - 'Final' - - #'socket.error': '@error' - - SentPrelogin: -> - responseBuffer = new Buffer(0) - - actions: - 'connectTimeout': -> - connectTimeout() - 'Final' - - 'messageIo.packet': (packet) -> - responseBuffer = responseBuffer.concat(packet.data()) - null - - 'messageIo.message': -> - preloginPayload = new PreloginPayload(responseBuffer) - connection.debug.payload(preloginPayload.toString(' ')) - - sendLogin7Packet() - 'SentLogin7WithStandardLogin' - - ### - SentTlsNegotiation: - entry: -> - console.log('sent tls neg') - ### - - SentLogin7WithStandardLogin: -> - loggedIn = false - - actions: - 'connectTimeout': -> - connectTimeout() - 'Final' - - 'messageIo.packet': (packet) -> - connection.tokenStreamParser.addBuffer(packet.data()) - null - - 'messageIo.message': -> - if loggedIn - clearConnectTimer() - client.emit('connection') - 'LoggedIn' - else - client.emit('connection', 'Login failed; one or more errorMessage events should have been emitted') - 'Final' - - 'tokenStream.loginack': (token) -> - loggedIn = true - null - - 'tokenStream.packetSizeChange': (token) -> - connection.messageIo.packetSize(token.newValue) - null - - LoggedIn: - entry: -> - #console.log('logged in') - null - - ### - SentLogin7WithSpNego: - entry: -> - console.log('sent l7 with spnego') - - SentClientRequest: - entry: -> - console.log('sent client request') - - SentAttention: - entry: -> - console.log('sent attention') - - RoutingComplete: - entry: -> - console.log('routing complete') - ### - - Final: - entry: -> - if !connection.closed - connection.closed = true - - clearConnectTimer() - - if connection.socket - connection.socket.destroy() - - client.emit('end') - - '@exit' - - defaultConfig = -> - config.options ||= {} - config.options.port ||= 1433 - config.options.connectTimeout ||= DEFAULT_CONNECT_TIMEOUT - config.options.requestTimeout ||= DEFAULT_CLIENT_REQUEST_TIMEOUT - config.options.cancelTimeout ||= DEFAULT_CANCEL_TIMEOUT - config.options.packetSize ||= DEFAULT_PACKET_SIZE - - createDebug = -> - connection.debug = new Debug(config.options.debug) - connection.debug.on('debug', (message) -> - client.emit('debug', message) - ) - - createTokenStreamParser = -> - connection.tokenStreamParser = new TokenStreamParser(connection.debug) - - connect = -> - connection.socket = new Socket({}) - connection.socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY) - connection.socket.connect(config.options.port, config.server) - - connection.socket.on('error', (error) -> - # Need this listener, or else the error actions are not fired. Weird. - ) - - connection.messageIo = new MessageIO(connection.socket, config.options.packetSize, connection.debug) - - createConnectTimer = -> - connection.connectTimer = setTimeout(fire.$cb('connectTimeout'), config.options.connectTimeout) - - connectTimeout = -> - client.emit('connection', "timeout : failed to connect in #{config.options.connectTimeout}ms") - - clearConnectTimer = -> - if connection.connectTimer - clearTimeout(connection.connectTimer) - - sendPreLogin = -> - payload = new PreloginPayload() - connection.messageIo.sendMessage(TYPE.PRELOGIN, payload.data) - connection.debug.payload(payload.toString(' ')) - - sendLogin7Packet = -> - loginData = - userName: config.userName - password: config.password - database: config.options.database - packetSize: config.options.packetSize - - payload = new Login7Payload(loginData) - connection.messageIo.sendMessage(TYPE.LOGIN7, payload.data) - connection.debug.payload(payload.toString(' ')) - -module.exports = connectionStateMachine diff --git a/package.json b/package.json index 784d3b8b8..a334026b1 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ }, "dependencies": { "coffee-script": "1.1.3", - "ignite": "0.1.5", "sprintf": "0.1.1" }, "devDependencies": { diff --git a/scripts/statemachine-generate-diagram b/scripts/statemachine-generate-diagram deleted file mode 100755 index 102768bae..000000000 --- a/scripts/statemachine-generate-diagram +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -COFFEE=node_modules/.bin/coffee -IGNITE=node_modules/.bin/ignite - -GENERATED_DIR=generated -COFFEE_OUTPUT_DIR=coffee/lib - -DIAGRAM_FILE=$GENERATED_DIR/connection-statemachine.png -STATEMACHINE_FILE=$COFFEE_OUTPUT_DIR/connection/connection-statemachine.js - -mkdir -p $GENERATED_DIR - -$COFFEE -b -o $COFFEE_OUTPUT_DIR lib/ -$IGNITE -m draw -T png --level 1 -o $DIAGRAM_FILE $STATEMACHINE_FILE \ No newline at end of file From 511c6415cb4a38b923e94c221e3d1f8a1e5083f5 Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Thu, 5 Jan 2012 21:25:54 +0000 Subject: [PATCH 34/34] Remove use of ignite. --- .../connection-factory-test.coffee | 107 ------------------ 1 file changed, 107 deletions(-) delete mode 100644 test/integration/connection-factory-test.coffee diff --git a/test/integration/connection-factory-test.coffee b/test/integration/connection-factory-test.coffee deleted file mode 100644 index 3cca94032..000000000 --- a/test/integration/connection-factory-test.coffee +++ /dev/null @@ -1,107 +0,0 @@ -ConnectionFactory = require('../../lib/connection/connection-factory') -fs = require('fs') - -#require('../../lib/tedious').statemachineLogLevel = 8 - -connectionFactory = new ConnectionFactory() - -getConfig = -> - config = JSON.parse(fs.readFileSync(process.env.HOME + '/.tedious/test-connection.json', 'utf8')) - - config.options.debug = - packet: true - data: true - payload: true - token: false - log: true - - config - -exports.badServer = (test) -> - config = getConfig() - config.server = 'bad-server' - - connection = connectionFactory.createConnection(config) - - connection.on('connection', (err) -> - test.ok(err) - ) - - connection.on('end', (info) -> - test.done() - ) - -exports.badPort = (test) -> - config = getConfig() - config.options.port = -1 - config.options.connectTimeout = 200 - - connection = connectionFactory.createConnection(config) - - connection.on('connection', (err) -> - test.ok(err) - ) - - connection.on('end', (info) -> - test.done() - ) - -exports.badCredentials = (test) -> - test.expect(2) - - config = getConfig() - config.password = 'bad-password' - - connection = connectionFactory.createConnection(config) - - connection.on('connection', (err) -> - test.ok(err) - - connection.close() - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('errorMessage', (error) -> - #console.log("#{error.number} : #{error.message}") - test.ok(~error.message.indexOf('failed')) - ) - - connection.on('debug', (text) -> - #console.log(text) - ) - -exports.connect = (test) -> - test.expect(2) - - config = getConfig() - - connection = connectionFactory.createConnection(config) - - connection.on('connection', (err) -> - test.ok(!err) - - connection.close() - ) - - connection.on('end', (info) -> - test.done() - ) - - connection.on('databaseChange', (database) -> - test.strictEqual(database, config.options.database) - ) - - connection.on('infoMessage', (info) -> - #console.log("#{info.number} : #{info.message}") - ) - - connection.on('debug', (text) -> - #console.log(text) - )