mssql-python 1.0.0a0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mssql-python might be problematic. Click here for more details.
- mssql_python/__init__.py +31 -0
- mssql_python/connection.py +302 -0
- mssql_python/constants.py +108 -0
- mssql_python/cursor.py +688 -0
- mssql_python/db_connection.py +37 -0
- mssql_python/ddbc_bindings.pyd +0 -0
- mssql_python/exceptions.py +254 -0
- mssql_python/helpers.py +99 -0
- mssql_python/libs/win/1033/msodbcsqlr18.rll +0 -0
- mssql_python/libs/win/msodbcdiag18.dll +0 -0
- mssql_python/libs/win/msodbcsql18.dll +0 -0
- mssql_python/logging_config.py +56 -0
- mssql_python/mssql_python.pyi +177 -0
- mssql_python/msvcp140.dll +0 -0
- mssql_python/testing_ddbc_bindings.py +321 -0
- mssql_python/type.py +81 -0
- mssql_python-1.0.0a0.dist-info/METADATA +24 -0
- mssql_python-1.0.0a0.dist-info/RECORD +20 -0
- mssql_python-1.0.0a0.dist-info/WHEEL +5 -0
- mssql_python-1.0.0a0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from mssql_python.exceptions import DatabaseError, InterfaceError
|
|
2
|
+
from mssql_python.connection import Connection
|
|
3
|
+
|
|
4
|
+
def connect(connection_str: str, autocommit: bool = True, **kwargs) -> Connection:
|
|
5
|
+
"""
|
|
6
|
+
Constructor for creating a connection to the database.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
connection_str (str): The connection string to connect to.
|
|
10
|
+
autocommit (bool): If True, causes a commit to be performed after each SQL statement.
|
|
11
|
+
TODO: Add the following parameters to the function signature:
|
|
12
|
+
timeout (int): The timeout for the connection attempt, in seconds.
|
|
13
|
+
readonly (bool): If True, the connection is set to read-only.
|
|
14
|
+
attrs_before (dict): A dictionary of connection attributes to set before connecting.
|
|
15
|
+
Keyword Args:
|
|
16
|
+
**kwargs: Additional key/value pairs for the connection string.
|
|
17
|
+
Below attributes are not implemented in the internal driver:
|
|
18
|
+
- encoding (str): The encoding for the connection string.
|
|
19
|
+
- ansi (bool): If True, indicates the driver does not support Unicode.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Connection: A new connection object to interact with the database.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
DatabaseError: If there is an error while trying to connect to the database.
|
|
26
|
+
InterfaceError: If there is an error related to the database interface.
|
|
27
|
+
|
|
28
|
+
This function provides a way to create a new connection object, which can then
|
|
29
|
+
be used to perform database operations such as executing queries, committing
|
|
30
|
+
transactions, and closing the connection.
|
|
31
|
+
"""
|
|
32
|
+
conn = Connection(
|
|
33
|
+
connection_str,
|
|
34
|
+
autocommit=autocommit,
|
|
35
|
+
**kwargs
|
|
36
|
+
)
|
|
37
|
+
return conn
|
|
Binary file
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
|
|
2
|
+
|
|
3
|
+
logger = get_logger()
|
|
4
|
+
|
|
5
|
+
class Exception(Exception):
|
|
6
|
+
"""
|
|
7
|
+
Base class for all DB API 2.0 exceptions.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
10
|
+
self.driver_error = driver_error
|
|
11
|
+
self.ddbc_error = truncate_error_message(ddbc_error)
|
|
12
|
+
self.message = f"Driver Error: {self.driver_error}; DDBC Error: {self.ddbc_error}"
|
|
13
|
+
super().__init__(self.message)
|
|
14
|
+
|
|
15
|
+
class Warning(Exception):
|
|
16
|
+
"""
|
|
17
|
+
Exception raised for important warnings like data truncations while inserting, etc.
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
20
|
+
super().__init__(driver_error, ddbc_error)
|
|
21
|
+
|
|
22
|
+
class Error(Exception):
|
|
23
|
+
"""
|
|
24
|
+
Base class for errors.
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
27
|
+
super().__init__(driver_error, ddbc_error)
|
|
28
|
+
|
|
29
|
+
class InterfaceError(Error):
|
|
30
|
+
"""
|
|
31
|
+
Exception raised for errors that are related to the database interface rather than the database itself.
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
34
|
+
super().__init__(driver_error, ddbc_error)
|
|
35
|
+
|
|
36
|
+
class DatabaseError(Error):
|
|
37
|
+
"""
|
|
38
|
+
Exception raised for errors that are related to the database.
|
|
39
|
+
"""
|
|
40
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
41
|
+
super().__init__(driver_error, ddbc_error)
|
|
42
|
+
|
|
43
|
+
class DataError(DatabaseError):
|
|
44
|
+
"""
|
|
45
|
+
Exception raised for errors that are due to problems with the processed data like division by zero, numeric value out of range, etc.
|
|
46
|
+
"""
|
|
47
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
48
|
+
super().__init__(driver_error, ddbc_error)
|
|
49
|
+
|
|
50
|
+
class OperationalError(DatabaseError):
|
|
51
|
+
"""
|
|
52
|
+
Exception raised for errors that are related to the database's operation and not necessarily under the control of the programmer.
|
|
53
|
+
"""
|
|
54
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
55
|
+
super().__init__(driver_error, ddbc_error)
|
|
56
|
+
|
|
57
|
+
class IntegrityError(DatabaseError):
|
|
58
|
+
"""
|
|
59
|
+
Exception raised when the relational integrity of the database is affected, e.g., a foreign key check fails.
|
|
60
|
+
"""
|
|
61
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
62
|
+
super().__init__(driver_error, ddbc_error)
|
|
63
|
+
|
|
64
|
+
class InternalError(DatabaseError):
|
|
65
|
+
"""
|
|
66
|
+
Exception raised when the database encounters an internal error, e.g., the cursor is not valid anymore, the transaction is out of sync, etc.
|
|
67
|
+
"""
|
|
68
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
69
|
+
super().__init__(driver_error, ddbc_error)
|
|
70
|
+
|
|
71
|
+
class ProgrammingError(DatabaseError):
|
|
72
|
+
"""
|
|
73
|
+
Exception raised for programming errors, e.g., table not found or already exists, syntax error in the SQL statement, wrong number of parameters specified, etc.
|
|
74
|
+
"""
|
|
75
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
76
|
+
super().__init__(driver_error, ddbc_error)
|
|
77
|
+
|
|
78
|
+
class NotSupportedError(DatabaseError):
|
|
79
|
+
"""
|
|
80
|
+
Exception raised in case a method or database API was used which is not supported by the database, e.g., requesting a .rollback() on a connection that does not support transaction or has transactions turned off.
|
|
81
|
+
"""
|
|
82
|
+
def __init__(self, driver_error, ddbc_error) -> None:
|
|
83
|
+
super().__init__(driver_error, ddbc_error)
|
|
84
|
+
|
|
85
|
+
# Mapping SQLSTATE codes to custom exception classes
|
|
86
|
+
def sqlstate_to_exception(sqlstate: str, ddbc_error: str) -> Exception:
|
|
87
|
+
"""
|
|
88
|
+
Map an SQLSTATE code to a custom exception class.
|
|
89
|
+
This function maps an SQLSTATE code to a custom exception class based on the code.
|
|
90
|
+
If the code is not found in the mapping, a generic DatabaseError class is returned
|
|
91
|
+
to handle the error.
|
|
92
|
+
Args:
|
|
93
|
+
sqlstate (str): The SQLSTATE code to map to a custom exception class.
|
|
94
|
+
Returns:
|
|
95
|
+
mapping[str, Exception]: A mapping of SQLSTATE codes to custom exception classes.
|
|
96
|
+
"""
|
|
97
|
+
mapping = {
|
|
98
|
+
'01000': Warning(driver_error="General warning", ddbc_error=ddbc_error), # General warning
|
|
99
|
+
'01001': OperationalError(driver_error="Cursor operation conflict", ddbc_error=ddbc_error), # Cursor operation conflict
|
|
100
|
+
'01002': OperationalError(driver_error="Disconnect error", ddbc_error=ddbc_error), # Disconnect error
|
|
101
|
+
'01003': DataError(driver_error="NULL value eliminated in set function", ddbc_error=ddbc_error), # NULL value eliminated in set function
|
|
102
|
+
'01004': DataError(driver_error="String data, right-truncated", ddbc_error=ddbc_error), # String data, right-truncated
|
|
103
|
+
'01006': OperationalError(driver_error="Privilege not revoked", ddbc_error=ddbc_error), # Privilege not revoked
|
|
104
|
+
'01007': OperationalError(driver_error="Privilege not granted", ddbc_error=ddbc_error), # Privilege not granted
|
|
105
|
+
'01S00': ProgrammingError(driver_error="Invalid connection string attribute", ddbc_error=ddbc_error), # Invalid connection string attribute
|
|
106
|
+
'01S01': DataError(driver_error="Error in row", ddbc_error=ddbc_error), # Error in row
|
|
107
|
+
'01S02': Warning(driver_error="Option value changed", ddbc_error=ddbc_error), # Option value changed
|
|
108
|
+
'01S06': OperationalError(driver_error="Attempt to fetch before the result set returned the first rowset", ddbc_error=ddbc_error), # Attempt to fetch before the result set returned the first rowset
|
|
109
|
+
'01S07': DataError(driver_error="Fractional truncation", ddbc_error=ddbc_error), # Fractional truncation
|
|
110
|
+
'01S08': OperationalError(driver_error="Error saving File DSN", ddbc_error=ddbc_error), # Error saving File DSN
|
|
111
|
+
'01S09': ProgrammingError(driver_error="Invalid keyword", ddbc_error=ddbc_error), # Invalid keyword
|
|
112
|
+
'07001': ProgrammingError(driver_error="Wrong number of parameters", ddbc_error=ddbc_error), # Wrong number of parameters
|
|
113
|
+
'07002': ProgrammingError(driver_error="COUNT field incorrect", ddbc_error=ddbc_error), # COUNT field incorrect
|
|
114
|
+
'07005': ProgrammingError(driver_error="Prepared statement not a cursor-specification", ddbc_error=ddbc_error), # Prepared statement not a cursor-specification
|
|
115
|
+
'07006': ProgrammingError(driver_error="Restricted data type attribute violation", ddbc_error=ddbc_error), # Restricted data type attribute violation
|
|
116
|
+
'07009': ProgrammingError(driver_error="Invalid descriptor index", ddbc_error=ddbc_error), # Invalid descriptor index
|
|
117
|
+
'07S01': ProgrammingError(driver_error="Invalid use of default parameter", ddbc_error=ddbc_error), # Invalid use of default parameter
|
|
118
|
+
'08001': OperationalError(driver_error="Client unable to establish connection", ddbc_error=ddbc_error), # Client unable to establish connection
|
|
119
|
+
'08002': OperationalError(driver_error="Connection name in use", ddbc_error=ddbc_error), # Connection name in use
|
|
120
|
+
'08003': OperationalError(driver_error="Connection not open", ddbc_error=ddbc_error), # Connection not open
|
|
121
|
+
'08004': OperationalError(driver_error="Server rejected the connection", ddbc_error=ddbc_error), # Server rejected the connection
|
|
122
|
+
'08007': OperationalError(driver_error="Connection failure during transaction", ddbc_error=ddbc_error), # Connection failure during transaction
|
|
123
|
+
'08S01': OperationalError(driver_error="Communication link failure", ddbc_error=ddbc_error), # Communication link failure
|
|
124
|
+
'21S01': ProgrammingError(driver_error="Insert value list does not match column list", ddbc_error=ddbc_error), # Insert value list does not match column list
|
|
125
|
+
'21S02': ProgrammingError(driver_error="Degree of derived table does not match column list", ddbc_error=ddbc_error), # Degree of derived table does not match column list
|
|
126
|
+
'22001': DataError(driver_error="String data, right-truncated", ddbc_error=ddbc_error), # String data, right-truncated
|
|
127
|
+
'22002': DataError(driver_error="Indicator variable required but not supplied", ddbc_error=ddbc_error), # Indicator variable required but not supplied
|
|
128
|
+
'22003': DataError(driver_error="Numeric value out of range", ddbc_error=ddbc_error), # Numeric value out of range
|
|
129
|
+
'22007': DataError(driver_error="Invalid datetime format", ddbc_error=ddbc_error), # Invalid datetime format
|
|
130
|
+
'22008': DataError(driver_error="Datetime field overflow", ddbc_error=ddbc_error), # Datetime field overflow
|
|
131
|
+
'22012': DataError(driver_error="Division by zero", ddbc_error=ddbc_error), # Division by zero
|
|
132
|
+
'22015': DataError(driver_error="Interval field overflow", ddbc_error=ddbc_error), # Interval field overflow
|
|
133
|
+
'22018': DataError(driver_error="Invalid character value for cast specification", ddbc_error=ddbc_error), # Invalid character value for cast specification
|
|
134
|
+
'22019': ProgrammingError(driver_error="Invalid escape character", ddbc_error=ddbc_error), # Invalid escape character
|
|
135
|
+
'22025': ProgrammingError(driver_error="Invalid escape sequence", ddbc_error=ddbc_error), # Invalid escape sequence
|
|
136
|
+
'22026': DataError(driver_error="String data, length mismatch", ddbc_error=ddbc_error), # String data, length mismatch
|
|
137
|
+
'23000': IntegrityError(driver_error="Integrity constraint violation", ddbc_error=ddbc_error), # Integrity constraint violation
|
|
138
|
+
'24000': ProgrammingError(driver_error="Invalid cursor state", ddbc_error=ddbc_error), # Invalid cursor state
|
|
139
|
+
'25000': OperationalError(driver_error="Invalid transaction state", ddbc_error=ddbc_error), # Invalid transaction state
|
|
140
|
+
'25S01': OperationalError(driver_error="Transaction state", ddbc_error=ddbc_error), # Transaction state
|
|
141
|
+
'25S02': OperationalError(driver_error="Transaction is still active", ddbc_error=ddbc_error), # Transaction is still active
|
|
142
|
+
'25S03': OperationalError(driver_error="Transaction is rolled back", ddbc_error=ddbc_error), # Transaction is rolled back
|
|
143
|
+
'28000': OperationalError(driver_error="Invalid authorization specification", ddbc_error=ddbc_error), # Invalid authorization specification
|
|
144
|
+
'34000': ProgrammingError(driver_error="Invalid cursor name", ddbc_error=ddbc_error), # Invalid cursor name
|
|
145
|
+
'3C000': ProgrammingError(driver_error="Duplicate cursor name", ddbc_error=ddbc_error), # Duplicate cursor name
|
|
146
|
+
'3D000': ProgrammingError(driver_error="Invalid catalog name", ddbc_error=ddbc_error), # Invalid catalog name
|
|
147
|
+
'3F000': ProgrammingError(driver_error="Invalid schema name", ddbc_error=ddbc_error), # Invalid schema name
|
|
148
|
+
'40001': OperationalError(driver_error="Serialization failure", ddbc_error=ddbc_error), # Serialization failure
|
|
149
|
+
'40002': IntegrityError(driver_error="Integrity constraint violation", ddbc_error=ddbc_error), # Integrity constraint violation
|
|
150
|
+
'40003': OperationalError(driver_error="Statement completion unknown", ddbc_error=ddbc_error), # Statement completion unknown
|
|
151
|
+
'42000': ProgrammingError(driver_error="Syntax error or access violation", ddbc_error=ddbc_error), # Syntax error or access violation
|
|
152
|
+
'42S01': ProgrammingError(driver_error="Base table or view already exists", ddbc_error=ddbc_error), # Base table or view already exists
|
|
153
|
+
'42S02': ProgrammingError(driver_error="Base table or view not found", ddbc_error=ddbc_error), # Base table or view not found
|
|
154
|
+
'42S11': ProgrammingError(driver_error="Index already exists", ddbc_error=ddbc_error), # Index already exists
|
|
155
|
+
'42S12': ProgrammingError(driver_error="Index not found", ddbc_error=ddbc_error), # Index not found
|
|
156
|
+
'42S21': ProgrammingError(driver_error="Column already exists", ddbc_error=ddbc_error), # Column already exists
|
|
157
|
+
'42S22': ProgrammingError(driver_error="Column not found", ddbc_error=ddbc_error), # Column not found
|
|
158
|
+
'44000': IntegrityError(driver_error="WITH CHECK OPTION violation", ddbc_error=ddbc_error), # WITH CHECK OPTION violation
|
|
159
|
+
'HY000': OperationalError(driver_error="General error", ddbc_error=ddbc_error), # General error
|
|
160
|
+
'HY001': OperationalError(driver_error="Memory allocation error", ddbc_error=ddbc_error), # Memory allocation error
|
|
161
|
+
'HY003': ProgrammingError(driver_error="Invalid application buffer type", ddbc_error=ddbc_error), # Invalid application buffer type
|
|
162
|
+
'HY004': ProgrammingError(driver_error="Invalid SQL data type", ddbc_error=ddbc_error), # Invalid SQL data type
|
|
163
|
+
'HY007': ProgrammingError(driver_error="Associated statement is not prepared", ddbc_error=ddbc_error), # Associated statement is not prepared
|
|
164
|
+
'HY008': OperationalError(driver_error="Operation canceled", ddbc_error=ddbc_error), # Operation canceled
|
|
165
|
+
'HY009': ProgrammingError(driver_error="Invalid use of null pointer", ddbc_error=ddbc_error), # Invalid use of null pointer
|
|
166
|
+
'HY010': ProgrammingError(driver_error="Function sequence error", ddbc_error=ddbc_error), # Function sequence error
|
|
167
|
+
'HY011': ProgrammingError(driver_error="Attribute cannot be set now", ddbc_error=ddbc_error), # Attribute cannot be set now
|
|
168
|
+
'HY012': ProgrammingError(driver_error="Invalid transaction operation code", ddbc_error=ddbc_error), # Invalid transaction operation code
|
|
169
|
+
'HY013': OperationalError(driver_error="Memory management error", ddbc_error=ddbc_error), # Memory management error
|
|
170
|
+
'HY014': OperationalError(driver_error="Limit on the number of handles exceeded", ddbc_error=ddbc_error), # Limit on the number of handles exceeded
|
|
171
|
+
'HY015': ProgrammingError(driver_error="No cursor name available", ddbc_error=ddbc_error), # No cursor name available
|
|
172
|
+
'HY016': ProgrammingError(driver_error="Cannot modify an implementation row descriptor", ddbc_error=ddbc_error), # Cannot modify an implementation row descriptor
|
|
173
|
+
'HY017': ProgrammingError(driver_error="Invalid use of an automatically allocated descriptor handle", ddbc_error=ddbc_error), # Invalid use of an automatically allocated descriptor handle
|
|
174
|
+
'HY018': OperationalError(driver_error="Server declined cancel request", ddbc_error=ddbc_error), # Server declined cancel request
|
|
175
|
+
'HY019': DataError(driver_error="Non-character and non-binary data sent in pieces", ddbc_error=ddbc_error), # Non-character and non-binary data sent in pieces
|
|
176
|
+
'HY020': DataError(driver_error="Attempt to concatenate a null value", ddbc_error=ddbc_error), # Attempt to concatenate a null value
|
|
177
|
+
'HY021': ProgrammingError(driver_error="Inconsistent descriptor information", ddbc_error=ddbc_error), # Inconsistent descriptor information
|
|
178
|
+
'HY024': ProgrammingError(driver_error="Invalid attribute value", ddbc_error=ddbc_error), # Invalid attribute value
|
|
179
|
+
'HY090': ProgrammingError(driver_error="Invalid string or buffer length", ddbc_error=ddbc_error), # Invalid string or buffer length
|
|
180
|
+
'HY091': ProgrammingError(driver_error="Invalid descriptor field identifier", ddbc_error=ddbc_error), # Invalid descriptor field identifier
|
|
181
|
+
'HY092': ProgrammingError(driver_error="Invalid attribute/option identifier", ddbc_error=ddbc_error), # Invalid attribute/option identifier
|
|
182
|
+
'HY095': ProgrammingError(driver_error="Function type out of range", ddbc_error=ddbc_error), # Function type out of range
|
|
183
|
+
'HY096': ProgrammingError(driver_error="Invalid information type", ddbc_error=ddbc_error), # Invalid information type
|
|
184
|
+
'HY097': ProgrammingError(driver_error="Column type out of range", ddbc_error=ddbc_error), # Column type out of range
|
|
185
|
+
'HY098': ProgrammingError(driver_error="Scope type out of range", ddbc_error=ddbc_error), # Scope type out of range
|
|
186
|
+
'HY099': ProgrammingError(driver_error="Nullable type out of range", ddbc_error=ddbc_error), # Nullable type out of range
|
|
187
|
+
'HY100': ProgrammingError(driver_error="Uniqueness option type out of range", ddbc_error=ddbc_error), # Uniqueness option type out of range
|
|
188
|
+
'HY101': ProgrammingError(driver_error="Accuracy option type out of range", ddbc_error=ddbc_error), # Accuracy option type out of range
|
|
189
|
+
'HY103': ProgrammingError(driver_error="Invalid retrieval code", ddbc_error=ddbc_error), # Invalid retrieval code
|
|
190
|
+
'HY104': ProgrammingError(driver_error="Invalid precision or scale value", ddbc_error=ddbc_error), # Invalid precision or scale value
|
|
191
|
+
'HY105': ProgrammingError(driver_error="Invalid parameter type", ddbc_error=ddbc_error), # Invalid parameter type
|
|
192
|
+
'HY106': ProgrammingError(driver_error="Fetch type out of range", ddbc_error=ddbc_error), # Fetch type out of range
|
|
193
|
+
'HY107': ProgrammingError(driver_error="Row value out of range", ddbc_error=ddbc_error), # Row value out of range
|
|
194
|
+
'HY109': ProgrammingError(driver_error="Invalid cursor position", ddbc_error=ddbc_error), # Invalid cursor position
|
|
195
|
+
'HY110': ProgrammingError(driver_error="Invalid driver completion", ddbc_error=ddbc_error), # Invalid driver completion
|
|
196
|
+
'HY111': ProgrammingError(driver_error="Invalid bookmark value", ddbc_error=ddbc_error), # Invalid bookmark value
|
|
197
|
+
'HYC00': NotSupportedError(driver_error="Optional feature not implemented", ddbc_error=ddbc_error), # Optional feature not implemented
|
|
198
|
+
'HYT00': OperationalError(driver_error="Timeout expired", ddbc_error=ddbc_error), # Timeout expired
|
|
199
|
+
'HYT01': OperationalError(driver_error="Connection timeout expired", ddbc_error=ddbc_error), # Connection timeout expired
|
|
200
|
+
'IM001': NotSupportedError(driver_error="Driver does not support this function", ddbc_error=ddbc_error), # Driver does not support this function
|
|
201
|
+
'IM002': OperationalError(driver_error="Data source name not found and no default driver specified", ddbc_error=ddbc_error), # Data source name not found and no default driver specified
|
|
202
|
+
'IM003': OperationalError(driver_error="Specified driver could not be loaded", ddbc_error=ddbc_error), # Specified driver could not be loaded
|
|
203
|
+
'IM004': OperationalError(driver_error="Driver's SQLAllocHandle on SQL_HANDLE_ENV failed", ddbc_error=ddbc_error), # Driver's SQLAllocHandle on SQL_HANDLE_ENV failed
|
|
204
|
+
'IM005': OperationalError(driver_error="Driver's SQLAllocHandle on SQL_HANDLE_DBC failed", ddbc_error=ddbc_error), # Driver's SQLAllocHandle on SQL_HANDLE_DBC failed
|
|
205
|
+
'IM006': OperationalError(driver_error="Driver's SQLSetConnectAttr failed", ddbc_error=ddbc_error), # Driver's SQLSetConnectAttr failed
|
|
206
|
+
'IM007': OperationalError(driver_error="No data source or driver specified; dialog prohibited", ddbc_error=ddbc_error), # No data source or driver specified; dialog prohibited
|
|
207
|
+
'IM008': OperationalError(driver_error="Dialog failed", ddbc_error=ddbc_error), # Dialog failed
|
|
208
|
+
'IM009': OperationalError(driver_error="Unable to load translation DLL", ddbc_error=ddbc_error), # Unable to load translation DLL
|
|
209
|
+
'IM010': OperationalError(driver_error="Data source name too long", ddbc_error=ddbc_error), # Data source name too long
|
|
210
|
+
'IM011': OperationalError(driver_error="Driver name too long", ddbc_error=ddbc_error), # Driver name too long
|
|
211
|
+
'IM012': OperationalError(driver_error="DRIVER keyword syntax error", ddbc_error=ddbc_error), # DRIVER keyword syntax error
|
|
212
|
+
'IM013': OperationalError(driver_error="Trace file error", ddbc_error=ddbc_error), # Trace file error
|
|
213
|
+
'IM014': OperationalError(driver_error="Invalid name of File DSN", ddbc_error=ddbc_error), # Invalid name of File DSN
|
|
214
|
+
'IM015': OperationalError(driver_error="Corrupt file data source", ddbc_error=ddbc_error), # Corrupt file data source
|
|
215
|
+
}
|
|
216
|
+
return mapping.get(sqlstate, None)
|
|
217
|
+
|
|
218
|
+
def truncate_error_message(error_message: str) -> str:
|
|
219
|
+
'''
|
|
220
|
+
- The Driver Error message is the message that is returned by the Internal driver.
|
|
221
|
+
- This section will always be at the start of the message.
|
|
222
|
+
'''
|
|
223
|
+
try:
|
|
224
|
+
if not error_message.startswith('[Microsoft]'):
|
|
225
|
+
# The message is not from the driver, so no need to truncate
|
|
226
|
+
return error_message
|
|
227
|
+
string_first = error_message[:error_message.index(']')+1]
|
|
228
|
+
string_second = error_message[error_message.index(']')+1:]
|
|
229
|
+
string_third = string_second[string_second.index(']')+1:]
|
|
230
|
+
return string_first + string_third
|
|
231
|
+
except Exception as e:
|
|
232
|
+
if ENABLE_LOGGING:
|
|
233
|
+
logger.error(f"Error while truncating error message: {e}")
|
|
234
|
+
return error_message
|
|
235
|
+
|
|
236
|
+
def raise_exception(sqlstate: str, ddbc_error: str) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Raise a custom exception based on the given SQLSTATE code.
|
|
239
|
+
This function raises a custom exception based on the provided SQLSTATE code.
|
|
240
|
+
If the code is not found in the mapping, a generic DatabaseError is raised.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
sqlstate (str): The SQLSTATE code to map to a custom exception.
|
|
244
|
+
ddbc_error (str): The DDBC error message.
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
DatabaseError: If the SQLSTATE code is not found in the mapping.
|
|
248
|
+
"""
|
|
249
|
+
exception_class = sqlstate_to_exception(sqlstate, ddbc_error)
|
|
250
|
+
if exception_class:
|
|
251
|
+
if ENABLE_LOGGING:
|
|
252
|
+
logger.error(exception_class)
|
|
253
|
+
raise exception_class
|
|
254
|
+
raise DatabaseError(driver_error="An error occurred with SQLSTATE code", ddbc_error=f"Unknown DDBC error: {sqlstate}")
|
mssql_python/helpers.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from mssql_python import ddbc_bindings
|
|
2
|
+
from mssql_python.exceptions import raise_exception
|
|
3
|
+
from mssql_python.logging_config import get_logger, ENABLE_LOGGING
|
|
4
|
+
|
|
5
|
+
logger = get_logger()
|
|
6
|
+
|
|
7
|
+
def add_driver_to_connection_str(connection_str):
|
|
8
|
+
"""
|
|
9
|
+
Add the ODBC driver to the connection string if not present.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
connection_str (str): The original connection string.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: The connection string with the ODBC driver added.
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
Exception: If the connection string is invalid.
|
|
19
|
+
"""
|
|
20
|
+
driver_name = 'Driver={ODBC Driver 18 for SQL Server}'
|
|
21
|
+
try:
|
|
22
|
+
# Strip any leading or trailing whitespace from the connection string
|
|
23
|
+
connection_str = connection_str.strip()
|
|
24
|
+
connection_str = add_driver_name_to_app_parameter(connection_str)
|
|
25
|
+
|
|
26
|
+
# Split the connection string into individual attributes
|
|
27
|
+
connection_attributes = connection_str.split(';')
|
|
28
|
+
final_connection_attributes = []
|
|
29
|
+
|
|
30
|
+
# Iterate through the attributes and exclude any existing driver attribute
|
|
31
|
+
for attribute in connection_attributes:
|
|
32
|
+
if attribute.lower().split('=')[0] == 'driver':
|
|
33
|
+
continue
|
|
34
|
+
final_connection_attributes.append(attribute)
|
|
35
|
+
|
|
36
|
+
# Join the remaining attributes back into a connection string
|
|
37
|
+
connection_str = ';'.join(final_connection_attributes)
|
|
38
|
+
|
|
39
|
+
# Insert the driver attribute at the beginning of the connection string
|
|
40
|
+
final_connection_attributes.insert(0, driver_name)
|
|
41
|
+
connection_str = ';'.join(final_connection_attributes)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
raise Exception(("Invalid connection string, Please follow the format: "
|
|
44
|
+
"Server=server_name;Database=database_name;UID=user_name;PWD=password"))
|
|
45
|
+
|
|
46
|
+
return connection_str
|
|
47
|
+
|
|
48
|
+
def check_error(handle_type, handle, ret):
|
|
49
|
+
"""
|
|
50
|
+
Check for errors and raise an exception if an error is found.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
handle_type: The type of the handle (e.g., SQL_HANDLE_ENV, SQL_HANDLE_DBC).
|
|
54
|
+
handle: The handle to check for errors.
|
|
55
|
+
ret: The return code from the ODBC function call.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
RuntimeError: If an error is found.
|
|
59
|
+
"""
|
|
60
|
+
if ret < 0:
|
|
61
|
+
error_info = ddbc_bindings.DDBCSQLCheckError(handle_type, handle, ret)
|
|
62
|
+
if ENABLE_LOGGING:
|
|
63
|
+
logger.error(f"Error: {error_info.ddbcErrorMsg}")
|
|
64
|
+
raise_exception(error_info.sqlState, error_info.ddbcErrorMsg)
|
|
65
|
+
|
|
66
|
+
def add_driver_name_to_app_parameter(connection_string):
|
|
67
|
+
"""
|
|
68
|
+
Modifies the input connection string by appending the APP name.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
connection_string (str): The input connection string.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
str: The modified connection string.
|
|
75
|
+
"""
|
|
76
|
+
# Split the input string into key-value pairs
|
|
77
|
+
parameters = connection_string.split(';')
|
|
78
|
+
|
|
79
|
+
# Initialize variables
|
|
80
|
+
app_found = False
|
|
81
|
+
modified_parameters = []
|
|
82
|
+
|
|
83
|
+
# Iterate through the key-value pairs
|
|
84
|
+
for param in parameters:
|
|
85
|
+
if param.lower().startswith("app="):
|
|
86
|
+
# Overwrite the value with 'MSSQL-Python'
|
|
87
|
+
app_found = True
|
|
88
|
+
key, value = param.split('=', 1)
|
|
89
|
+
modified_parameters.append(f"{key}=MSSQL-Python")
|
|
90
|
+
else:
|
|
91
|
+
# Keep other parameters as is
|
|
92
|
+
modified_parameters.append(param)
|
|
93
|
+
|
|
94
|
+
# If APP key is not found, append it
|
|
95
|
+
if not app_found:
|
|
96
|
+
modified_parameters.append("APP=MSSQL-Python")
|
|
97
|
+
|
|
98
|
+
# Join the parameters back into a connection string
|
|
99
|
+
return ';'.join(modified_parameters) + ';'
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from logging.handlers import RotatingFileHandler
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
ENABLE_LOGGING = False
|
|
6
|
+
|
|
7
|
+
def setup_logging(mode='file', log_level=logging.DEBUG):
|
|
8
|
+
"""
|
|
9
|
+
Set up logging configuration.
|
|
10
|
+
|
|
11
|
+
This method configures the logging settings for the application.
|
|
12
|
+
It sets the log level, format, and log file location.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
log_level (int): The logging level (default: logging.INFO).
|
|
16
|
+
"""
|
|
17
|
+
global ENABLE_LOGGING
|
|
18
|
+
ENABLE_LOGGING = True
|
|
19
|
+
|
|
20
|
+
# Create a logger for mssql_python module
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
logger.setLevel(log_level)
|
|
23
|
+
|
|
24
|
+
# Construct the path to the log file
|
|
25
|
+
# TODO: Use a different dir to dump log file
|
|
26
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
27
|
+
log_file = os.path.join(current_dir, 'mssql_python_trace_{pid}.log'.format(pid=os.getpid()))
|
|
28
|
+
|
|
29
|
+
# Create a log handler to log to driver specific file
|
|
30
|
+
# By default we only want to log to a file, max size 500MB, and keep 5 backups
|
|
31
|
+
# TODO: Rotate files based on time too? Ex: everyday
|
|
32
|
+
file_handler = RotatingFileHandler(log_file, maxBytes=512*1024*1024, backupCount=5)
|
|
33
|
+
file_handler.setLevel(log_level)
|
|
34
|
+
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(message)s')
|
|
35
|
+
file_handler.setFormatter(formatter)
|
|
36
|
+
logger.addHandler(file_handler)
|
|
37
|
+
|
|
38
|
+
if mode == 'stdout':
|
|
39
|
+
# If the mode is stdout, then we want to log to the console as well
|
|
40
|
+
stdout_handler = logging.StreamHandler()
|
|
41
|
+
stdout_handler.setLevel(log_level)
|
|
42
|
+
stdout_handler.setFormatter(formatter)
|
|
43
|
+
logger.addHandler(stdout_handler)
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(f'Invalid logging mode: {mode}')
|
|
46
|
+
|
|
47
|
+
def get_logger():
|
|
48
|
+
"""
|
|
49
|
+
Get the logger instance.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
logging.Logger: The logger instance.
|
|
53
|
+
"""
|
|
54
|
+
if not ENABLE_LOGGING:
|
|
55
|
+
return None
|
|
56
|
+
return logging.getLogger(__name__)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from typing import (
|
|
2
|
+
Final, Union
|
|
3
|
+
)
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
# GLOBALS
|
|
7
|
+
# Read-Only
|
|
8
|
+
apilevel: Final[str] = '2.0'
|
|
9
|
+
paramstyle: Final[str] = 'pyformat'
|
|
10
|
+
threadsafety: Final[int] = 1
|
|
11
|
+
|
|
12
|
+
# Type Objects
|
|
13
|
+
# https://www.python.org/dev/peps/pep-0249/#type-objects
|
|
14
|
+
class STRING:
|
|
15
|
+
"""
|
|
16
|
+
This type object is used to describe columns in a database that are string-based (e.g. CHAR).
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self) -> None: ...
|
|
19
|
+
|
|
20
|
+
class BINARY:
|
|
21
|
+
"""
|
|
22
|
+
This type object is used to describe (long) binary columns in a database (e.g. LONG, RAW, BLOBs).
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self) -> None: ...
|
|
25
|
+
|
|
26
|
+
class NUMBER:
|
|
27
|
+
"""
|
|
28
|
+
This type object is used to describe numeric columns in a database.
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self) -> None: ...
|
|
31
|
+
|
|
32
|
+
class DATETIME:
|
|
33
|
+
"""
|
|
34
|
+
This type object is used to describe date/time columns in a database.
|
|
35
|
+
"""
|
|
36
|
+
def __init__(self) -> None: ...
|
|
37
|
+
|
|
38
|
+
class ROWID:
|
|
39
|
+
"""
|
|
40
|
+
This type object is used to describe the “Row ID” column in a database.
|
|
41
|
+
"""
|
|
42
|
+
def __init__(self) -> None: ...
|
|
43
|
+
|
|
44
|
+
# Type Constructors
|
|
45
|
+
def Date(year: int, month: int, day: int) -> datetime.date: ...
|
|
46
|
+
def Time(hour: int, minute: int, second: int) -> datetime.time: ...
|
|
47
|
+
def Timestamp(year: int, month: int, day: int, hour: int, minute: int, second: int) -> datetime.datetime: ...
|
|
48
|
+
def DateFromTicks(ticks: int) -> datetime.date: ...
|
|
49
|
+
def TimeFromTicks(ticks: int) -> datetime.time: ...
|
|
50
|
+
def TimestampFromTicks(ticks: int) -> datetime.datetime: ...
|
|
51
|
+
def Binary(string: str) -> bytes: ...
|
|
52
|
+
|
|
53
|
+
# Exceptions
|
|
54
|
+
# https://www.python.org/dev/peps/pep-0249/#exceptions
|
|
55
|
+
class Warning(Exception): ...
|
|
56
|
+
class Error(Exception): ...
|
|
57
|
+
class InterfaceError(Error): ...
|
|
58
|
+
class DatabaseError(Error): ...
|
|
59
|
+
class DataError(DatabaseError): ...
|
|
60
|
+
class OperationalError(DatabaseError): ...
|
|
61
|
+
class IntegrityError(DatabaseError): ...
|
|
62
|
+
class InternalError(DatabaseError): ...
|
|
63
|
+
class ProgrammingError(DatabaseError): ...
|
|
64
|
+
class NotSupportedError(DatabaseError): ...
|
|
65
|
+
|
|
66
|
+
# Connection Objects
|
|
67
|
+
class Connection:
|
|
68
|
+
"""
|
|
69
|
+
Connection object for interacting with the database.
|
|
70
|
+
|
|
71
|
+
https://www.python.org/dev/peps/pep-0249/#connection-objects
|
|
72
|
+
|
|
73
|
+
This class should not be instantiated directly, instead call global connect() method to
|
|
74
|
+
create a Connection object.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def cursor(self) -> 'Cursor':
|
|
78
|
+
"""
|
|
79
|
+
Return a new Cursor object using the connection.
|
|
80
|
+
"""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
def commit(self) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Commit the current transaction.
|
|
86
|
+
"""
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
def rollback(self) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Roll back the current transaction.
|
|
92
|
+
"""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
def close(self) -> None:
|
|
96
|
+
"""
|
|
97
|
+
Close the connection now.
|
|
98
|
+
"""
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
# Cursor Objects
|
|
102
|
+
class Cursor:
|
|
103
|
+
"""
|
|
104
|
+
Cursor object for executing SQL queries and fetching results.
|
|
105
|
+
|
|
106
|
+
https://www.python.org/dev/peps/pep-0249/#cursor-objects
|
|
107
|
+
|
|
108
|
+
This class should not be instantiated directly, instead call cursor() from a Connection
|
|
109
|
+
object to create a Cursor object.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def callproc(self, procname: str, parameters: Union[None, list] = None) -> Union[None, list]:
|
|
113
|
+
"""
|
|
114
|
+
Call a stored database procedure with the given name.
|
|
115
|
+
"""
|
|
116
|
+
...
|
|
117
|
+
|
|
118
|
+
def close(self) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Close the cursor now.
|
|
121
|
+
"""
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
def execute(self, operation: str, parameters: Union[None, list, dict] = None) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Prepare and execute a database operation (query or command).
|
|
127
|
+
"""
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
def executemany(self, operation: str, seq_of_parameters: list) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Prepare a database operation and execute it against all parameter sequences.
|
|
133
|
+
"""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
def fetchone(self) -> Union[None, tuple]:
|
|
137
|
+
"""
|
|
138
|
+
Fetch the next row of a query result set.
|
|
139
|
+
"""
|
|
140
|
+
...
|
|
141
|
+
|
|
142
|
+
def fetchmany(self, size: int = None) -> list:
|
|
143
|
+
"""
|
|
144
|
+
Fetch the next set of rows of a query result.
|
|
145
|
+
"""
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
def fetchall(self) -> list:
|
|
149
|
+
"""
|
|
150
|
+
Fetch all (remaining) rows of a query result.
|
|
151
|
+
"""
|
|
152
|
+
...
|
|
153
|
+
|
|
154
|
+
def nextset(self) -> Union[None, bool]:
|
|
155
|
+
"""
|
|
156
|
+
Skip to the next available result set.
|
|
157
|
+
"""
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
def setinputsizes(self, sizes: list) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Predefine memory areas for the operation’s parameters.
|
|
163
|
+
"""
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
def setoutputsize(self, size: int, column: int = None) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Set a column buffer size for fetches of large columns.
|
|
169
|
+
"""
|
|
170
|
+
...
|
|
171
|
+
|
|
172
|
+
# Module Functions
|
|
173
|
+
def connect(connection_str: str) -> Connection:
|
|
174
|
+
"""
|
|
175
|
+
Constructor for creating a connection to the database.
|
|
176
|
+
"""
|
|
177
|
+
...
|
|
Binary file
|