-
Notifications
You must be signed in to change notification settings - Fork 8
/
tds.py
4179 lines (3479 loc) · 124 KB
/
tds.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import struct
import codecs
from contextlib import contextmanager
import logging
import socket
import sys
from datetime import datetime, date, time, timedelta
from decimal import Decimal, localcontext
import tz
import uuid
import six
from six.moves import reduce
from six.moves import xrange
try:
import ssl
except:
encryption_supported = False
else:
encryption_supported = True
from collate import ucs2_codec, Collation, lcid2charset, raw_collation
logger = logging.getLogger()
ENCRYPTION_ENABLED = False
# tds protocol versions
TDS70 = 0x70000000
TDS71 = 0x71000000
TDS71rev1 = 0x71000001
TDS72 = 0x72090002
TDS73A = 0x730A0003
TDS73 = TDS73A
TDS73B = 0x730B0003
TDS74 = 0x74000004
IS_TDS7_PLUS = lambda x: x.tds_version >= TDS70
IS_TDS71_PLUS = lambda x: x.tds_version >= TDS71
IS_TDS72_PLUS = lambda x: x.tds_version >= TDS72
IS_TDS73_PLUS = lambda x: x.tds_version >= TDS73A
# packet types
TDS_QUERY = 1
TDS_LOGIN = 2
TDS_RPC = 3
TDS_REPLY = 4
TDS_CANCEL = 6
TDS_BULK = 7
TDS7_TRANS = 14 # transaction management
TDS_NORMAL = 15
TDS7_LOGIN = 16
TDS7_AUTH = 17
TDS71_PRELOGIN = 18
# mssql login options flags
# option_flag1_values
TDS_BYTE_ORDER_X86 = 0
TDS_CHARSET_ASCII = 0
TDS_DUMPLOAD_ON = 0
TDS_FLOAT_IEEE_754 = 0
TDS_INIT_DB_WARN = 0
TDS_SET_LANG_OFF = 0
TDS_USE_DB_SILENT = 0
TDS_BYTE_ORDER_68000 = 0x01
TDS_CHARSET_EBDDIC = 0x02
TDS_FLOAT_VAX = 0x04
TDS_FLOAT_ND5000 = 0x08
TDS_DUMPLOAD_OFF = 0x10 # prevent BCP
TDS_USE_DB_NOTIFY = 0x20
TDS_INIT_DB_FATAL = 0x40
TDS_SET_LANG_ON = 0x80
#enum option_flag2_values {
TDS_INIT_LANG_WARN = 0
TDS_INTEGRATED_SECURTY_OFF = 0
TDS_ODBC_OFF = 0
TDS_USER_NORMAL = 0 # SQL Server login
TDS_INIT_LANG_REQUIRED = 0x01
TDS_ODBC_ON = 0x02
TDS_TRANSACTION_BOUNDARY71 = 0x04 # removed in TDS 7.2
TDS_CACHE_CONNECT71 = 0x08 # removed in TDS 7.2
TDS_USER_SERVER = 0x10 # reserved
TDS_USER_REMUSER = 0x20 # DQ login
TDS_USER_SQLREPL = 0x40 # replication login
TDS_INTEGRATED_SECURITY_ON = 0x80
#enum option_flag3_values TDS 7.3+
TDS_RESTRICTED_COLLATION = 0
TDS_CHANGE_PASSWORD = 0x01
TDS_SEND_YUKON_BINARY_XML = 0x02
TDS_REQUEST_USER_INSTANCE = 0x04
TDS_UNKNOWN_COLLATION_HANDLING = 0x08
TDS_ANY_COLLATION = 0x10
TDS5_PARAMFMT2_TOKEN = 32 # 0x20
TDS_LANGUAGE_TOKEN = 33 # 0x20 TDS 5.0 only
TDS_ORDERBY2_TOKEN = 34 # 0x22
TDS_ROWFMT2_TOKEN = 97 # 0x61 TDS 5.0 only
TDS_LOGOUT_TOKEN = 113 # 0x71 TDS 5.0 only?
TDS_RETURNSTATUS_TOKEN = 121 # 0x79
TDS_PROCID_TOKEN = 124 # 0x7C TDS 4.2 only
TDS7_RESULT_TOKEN = 129 # 0x81 TDS 7.0 only
TDS7_COMPUTE_RESULT_TOKEN = 136 # 0x88 TDS 7.0 only
TDS_COLNAME_TOKEN = 160 # 0xA0 TDS 4.2 only
TDS_COLFMT_TOKEN = 161 # 0xA1 TDS 4.2 only
TDS_DYNAMIC2_TOKEN = 163 # 0xA3
TDS_TABNAME_TOKEN = 164 # 0xA4
TDS_COLINFO_TOKEN = 165 # 0xA5
TDS_OPTIONCMD_TOKEN = 166 # 0xA6
TDS_COMPUTE_NAMES_TOKEN = 167 # 0xA7
TDS_COMPUTE_RESULT_TOKEN = 168 # 0xA8
TDS_ORDERBY_TOKEN = 169 # 0xA9
TDS_ERROR_TOKEN = 170 # 0xAA
TDS_INFO_TOKEN = 171 # 0xAB
TDS_PARAM_TOKEN = 172 # 0xAC
TDS_LOGINACK_TOKEN = 173 # 0xAD
TDS_CONTROL_TOKEN = 174 # 0xAE
TDS_ROW_TOKEN = 209 # 0xD1
TDS_NBC_ROW_TOKEN = 210 # 0xD2 as of TDS 7.3.B
TDS_CMP_ROW_TOKEN = 211 # 0xD3
TDS5_PARAMS_TOKEN = 215 # 0xD7 TDS 5.0 only
TDS_CAPABILITY_TOKEN = 226 # 0xE2
TDS_ENVCHANGE_TOKEN = 227 # 0xE3
TDS_EED_TOKEN = 229 # 0xE5
TDS_DBRPC_TOKEN = 230 # 0xE6
TDS5_DYNAMIC_TOKEN = 231 # 0xE7 TDS 5.0 only
TDS5_PARAMFMT_TOKEN = 236 # 0xEC TDS 5.0 only
TDS_AUTH_TOKEN = 237 # 0xED TDS 7.0 only
TDS_RESULT_TOKEN = 238 # 0xEE
TDS_DONE_TOKEN = 253 # 0xFD
TDS_DONEPROC_TOKEN = 254 # 0xFE
TDS_DONEINPROC_TOKEN = 255 # 0xFF
# CURSOR support: TDS 5.0 only
TDS_CURCLOSE_TOKEN = 128 # 0x80 TDS 5.0 only
TDS_CURDELETE_TOKEN = 129 # 0x81 TDS 5.0 only
TDS_CURFETCH_TOKEN = 130 # 0x82 TDS 5.0 only
TDS_CURINFO_TOKEN = 131 # 0x83 TDS 5.0 only
TDS_CUROPEN_TOKEN = 132 # 0x84 TDS 5.0 only
TDS_CURDECLARE_TOKEN = 134 # 0x86 TDS 5.0 only
# environment type field
TDS_ENV_DATABASE = 1
TDS_ENV_LANG = 2
TDS_ENV_CHARSET = 3
TDS_ENV_PACKSIZE = 4
TDS_ENV_LCID = 5
TDS_ENV_SQLCOLLATION = 7
TDS_ENV_BEGINTRANS = 8
TDS_ENV_COMMITTRANS = 9
TDS_ENV_ROLLBACKTRANS = 10
TDS_ENV_ENLIST_DTC_TRANS = 11
TDS_ENV_DEFECT_TRANS = 12
TDS_ENV_DB_MIRRORING_PARTNER = 13
TDS_ENV_PROMOTE_TRANS = 15
TDS_ENV_TRANS_MANAGER_ADDR = 16
TDS_ENV_TRANS_ENDED = 17
TDS_ENV_RESET_COMPLETION_ACK = 18
TDS_ENV_INSTANCE_INFO = 19
TDS_ENV_ROUTING = 20
# Microsoft internal stored procedure id's
TDS_SP_CURSOR = 1
TDS_SP_CURSOROPEN = 2
TDS_SP_CURSORPREPARE = 3
TDS_SP_CURSOREXECUTE = 4
TDS_SP_CURSORPREPEXEC = 5
TDS_SP_CURSORUNPREPARE = 6
TDS_SP_CURSORFETCH = 7
TDS_SP_CURSOROPTION = 8
TDS_SP_CURSORCLOSE = 9
TDS_SP_EXECUTESQL = 10
TDS_SP_PREPARE = 11
TDS_SP_EXECUTE = 12
TDS_SP_PREPEXEC = 13
TDS_SP_PREPEXECRPC = 14
TDS_SP_UNPREPARE = 15
# Flags returned in TDS_DONE token
TDS_DONE_FINAL = 0
TDS_DONE_MORE_RESULTS = 0x01 # more results follow
TDS_DONE_ERROR = 0x02 # error occurred
TDS_DONE_INXACT = 0x04 # transaction in progress
TDS_DONE_PROC = 0x08 # results are from a stored procedure
TDS_DONE_COUNT = 0x10 # count field in packet is valid
TDS_DONE_CANCELLED = 0x20 # acknowledging an attention command (usually a cancel)
TDS_DONE_EVENT = 0x40 # part of an event notification.
TDS_DONE_SRVERROR = 0x100 # SQL server server error
SYBVOID = 31 # 0x1F
IMAGETYPE = SYBIMAGE = 34 # 0x22
TEXTTYPE = SYBTEXT = 35 # 0x23
SYBVARBINARY = 37 # 0x25
INTNTYPE = SYBINTN = 38 # 0x26
SYBVARCHAR = 39 # 0x27
BINARYTYPE = SYBBINARY = 45 # 0x2D
SYBCHAR = 47 # 0x2F
INT1TYPE = SYBINT1 = 48 # 0x30
BITTYPE = SYBBIT = 50 # 0x32
INT2TYPE = SYBINT2 = 52 # 0x34
INT4TYPE = SYBINT4 = 56 # 0x38
DATETIM4TYPE = SYBDATETIME4 = 58 # 0x3A
FLT4TYPE = SYBREAL = 59 # 0x3B
MONEYTYPE = SYBMONEY = 60 # 0x3C
DATETIMETYPE = SYBDATETIME = 61 # 0x3D
FLT8TYPE = SYBFLT8 = 62 # 0x3E
NTEXTTYPE = SYBNTEXT = 99 # 0x63
SYBNVARCHAR = 103 # 0x67
BITNTYPE = SYBBITN = 104 # 0x68
NUMERICNTYPE = SYBNUMERIC = 108 # 0x6C
DECIMALNTYPE = SYBDECIMAL = 106 # 0x6A
FLTNTYPE = SYBFLTN = 109 # 0x6D
MONEYNTYPE = SYBMONEYN = 110 # 0x6E
DATETIMNTYPE = SYBDATETIMN = 111 # 0x6F
MONEY4TYPE = SYBMONEY4 = 122 # 0x7A
INT8TYPE = SYBINT8 = 127 # 0x7F
BIGCHARTYPE = XSYBCHAR = 175 # 0xAF
BIGVARCHRTYPE = XSYBVARCHAR = 167 # 0xA7
NVARCHARTYPE = XSYBNVARCHAR = 231 # 0xE7
NCHARTYPE = XSYBNCHAR = 239 # 0xEF
BIGVARBINTYPE = XSYBVARBINARY = 165 # 0xA5
BIGBINARYTYPE = XSYBBINARY = 173 # 0xAD
GUIDTYPE = SYBUNIQUE = 36 # 0x24
SSVARIANTTYPE = SYBVARIANT = 98 # 0x62
UDTTYPE = SYBMSUDT = 240 # 0xF0
XMLTYPE = SYBMSXML = 241 # 0xF1
DATENTYPE = SYBMSDATE = 40 # 0x28
TIMENTYPE = SYBMSTIME = 41 # 0x29
DATETIME2NTYPE = SYBMSDATETIME2 = 42 # 0x2a
DATETIMEOFFSETNTYPE = SYBMSDATETIMEOFFSET = 43 # 0x2b
#
# Sybase only types
#
SYBLONGBINARY = 225 # 0xE1
SYBUINT1 = 64 # 0x40
SYBUINT2 = 65 # 0x41
SYBUINT4 = 66 # 0x42
SYBUINT8 = 67 # 0x43
SYBBLOB = 36 # 0x24
SYBBOUNDARY = 104 # 0x68
SYBDATE = 49 # 0x31
SYBDATEN = 123 # 0x7B
SYB5INT8 = 191 # 0xBF
SYBINTERVAL = 46 # 0x2E
SYBLONGCHAR = 175 # 0xAF
SYBSENSITIVITY = 103 # 0x67
SYBSINT1 = 176 # 0xB0
SYBTIME = 51 # 0x33
SYBTIMEN = 147 # 0x93
SYBUINTN = 68 # 0x44
SYBUNITEXT = 174 # 0xAE
SYBXML = 163 # 0xA3
TDS_UT_TIMESTAMP = 80
# compute operator
SYBAOPCNT = 0x4b
SYBAOPCNTU = 0x4c
SYBAOPSUM = 0x4d
SYBAOPSUMU = 0x4e
SYBAOPAVG = 0x4f
SYBAOPAVGU = 0x50
SYBAOPMIN = 0x51
SYBAOPMAX = 0x52
# mssql2k compute operator
SYBAOPCNT_BIG = 0x09
SYBAOPSTDEV = 0x30
SYBAOPSTDEVP = 0x31
SYBAOPVAR = 0x32
SYBAOPVARP = 0x33
SYBAOPCHECKSUM_AGG = 0x72
# param flags
fByRefValue = 1
fDefaultValue = 2
TDS_IDLE = 0
TDS_QUERYING = 1
TDS_PENDING = 2
TDS_READING = 3
TDS_DEAD = 4
state_names = ['IDLE', 'QUERYING', 'PENDING', 'READING', 'DEAD']
TDS_ENCRYPTION_OFF = 0
TDS_ENCRYPTION_REQUEST = 1
TDS_ENCRYPTION_REQUIRE = 2
USE_CORK = hasattr(socket, 'TCP_CORK')
TDS_NO_COUNT = -1
_utc = tz.utc
_header = struct.Struct('>BBHHBx')
_byte = struct.Struct('B')
_smallint_le = struct.Struct('<h')
_smallint_be = struct.Struct('>h')
_usmallint_le = struct.Struct('<H')
_usmallint_be = struct.Struct('>H')
_int_le = struct.Struct('<l')
_int_be = struct.Struct('>l')
_uint_le = struct.Struct('<L')
_uint_be = struct.Struct('>L')
_int8_le = struct.Struct('<q')
_int8_be = struct.Struct('>q')
_uint8_le = struct.Struct('<Q')
_uint8_be = struct.Struct('>Q')
_flt8_struct = struct.Struct('d')
_flt4_struct = struct.Struct('f')
PLP_MARKER = 0xffff
PLP_NULL = 0xffffffffffffffff
PLP_UNKNOWN = 0xfffffffffffffffe
class PlpReader(object):
""" Partially length prefixed reader
Spec: http://msdn.microsoft.com/en-us/library/dd340469.aspx
"""
def __init__(self, r):
"""
:param r: An instance of :class:`_TdsReader`
"""
self._rdr = r
size = r.get_uint8()
self._size = size
def is_null(self):
"""
:return: True if stored value is NULL
"""
return self._size == PLP_NULL
def is_unknown_len(self):
"""
:return: True if total size is unknown upfront
"""
return self._size == PLP_UNKNOWN
def size(self):
"""
:return: Total size in bytes if is_uknown_len and is_null are both False
"""
return self._size
def chunks(self):
""" Generates chunks from stream, each chunk is an instace of bytes.
"""
if self.is_null():
return
total = 0
while True:
chunk_len = self._rdr.get_uint()
if chunk_len == 0:
if not self.is_unknown_len() and total != self._size:
msg = "PLP actual length (%d) doesn't match reported length (%d)" % (total, self._size)
self._rdr.session.bad_stream(msg)
return
total += chunk_len
left = chunk_len
while left:
buf = self._rdr.read(left)
yield buf
left -= len(buf)
def iterdecode(iterable, codec):
""" Uses an incremental decoder to decode each chunk in iterable.
This function is a generator.
:param codec: An instance of codec
"""
decoder = codec.incrementaldecoder()
for chunk in iterable:
yield decoder.decode(chunk)
yield decoder.decode(b'', True)
class SimpleLoadBalancer(object):
def __init__(self, hosts):
self._hosts = hosts
def choose(self):
for host in self._hosts:
yield host
def force_unicode(s):
if isinstance(s, bytes):
try:
return s.decode('utf8')
except UnicodeDecodeError as e:
raise DatabaseError(e)
else:
return s
def tds_quote_id(id):
""" Quote an identifier
:param id: id to quote
:returns: Quoted identifier
"""
return '[{0}]'.format(id.replace(']', ']]'))
def tds7_crypt_pass(password):
""" Mangle password according to tds rules
:param password: Password str
:returns: Byte-string with encoded password
"""
encoded = bytearray(ucs2_codec.encode(password)[0])
for i, ch in enumerate(encoded):
encoded[i] = ((ch << 4) & 0xff | (ch >> 4)) ^ 0xA5
return encoded
def total_seconds(td):
""" Total number of seconds in timedelta object
Python 2.6 doesn't have total_seconds method, this function
provides a backport
"""
return td.days * 24 * 60 * 60 + td.seconds
# store a tuple of programming error codes
prog_errors = (
102, # syntax error
207, # invalid column name
208, # invalid object name
2812, # unknown procedure
4104 # multi-part identifier could not be bound
)
# store a tuple of integrity error codes
integrity_errors = (
515, # NULL insert
547, # FK related
2601, # violate unique index
2627, # violate UNIQUE KEY constraint
)
if sys.version_info[0] >= 3:
exc_base_class = Exception
def _ord(val):
return val
else:
exc_base_class = StandardError
def _ord(val):
return ord(val)
def _decode_num(buf):
""" Decodes little-endian integer from buffer
Buffer can be of any size
"""
return reduce(lambda acc, val: acc * 256 + _ord(val), reversed(buf), 0)
# exception hierarchy
class Warning(exc_base_class):
pass
class Error(exc_base_class):
pass
TimeoutError = socket.timeout
class InterfaceError(Error):
pass
class DatabaseError(Error):
@property
def message(self):
if self.procname:
return 'SQL Server message %d, severity %d, state %d, ' \
'procedure %s, line %d:\n%s' % (self.number,
self.severity, self.state, self.procname,
self.line, self.text)
else:
return 'SQL Server message %d, severity %d, state %d, ' \
'line %d:\n%s' % (self.number, self.severity,
self.state, self.line, self.text)
class ClosedConnectionError(InterfaceError):
def __init__(self):
super(ClosedConnectionError, self).__init__('Server closed connection')
class DataError(Error):
pass
class OperationalError(DatabaseError):
pass
class LoginError(OperationalError):
pass
class IntegrityError(DatabaseError):
pass
class InternalError(DatabaseError):
pass
class ProgrammingError(DatabaseError):
pass
class NotSupportedError(DatabaseError):
pass
#############################
## DB-API type definitions ##
#############################
class DBAPITypeObject:
def __init__(self, *values):
self.values = set(values)
def __eq__(self, other):
return other in self.values
def __cmp__(self, other):
if other in self.values:
return 0
if other < self.values:
return 1
else:
return -1
STRING = DBAPITypeObject(SYBVARCHAR, SYBCHAR, SYBTEXT,
XSYBNVARCHAR, XSYBNCHAR, SYBNTEXT,
XSYBVARCHAR, XSYBCHAR, SYBMSXML)
BINARY = DBAPITypeObject(SYBIMAGE, SYBBINARY, SYBVARBINARY, XSYBVARBINARY, XSYBBINARY)
NUMBER = DBAPITypeObject(SYBBIT, SYBBITN, SYBINT1, SYBINT2, SYBINT4, SYBINT8, SYBINTN,
SYBREAL, SYBFLT8, SYBFLTN)
DATETIME = DBAPITypeObject(SYBDATETIME, SYBDATETIME4, SYBDATETIMN)
DECIMAL = DBAPITypeObject(SYBMONEY, SYBMONEY4, SYBMONEYN, SYBNUMERIC,
SYBDECIMAL)
ROWID = DBAPITypeObject()
# stored procedure output parameter
class output:
#property
def type(self):
"""
This is the type of the parameter.
"""
return self._type
@property
def value(self):
"""
This is the value of the parameter.
"""
return self._value
def __init__(self, param_type, value=None):
self._type = param_type
self._value = value
class Binary(bytes):
def __repr__(self):
return 'Binary({0})'.format(super(Binary, self).__repr__())
class _Default:
pass
default = _Default()
class InternalProc(object):
def __init__(self, proc_id, name):
self.proc_id = proc_id
self.name = name
def __unicode__(self):
return self.name
SP_EXECUTESQL = InternalProc(TDS_SP_EXECUTESQL, 'sp_executesql')
class _TdsEnv:
pass
def skipall(stm, size):
""" Skips exactly size bytes in stm
If EOF is reached before size bytes are skipped
will raise :class:`ClosedConnectionError`
:param stm: Stream to skip bytes in, should have read method
this read method can return less than requested
number of bytes.
:param size: Number of bytes to skip.
"""
res = stm.read(size)
if len(res) == size:
return
elif len(res) == 0:
raise ClosedConnectionError()
left = size - len(res)
while left:
buf = stm.read(left)
if len(buf) == 0:
raise ClosedConnectionError()
left -= len(buf)
def read_chunks(stm, size):
""" Reads exactly size bytes from stm and produces chunks
May call stm.read multiple times until required
number of bytes is read.
If EOF is reached before size bytes are read
will raise :class:`ClosedConnectionError`
:param stm: Stream to read bytes from, should have read method,
this read method can return less than requested
number of bytes.
:param size: Number of bytes to read.
"""
if size == 0:
yield b''
return
res = stm.read(size)
if len(res) == 0:
raise ClosedConnectionError()
yield res
left = size - len(res)
while left:
buf = stm.read(left)
if len(buf) == 0:
raise ClosedConnectionError()
yield buf
left -= len(buf)
def readall(stm, size):
""" Reads exactly size bytes from stm
May call stm.read multiple times until required
number of bytes read.
If EOF is reached before size bytes are read
will raise :class:`ClosedConnectionError`
:param stm: Stream to read bytes from, should have read method
this read method can return less than requested
number of bytes.
:param size: Number of bytes to read.
:returns: Bytes buffer of exactly given size.
"""
return b''.join(read_chunks(stm, size))
def readall_fast(stm, size):
buf, offset = stm.read_fast(size)
if len(buf) - offset < size:
# slow case
buf = buf[offset:]
buf += stm.read(size - len(buf))
return buf, 0
return buf, offset
class _TdsReader(object):
""" TDS stream reader
Provides stream-like interface for TDS packeted stream.
Also provides convinience methods to decode primitive data like
different kinds of integers etc.
"""
def __init__(self, session):
self._buf = ''
self._pos = 0 # position in the buffer
self._have = 0 # number of bytes read from packet
self._size = 0 # size of current packet
self._session = session
self._transport = session._transport
self._type = None
self._status = None
@property
def session(self):
""" Link to :class:`_TdsSession` object
"""
return self._session
@property
def packet_type(self):
""" Type of current packet
Possible values are TDS_QUERY, TDS_LOGIN, etc.
"""
return self._type
def read_fast(self, size):
""" Faster version of read
Instead of returning sliced buffer it returns reference to internal
buffer and the offset to this buffer.
:param size: Number of bytes to read
:returns: Tuple of bytes buffer, and offset in this buffer
"""
if self._pos >= len(self._buf):
if self._have >= self._size:
self._read_packet()
else:
self._buf = self._transport.read(self._size - self._have)
self._pos = 0
self._have += len(self._buf)
offset = self._pos
self._pos += size
return self._buf, offset
def unpack(self, struct):
""" Unpacks given structure from stream
:param struct: A struct.Struct instance
:returns: Result of unpacking
"""
buf, offset = readall_fast(self, struct.size)
return struct.unpack_from(buf, offset)
def get_byte(self):
""" Reads one byte from stream """
return self.unpack(_byte)[0]
def get_smallint(self):
""" Reads 16bit signed integer from the stream """
return self.unpack(_smallint_le)[0]
def get_usmallint(self):
""" Reads 16bit unsigned integer from the stream """
return self.unpack(_usmallint_le)[0]
def get_int(self):
""" Reads 32bit signed integer from the stream """
return self.unpack(_int_le)[0]
def get_uint(self):
""" Reads 32bit unsigned integer from the stream """
return self.unpack(_uint_le)[0]
def get_uint_be(self):
""" Reads 32bit unsigned big-endian integer from the stream """
return self.unpack(_uint_be)[0]
def get_uint8(self):
""" Reads 64bit unsigned integer from the stream """
return self.unpack(_uint8_le)[0]
def get_int8(self):
""" Reads 64bit signed integer from the stream """
return self.unpack(_int8_le)[0]
def read_ucs2(self, num_chars):
""" Reads num_chars UCS2 string from the stream """
buf = readall(self, num_chars * 2)
return ucs2_codec.decode(buf)[0]
def read_str(self, size, codec):
""" Reads byte string from the stream and decodes it
:param size: Size of string in bytes
:param codec: Instance of codec to decode string
:returns: Unicode string
"""
return codec.decode(readall(self, size))[0]
def get_collation(self):
""" Reads :class:`Collation` object from stream """
buf = readall(self, Collation.wire_size)
return Collation.unpack(buf)
def unget_byte(self):
""" Returns one last read byte to stream
Can only be called once per read byte.
"""
# this is a one trick pony...don't call it twice
assert self._pos > 0
self._pos -= 1
def peek(self):
""" Returns next byte from stream without consuming it
"""
res = self.get_byte()
self.unget_byte()
return res
def read(self, size):
""" Reads size bytes from buffer
May return fewer bytes than requested
:param size: Number of bytes to read
:returns: Bytes buffer, possibly shorter than requested,
returns empty buffer in case of EOF
"""
buf, offset = self.read_fast(size)
return buf[offset:offset + size]
def _read_packet(self):
""" Reads next TDS packet from the underlying transport
If timeout is happened during reading of packet's header will
cancel current request.
Can only be called when transport's read pointer is at the begining
of the packet.
"""
try:
header = readall(self._transport, _header.size)
except TimeoutError:
self._session._put_cancel()
raise
self._pos = 0
self._type, self._status, self._size, self._session._spid, _ = _header.unpack(header)
self._have = _header.size
assert self._size > self._have, 'Empty packet doesn make any sense'
self._buf = self._transport.read(self._size - self._have)
self._have += len(self._buf)
def read_whole_packet(self):
""" Reads single packet and returns bytes payload of the packet
Can only be called when transport's read pointer is at the begining
of the packet.
"""
self._read_packet()
return readall(self, self._size - _header.size)
class _TdsWriter(object):
""" TDS stream writer
Handles splitting of incoming data into TDS packets according to TDS protocol.
Provides convinience methods for writing primitive data types.
"""
def __init__(self, session, bufsize):
self._session = session
self._tds = session
self._transport = session
self._pos = 0
self._buf = bytearray(bufsize)
self._packet_no = 0
self.data = ''
@property
def session(self):
""" Back reference to parent :class:`_TdsSession` object """
return self._session
@property
def bufsize(self):
""" Size of the buffer """
return len(self._buf)
@bufsize.setter
def bufsize(self, bufsize):
if len(self._buf) == bufsize:
return
if bufsize > len(self._buf):
self._buf.extend(b'\0' * (bufsize - len(self._buf)))
else:
self._buf = self._buf[0:bufsize]
def begin_packet(self, packet_type):
""" Starts new packet stream
:param packet_type: Type of TDS stream, e.g. TDS_PRELOGIN, TDS_QUERY etc.
"""
self._type = packet_type
self._pos = 8
def pack(self, struct, *args):
""" Packs and writes structure into stream """
self.write(struct.pack(*args))
def put_byte(self, value):
""" Writes single byte into stream """
self.pack(_byte, value)
def put_smallint(self, value):
""" Writes 16-bit signed integer into the stream """
self.pack(_smallint_le, value)
def put_usmallint(self, value):
""" Writes 16-bit unsigned integer into the stream """
self.pack(_usmallint_le, value)
def put_smallint_be(self, value):
""" Writes 16-bit signed big-endian integer into the stream """
self.pack(_smallint_be, value)
def put_usmallint_be(self, value):
""" Writes 16-bit unsigned big-endian integer into the stream """
self.pack(_usmallint_be, value)
def put_int(self, value):
""" Writes 32-bit signed integer into the stream """
self.pack(_int_le, value)
def put_uint(self, value):
""" Writes 32-bit unsigned integer into the stream """
self.pack(_uint_le, value)
def put_int_be(self, value):
""" Writes 32-bit signed big-endian integer into the stream """
self.pack(_int_be, value)
def put_uint_be(self, value):
""" Writes 32-bit unsigned big-endian integer into the stream """
self.pack(_uint_be, value)
def put_int8(self, value):
""" Writes 64-bit signed integer into the stream """
self.pack(_int8_le, value)
def put_uint8(self, value):
""" Writes 64-bit unsigned integer into the stream """
self.pack(_uint8_le, value)
def put_collation(self, collation):
""" Writes :class:`Collation` structure into the stream """
self.write(collation.pack())
def write(self, data):
""" Writes given bytes buffer into the stream
Function returns only when entire buffer is written
"""
#self.data += data
data_off = 0
while data_off < len(data):
left = len(self._buf) - self._pos
if left <= 0:
self._write_packet(final=False)
else:
to_write = min(left, len(data) - data_off)
self._buf[self._pos:self._pos + to_write] = data[data_off:data_off + to_write]
self._pos += to_write
data_off += to_write
def write_ucs2(self, s):
""" Write string encoding it in UCS2 into stream """
self.write_string(s, ucs2_codec)
def write_string(self, s, codec):
""" Write string encoding it with codec into stream """
for i in xrange(0, len(s), self.bufsize):
chunk = s[i:i + self.bufsize]
buf, consumed = codec.encode(chunk)
assert consumed == len(chunk)
self.write(buf)
def flush(self):
""" Closes current packet stream """
return self._write_packet(final=True)
def _write_packet(self, final):
""" Writes single TDS packet into underlying transport.
Data for the packet is taken from internal buffer.
:param final: True means this is the final packet in substream.
"""
status = 1 if final else 0
_header.pack_into(self._buf, 0, self._type, status, self._pos, 0, self._packet_no)
self._packet_no = (self._packet_no + 1) % 256
self.data += self._buf[:self._pos]