denisenkom / pytds

Python DBAPI driver for MSSQL using pure Python TDS (Tabular Data Stream) protocol implementation
MIT License
192 stars 53 forks source link

Enclose destination column names in [brackets] in bulk copy. #88

Closed LHCGreg closed 6 years ago

LHCGreg commented 6 years ago

This allows having destination column names that are SQL keywords, such as User.

Fixes #87.

By the way, it was not clear to me how to run the tests. docs/index.rst says to use tox after setting some environment variables but I wasn't able to get that to work, getting "ModuleNotFoundError: No module named 'pytest'", even though I was in a virtualenv that does have pytest installed. I'm not very familiar with Python testing frameworks so maybe I'm doing something wrong.

I was able to run tests/connected_test.py with pytest but got 5 failures before I even made any changes. The test that I wrote passes after the change at least.

================================== FAILURES ===================================
_________ test_select_values[99999999999999999999999999999999999999] __________

cursor = <pytds._MarsCursor object at 0x0000026535225B00>
val = 99999999999999999999999999999999999999

    @pytest.mark.parametrize('val', [u'hello',
                                     u'x' * 5000,
                                     'x' * 9000,
                                     123,
                                     -123,
                                     123.12,
                                     -123.12,
                                     10 ** 20,
                                     10 ** 38 - 1,
                                     -10 ** 38 + 1,
                                     datetime.datetime(2011, 2, 3, 10, 11, 12, 3000),
                                     Decimal('1234.567'),
                                     Decimal('1234000'),
                                     Decimal('9' * 38),
                                     Decimal('0.' + '9' * 38),
                                     -Decimal('9' * 38),
                                     Decimal('1E10'),
                                     Decimal('1E-10'),
                                     Decimal('0.{0}1'.format('0' * 37)),
                                     None,
                                     'hello',
                                     '',
                                     pytds.Binary(b''),
                                     pytds.Binary(b'\x00\x01\x02'),
                                     pytds.Binary(b'x' * 9000),
                                     2 ** 63 - 1,
                                     False,
                                     True,
                                     uuid.uuid4(),
                                     u'Iñtërnâtiônàlizætiøn1',
                                     u'\U0001d6fc',
                                     ])
    def test_select_values(cursor, val):
>       cursor.execute('select %s', (val,))

tests\connected_test.py:384:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src\pytds\__init__.py:1019: in execute
    self._execute(operation, params)
src\pytds\__init__.py:712: in _execute
    self._exec_with_retry(lambda: self._session.submit_rpc(
src\pytds\__init__.py:660: in _exec_with_retry
    return fun()
src\pytds\__init__.py:715: in <lambda>
    0))
src\pytds\tds.py:1034: in submit_rpc
    serializer.write(w, param.value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = MsDecimal(scale=0, prec=38)
w = <pytds.tds._TdsWriter object at 0x00000265352259B0>
value = Decimal('1E+38')

    def write(self, w, value):
        if value is None:
            w.put_byte(0)
            return
        if not isinstance(value, decimal.Decimal):
            value = decimal.Decimal(value)
        value = value.normalize()
        scale = self.scale
        size = self.size
        w.put_byte(size)
        val = value
        positive = 1 if val > 0 else 0
        w.put_byte(positive)  # sign
        with decimal.localcontext() as ctx:
            ctx.prec = 38
            if not positive:
                val *= -1
            size -= 1
            val *= 10 ** scale
        for i in range(size):
>           w.put_byte(int(val % 256))
E           decimal.InvalidOperation: [<class 'decimal.DivisionImpossible'>]

src\pytds\tds_types.py:1880: InvalidOperation
------------------------------ Captured log call ------------------------------
tds.py                     989 INFO     Sending RPC <pytds.tds_base.InternalProc object at 0x000002653440F5F8>
---------------------------- Captured log teardown ----------------------------
tds.py                    1142 INFO     Sending ROLLBACK TRAN
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got DONE/DONEINPROC/DONEPROC message
_________ test_select_values[-99999999999999999999999999999999999999] _________

cursor = <pytds._MarsCursor object at 0x00000265350E1D30>
val = -99999999999999999999999999999999999999

    @pytest.mark.parametrize('val', [u'hello',
                                     u'x' * 5000,
                                     'x' * 9000,
                                     123,
                                     -123,
                                     123.12,
                                     -123.12,
                                     10 ** 20,
                                     10 ** 38 - 1,
                                     -10 ** 38 + 1,
                                     datetime.datetime(2011, 2, 3, 10, 11, 12, 3000),
                                     Decimal('1234.567'),
                                     Decimal('1234000'),
                                     Decimal('9' * 38),
                                     Decimal('0.' + '9' * 38),
                                     -Decimal('9' * 38),
                                     Decimal('1E10'),
                                     Decimal('1E-10'),
                                     Decimal('0.{0}1'.format('0' * 37)),
                                     None,
                                     'hello',
                                     '',
                                     pytds.Binary(b''),
                                     pytds.Binary(b'\x00\x01\x02'),
                                     pytds.Binary(b'x' * 9000),
                                     2 ** 63 - 1,
                                     False,
                                     True,
                                     uuid.uuid4(),
                                     u'Iñtërnâtiônàlizætiøn1',
                                     u'\U0001d6fc',
                                     ])
    def test_select_values(cursor, val):
>       cursor.execute('select %s', (val,))

tests\connected_test.py:384:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src\pytds\__init__.py:1019: in execute
    self._execute(operation, params)
src\pytds\__init__.py:712: in _execute
    self._exec_with_retry(lambda: self._session.submit_rpc(
src\pytds\__init__.py:660: in _exec_with_retry
    return fun()
src\pytds\__init__.py:715: in <lambda>
    0))
src\pytds\tds.py:1034: in submit_rpc
    serializer.write(w, param.value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = MsDecimal(scale=0, prec=38)
w = <pytds.tds._TdsWriter object at 0x00000265350E1780>
value = Decimal('-1E+38')

    def write(self, w, value):
        if value is None:
            w.put_byte(0)
            return
        if not isinstance(value, decimal.Decimal):
            value = decimal.Decimal(value)
        value = value.normalize()
        scale = self.scale
        size = self.size
        w.put_byte(size)
        val = value
        positive = 1 if val > 0 else 0
        w.put_byte(positive)  # sign
        with decimal.localcontext() as ctx:
            ctx.prec = 38
            if not positive:
                val *= -1
            size -= 1
            val *= 10 ** scale
        for i in range(size):
>           w.put_byte(int(val % 256))
E           decimal.InvalidOperation: [<class 'decimal.DivisionImpossible'>]

src\pytds\tds_types.py:1880: InvalidOperation
------------------------------ Captured log call ------------------------------
tds.py                     989 INFO     Sending RPC <pytds.tds_base.InternalProc object at 0x000002653440F5F8>
---------------------------- Captured log teardown ----------------------------
tds.py                    1142 INFO     Sending ROLLBACK TRAN
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got DONE/DONEINPROC/DONEPROC message
__________________________ test_select_values[val13] __________________________

cursor = <pytds._MarsCursor object at 0x0000026535752470>
val = Decimal('99999999999999999999999999999999999999')

    @pytest.mark.parametrize('val', [u'hello',
                                     u'x' * 5000,
                                     'x' * 9000,
                                     123,
                                     -123,
                                     123.12,
                                     -123.12,
                                     10 ** 20,
                                     10 ** 38 - 1,
                                     -10 ** 38 + 1,
                                     datetime.datetime(2011, 2, 3, 10, 11, 12, 3000),
                                     Decimal('1234.567'),
                                     Decimal('1234000'),
                                     Decimal('9' * 38),
                                     Decimal('0.' + '9' * 38),
                                     -Decimal('9' * 38),
                                     Decimal('1E10'),
                                     Decimal('1E-10'),
                                     Decimal('0.{0}1'.format('0' * 37)),
                                     None,
                                     'hello',
                                     '',
                                     pytds.Binary(b''),
                                     pytds.Binary(b'\x00\x01\x02'),
                                     pytds.Binary(b'x' * 9000),
                                     2 ** 63 - 1,
                                     False,
                                     True,
                                     uuid.uuid4(),
                                     u'Iñtërnâtiônàlizætiøn1',
                                     u'\U0001d6fc',
                                     ])
    def test_select_values(cursor, val):
>       cursor.execute('select %s', (val,))

tests\connected_test.py:384:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src\pytds\__init__.py:1019: in execute
    self._execute(operation, params)
src\pytds\__init__.py:712: in _execute
    self._exec_with_retry(lambda: self._session.submit_rpc(
src\pytds\__init__.py:660: in _exec_with_retry
    return fun()
src\pytds\__init__.py:715: in <lambda>
    0))
src\pytds\tds.py:1028: in submit_rpc
    collation=self._tds.collation or raw_collation
src\pytds\tds_base.py:625: in choose_serializer
    return type_factory.serializer_by_type(sql_type=self.type, collation=collation)
src\pytds\tds_types.py:2467: in serializer_by_type
    return self._type_map[tds_base.SYBDECIMAL](scale=typ.scale, precision=typ.precision)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[AttributeError("'MsDecimalSerializer' object has no attribute '_scale'") raised in repr()] MsDecimalSerializer object at 0x26535216be0>
precision = 39, scale = 0

    def __init__(self, precision=18, scale=0):
        super(MsDecimalSerializer, self).__init__(precision=precision,
                                                  scale=scale,
>                                                 size=self._bytes_per_prec[precision])
E       IndexError: list index out of range

src\pytds\tds_types.py:1840: IndexError
------------------------------ Captured log call ------------------------------
tds.py                     989 INFO     Sending RPC <pytds.tds_base.InternalProc object at 0x000002653440F5F8>
---------------------------- Captured log teardown ----------------------------
tds.py                    1142 INFO     Sending ROLLBACK TRAN
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got DONE/DONEINPROC/DONEPROC message
__________________________ test_select_values[val14] __________________________

cursor = <pytds._MarsCursor object at 0x00000265357B0C50>
val = Decimal('0.99999999999999999999999999999999999999')

    @pytest.mark.parametrize('val', [u'hello',
                                     u'x' * 5000,
                                     'x' * 9000,
                                     123,
                                     -123,
                                     123.12,
                                     -123.12,
                                     10 ** 20,
                                     10 ** 38 - 1,
                                     -10 ** 38 + 1,
                                     datetime.datetime(2011, 2, 3, 10, 11, 12, 3000),
                                     Decimal('1234.567'),
                                     Decimal('1234000'),
                                     Decimal('9' * 38),
                                     Decimal('0.' + '9' * 38),
                                     -Decimal('9' * 38),
                                     Decimal('1E10'),
                                     Decimal('1E-10'),
                                     Decimal('0.{0}1'.format('0' * 37)),
                                     None,
                                     'hello',
                                     '',
                                     pytds.Binary(b''),
                                     pytds.Binary(b'\x00\x01\x02'),
                                     pytds.Binary(b'x' * 9000),
                                     2 ** 63 - 1,
                                     False,
                                     True,
                                     uuid.uuid4(),
                                     u'Iñtërnâtiônàlizætiøn1',
                                     u'\U0001d6fc',
                                     ])
    def test_select_values(cursor, val):
        cursor.execute('select %s', (val,))
>       assert cursor.fetchone() == (val,)
E       AssertionError: assert (Decimal('1'),) == (Decimal('0.999999999...999999999999999999'),)
E         At index 0 diff: Decimal('1') != Decimal('0.99999999999999999999999999999999999999')
E         Full diff:
E         - (Decimal('1'),)
E         + (Decimal('0.99999999999999999999999999999999999999'),)

tests\connected_test.py:385: AssertionError
------------------------------ Captured log call ------------------------------
tds.py                     989 INFO     Sending RPC <pytds.tds_base.InternalProc object at 0x000002653440F5F8>
tds.py                     466 INFO     [52] got COLMETADATA
tds.py                     466 INFO     [52] got ROW message
---------------------------- Captured log teardown ----------------------------
tds.py                    1142 INFO     Sending ROLLBACK TRAN
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got DONE/DONEINPROC/DONEPROC message
__________________________ test_select_values[val15] __________________________

cursor = <pytds._MarsCursor object at 0x0000026535349CC0>
val = Decimal('-1.000000000000000000000000000E+38')

    @pytest.mark.parametrize('val', [u'hello',
                                     u'x' * 5000,
                                     'x' * 9000,
                                     123,
                                     -123,
                                     123.12,
                                     -123.12,
                                     10 ** 20,
                                     10 ** 38 - 1,
                                     -10 ** 38 + 1,
                                     datetime.datetime(2011, 2, 3, 10, 11, 12, 3000),
                                     Decimal('1234.567'),
                                     Decimal('1234000'),
                                     Decimal('9' * 38),
                                     Decimal('0.' + '9' * 38),
                                     -Decimal('9' * 38),
                                     Decimal('1E10'),
                                     Decimal('1E-10'),
                                     Decimal('0.{0}1'.format('0' * 37)),
                                     None,
                                     'hello',
                                     '',
                                     pytds.Binary(b''),
                                     pytds.Binary(b'\x00\x01\x02'),
                                     pytds.Binary(b'x' * 9000),
                                     2 ** 63 - 1,
                                     False,
                                     True,
                                     uuid.uuid4(),
                                     u'Iñtërnâtiônàlizætiøn1',
                                     u'\U0001d6fc',
                                     ])
    def test_select_values(cursor, val):
>       cursor.execute('select %s', (val,))

tests\connected_test.py:384:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src\pytds\__init__.py:1019: in execute
    self._execute(operation, params)
src\pytds\__init__.py:708: in _execute
    named_params = self._session._convert_params(named_params)
src\pytds\tds.py:957: in _convert_params
    for name, value in parameters.items()]
src\pytds\tds.py:957: in <listcomp>
    for name, value in parameters.items()]
src\pytds\tds.py:946: in make_param
    column.type = self.conn.type_inferrer.from_value(value)
src\pytds\tds_types.py:2589: in from_value
    sql_type = self._from_class_value(value, type(value))
src\pytds\tds_types.py:2645: in _from_class_value
    return DecimalType.from_value(value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

cls = <class 'pytds.tds_types.DecimalType'>
value = Decimal('-1.000000000000000000000000000E+38')

    @classmethod
    def from_value(cls, value):
        if not (-10 ** 38 + 1 <= value <= 10 ** 38 - 1):
>           raise tds_base.DataError('Decimal value is out of range')
E           pytds.tds_base.DataError: Decimal value is out of range

src\pytds\tds_types.py:252: DataError
---------------------------- Captured log teardown ----------------------------
tds.py                    1142 INFO     Sending ROLLBACK TRAN
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got ENVCHANGE message
tds.py                     466 INFO     [52] got DONE/DONEINPROC/DONEPROC message
==================== 5 failed, 158 passed in 4.57 seconds =====================
codecov[bot] commented 6 years ago

Codecov Report

Merging #88 into master will increase coverage by <.01%. The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #88      +/-   ##
==========================================
+ Coverage   93.86%   93.87%   +<.01%     
==========================================
  Files          26       26              
  Lines        7797     7804       +7     
==========================================
+ Hits         7319     7326       +7     
  Misses        478      478
Impacted Files Coverage Δ
tests/connected_test.py 98.53% <100%> (+0.01%) :arrow_up:
src/pytds/__init__.py 92.91% <100%> (ø) :arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 0cdf606...90603f8. Read the comment docs.

denisenkom commented 6 years ago

Looks good, thanks!

denisenkom commented 6 years ago

It is interesting why tests failed for you. Which Python version did you use?

LHCGreg commented 6 years ago

3.6, running on Windows.