zillow / ctds

Python DB-API 2.0 library for MS SQL Server
MIT License
83 stars 12 forks source link

Recieving _tds.OperationalError instead of expected _tds.IntegrityError #95

Open ShishkovSpace opened 2 years ago

ShishkovSpace commented 2 years ago

Hi,

Faced an unexpected error trace during inserting NULL value into table column which has a NOT NULL constraint and rightmost in table. In scenario above i've expected to recieve _tds.IntegrityError with error trace about NOT NULL constraint but instead of it i'm recieving: _tds.OperationalError: While reading current row from host, a premature end-of-message was encountered--an incoming data stream was interrupted when the server expected to see more data. The host program may have terminated. Ensure that you are using a supported client application programming interface (API).

Here is a Python code to reproduce it:

from typing import Optional
import ctds

class CtdsObj(object):
    """
    Ctds object

    SQL target table:
    CREATE TABLE dbo.ctds_test (
        first_col INT NULL
        , second_col VARCHAR(20) NOT NULL
        , third_col INT NOT NULL
    );

    SQL Server version - 14.0.3048.4
    ctds version - 1.12.0
    Python 3.8
    Windows 10 64 bit
    """
    def __init__(self):
        self._connection: Optional[ctds.Connection] = None
        self.data: list = [
            {
                "first_col": 1,
                "second_col": "any",
                "third_col": None
            },
            {
                "first_col": 2,
                "second_col": "some",
                "third_col": 3
            }
        ]

    def start(self):
        self.db_inserter(self.data, "dbo.ctds_test")

    @property
    def connection(self) -> ctds.Connection:
        _params: dict = {
            "server": "any-server",
            "instance": "any-server-instance",
            "database": "test",
            "timeout": 10000,
            "login_timeout": 10,
            "user": "any_user",
            "password": "****"
        }
        if not self.is_open:
            self._connection = ctds.connect(**_params)
        return self._connection

    @property
    def is_open(self):
        return (
                self._connection is not None
                and self._connection.spid is not None
                and self._connection.spid > 0
        )

    def db_inserter(self, data, table):
        self.connection.bulk_insert(str(table), data, batch_size=10000, tablock=True)
        self.connection.commit()
        self.connection.close()

if __name__ == '__main__':
    CtdsObj().start()

If i'm trying to insert NULL value into any other not rightmost column with NOT NULL constraint i recieve expected error trace: _tds.IntegrityError: Cannot insert the value NULL into column 'second_col', table 'test.dbo.ctds_test'; column does not allow nulls. INSERT fails.

I guess this behaviour is not correct and in this scenario ctds should message about NOT NULL constraint violation. Or problem above already fixed/excluded for higher ctds versions (i'm using 1.10.0 or 1.12.0 in projects)? Or maybe there are some workarounds for it how it should be handled?

P.S. Hope you are still supporting project and repository, because its hard to find any alternatives with bulk operations for SQL Server via python other than ctds!)

joshuahlang commented 2 years ago

Well I haven't looked at this project in a while and no longer have write access to it since I left Zillow. I can probably ask around and get back on the project though.

There does seem to be some issue as I can get a seg fault on OS X with similar inputs to yours. I'll see if I can find some time to look into it more in the coming week.

ShishkovSpace commented 2 years ago

It would be great.

Thanks a lot!