Permalink
Browse files

Add request cancelation

  • Loading branch information...
patriksimek committed Mar 11, 2014
1 parent cda20e4 commit 0326a5bbea0e071f30d0e7c50d60739d8d2086b8
Showing with 100 additions and 5 deletions.
  1. +1 −0 README.md
  2. +32 −1 src/connection.coffee
  3. +6 −1 src/message-io.coffee
  4. +4 −2 src/packet.coffee
  5. +3 −0 src/request.coffee
  6. +54 −1 test/integration/connection-test.coffee
View
@@ -19,6 +19,7 @@ Current version: 0.1.5
### Coming soon in 0.2.0
- Added support for TDS 7.4
+- Added request cancelation
- Added support for UDT, Time, Date, DateTime2 and DateTimeOffset data types
- Added option to choose whether to pass/receive times in UTC or local time (`useUTC`)
- Binary, VarBinary and Image are now supported as input parameters
View
@@ -132,6 +132,23 @@ class Connection extends EventEmitter
@request = undefined
sqlRequest.callback(sqlRequest.error, sqlRequest.rowCount, sqlRequest.rows)
+ SENT_ATTENTION:
+ name: 'SentAttention'
+ events:
+ socketError: (error) ->
+ @transitionTo(@STATE.FINAL)
+ data: (data) ->
+ @sendDataToTokenStreamParser(data)
+ message: ->
+ if @request.canceled
+ @transitionTo(@STATE.LOGGED_IN)
+
+ sqlRequest = @request
+ @request = undefined
+ sqlRequest.callback(RequestError("Canceled.", 'ECANCEL'))
+
+ # else: skip all messages, now we are only interested about cancel acknowledgement (2.2.1.6)
+
FINAL:
name: 'Final'
enter: ->
@@ -307,12 +324,15 @@ class Connection extends EventEmitter
@tokenStreamParser.on('done', (token) =>
if @request
@request.emit('done', token.rowCount, token.more, @request.rows)
-
+
if token.rowCount != undefined
@request.rowCount += token.rowCount
if @config.options.rowCollectionOnDone
@request.rows = []
+
+ if token.attention
+ @request.canceled = true
)
@tokenStreamParser.on('resetConnection', (token) =>
@emit('resetConnection')
@@ -576,6 +596,17 @@ set transaction isolation level read committed'''
)
@transitionTo(@STATE.SENT_CLIENT_REQUEST)
+
+ cancel: ->
+ if @state != @STATE.SENT_CLIENT_REQUEST
+ message = "Requests can only be canceled in the #{@STATE.SENT_CLIENT_REQUEST.name} state, not the #{@state.name} state"
+
+ @debug.log(message)
+ false
+ else
+ @messageIo.sendMessage(TYPE.ATTENTION)
+ @transitionTo(@STATE.SENT_ATTENTION)
+ true
reset: (callback) =>
request = new Request(@getInitialSql(), (err, rowCount, rows) ->
View
@@ -63,8 +63,13 @@ class MessageIO extends EventEmitter
@tlsNegotiationInProgress = false;
# TODO listen for 'drain' event when socket.write returns false.
+ # TODO implement incomplete request cancelation (2.2.1.6)
sendMessage: (packetType, data, resetConnection) ->
- numberOfPackets = (Math.floor((data.length - 1) / @packetDataSize)) + 1
+ if data

This comment has been minimized.

Show comment Hide comment
@arthurschreiber

arthurschreiber Mar 11, 2014

Collaborator

Looking at this again now, couldn't we also do:

data ||= new Buffer 0
numberOfPackets = (Math.floor((data.length - 1) / @packetDataSize)) + 1
@arthurschreiber

arthurschreiber Mar 11, 2014

Collaborator

Looking at this again now, couldn't we also do:

data ||= new Buffer 0
numberOfPackets = (Math.floor((data.length - 1) / @packetDataSize)) + 1

This comment has been minimized.

Show comment Hide comment
@patriksimek

patriksimek Mar 11, 2014

Collaborator

I'm affraid we could not.

(Math.floor((0 - 1) / 4096)) + 1 == 0
@patriksimek

patriksimek Mar 11, 2014

Collaborator

I'm affraid we could not.

(Math.floor((0 - 1) / 4096)) + 1 == 0
+ numberOfPackets = (Math.floor((data.length - 1) / @packetDataSize)) + 1
+ else
+ numberOfPackets = 1
+ data = new Buffer 0
for packetNumber in [0..numberOfPackets - 1]
payloadStart = packetNumber * @packetDataSize
View
@@ -7,6 +7,7 @@ TYPE =
SQL_BATCH: 0x01
RPC_REQUEST: 0x03
TABULAR_RESULT: 0x04
+ ATTENTION: 0x06
TRANSACTION_MANAGER: 0x0E
LOGIN7: 0x10
PRELOGIN: 0x12
@@ -146,8 +147,9 @@ class Packet
chars += ' '
else
chars += String.fromCharCode(data[offset])
-
- dataDump += sprintf('%02X', data[offset]);
+
+ if data[offset]?
+ dataDump += sprintf('%02X', data[offset]);
if ((offset + 1) % BYTES_PER_GROUP == 0) && !((offset + 1) % BYTES_PER_LINE == 0)
# Inter-group space.
View
@@ -4,6 +4,9 @@ TYPES = require('./data-type').typeByName
{RequestError} = require('./errors')
class Request extends EventEmitter
+ error: null
+ canceled: false
+
constructor: (@sqlTextOrProcedure, @callback) ->
@parameters = []
@parametersByName = {}
@@ -11,7 +11,7 @@ getConfig = ->
packet: true
data: true
payload: true
- token: false
+ token: true
log: true
config
@@ -768,3 +768,56 @@ exports.resetConnection = (test) ->
connection.on('debug', (text) ->
#console.log(text)
)
+
+exports.cancelRequest = (test) ->
+ test.expect(8)
+
+ config = getConfig()
+
+ request = new Request('select 1 as C1;waitfor delay \'00:00:05\';select 2 as C2', (err, rowCount, rows) ->
+ test.strictEqual err.message, 'Canceled.'
+
+ connection.close()
+ )
+
+ request.on('doneInProc', (rowCount, more) ->
+ test.ok false
+ )
+
+ request.on('doneProc', (rowCount, more) ->
+ test.ok !rowCount
+ test.strictEqual more, false
+ )
+
+ request.on('done', (rowCount, more, rows) ->
+ test.ok !rowCount
+ test.strictEqual more, false
+ )
+
+ request.on('columnMetadata', (columnsMetadata) ->
+ test.strictEqual(columnsMetadata.length, 1)
+ )
+
+ request.on('row', (columns) ->
+ test.strictEqual(columns.length, 1)
+ test.strictEqual(columns.C1.value, 1)
+ )
+
+ connection = new Connection(config)
+
+ connection.on('connect', (err) ->
+ connection.execSql(request)
+ connection.cancel()
+ )
+
+ connection.on('end', (info) ->
+ test.done()
+ )
+
+ connection.on('infoMessage', (info) ->
+ #console.log("#{info.number} : #{info.message}")
+ )
+
+ connection.on('debug', (text) ->
+ #console.log(text)
+ )

0 comments on commit 0326a5b

Please sign in to comment.