New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cursor.columns doesn't return column names #506
Comments
I was able to partially reproduce the issue under 64-bit Python 2.7.14 on Windows 7 using the "SQL Server" ODBC driver (SQLSRV32.DLL). With pyodbc 4.0.25, One difference I noticed is that the
whereas with 4.0.25 the call included a
Nothing else in the ODBC logs looked suspicious me, except of course that when 4.0.24 went to retrieve the actual column information it succeeded
while 4.0.25 was told that there was no data to retrieve
|
I have the same issue using Python 3.6.8 on Windows 64-bit and MS-SQL (SQL server 2012). Makes 4.0.25 pretty much unusable for me. 4.0.24 works fine though, Possibly this comment should be on #501 though, those two bugs seem pretty similar. |
Additional information re: my earlier test: This code tests for i in range(1, 28):
table_name = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'[:i]
crsr = cnxn.cursor()
crsr.execute("""\
BEGIN TRY
DROP TABLE {0};
END TRY
BEGIN CATCH
END CATCH
CREATE TABLE {0} (id INT PRIMARY KEY);
""".format(table_name))
col_count = len([col.column_name for col in crsr.columns(table_name)])
print('table [{}] ({} characters): {} columns{}'.format(table_name, i, col_count, ' <-' if col_count == 0 else ''))
# print(col_list)
crsr.execute("DROP TABLE {};".format(table_name)) The results are very consistent. 4.0.25 returns an empty list when the length of the table name is 1, 4, 5, 6, 8, 17, 21, 24, 25, or 26 characters. Examples of the failing entries in the ODBC log are
while the parameters for a successful call look like
|
Extra additional information: Repeated the above test with 4.0.25 under Python 3.7. The results are less consistent and the failing name lengths are different: 1 (usually, but not always), 4 (usually, but not always), 5, 8, 9, 12, 17, 20, 21, 22, 24, or 25 characters. |
This was noticed in Cursor.columns, but I'm sure it affects a couple of other places in the code. Unfortunately the solution is hideously inefficient. I tried not null terminating, but I'm afraid driver differences when not using SQLWCHAR, such as drivers that want UTF8, will cause non-stop errors. Suggestions for a more efficient implementation are welcome. Alternatively, if someone wants to try converting to using lengths instead of null terminated strings *and* testing it, I'd be interested.
I got to the bottom of this - the Unicode encoding doesn't automatically add null terminators. Encoding to UTF8 does, but not named encodings. In particular, we need two nulls at the end of a UTF16 string and 4 for UTF32. There doesn't seem to be a way to force nulls on the end during conversion. My first thought was to not use null terminators and pass string lengths into the ODBC APIs. This is easy to do when following the specification, but a big part of pyodbc is dealing with non-standard drivers like MySQL and PostgreSQL that will want UTF8. What length would they want - characters or bytes? And what about all of the other drivers out there that I can't test? In the end, I bit the bullet and concatenated 4 nulls on to the end of encoded bytes which is a memory copy. It isn't used for data we're reading, just column names, so I think it is acceptable. @gordthompson Does this work for you? |
Also, I could not figure out how to make it fail in a unit test. If you can come up with one, I'd love to add it. I could get to fail every time by using a separate test file, but could not ever get it to fail in the SQL Server unit tests. |
This was noticed in Cursor.columns, but I'm sure it affects a couple of other places in the code. Unfortunately the solution is hideously inefficient. I tried not null terminating, but I'm afraid driver differences when not using SQLWCHAR, such as drivers that want UTF8, will cause non-stop errors. Suggestions for a more efficient implementation are welcome. Alternatively, if someone wants to try converting to using lengths instead of null terminated strings *and* testing it, I'd be interested.
Is this issue really fixed? I could not get python 3.6/3.8 + pyodbc==4.0.30 to work. Example: I have 127 tables I was researching. On 62,
I used conda to downgrade python to 2.7 and did a How should I structure my code in python 3.x to work? |
Do you have an ODBC trace, both of the working and non-working cases? |
This is bizarre -- I cannot get this to successfully run again. I got all 62 successfully one time and now I cannot reproduce it. I've created new conda environments and reinstalled the exact versions I used earlier (I still have the window open), and it repeatedly fails. |
In any case, please provide an ODBC trace and more details on your environment. |
Doh, I found my problem. Attached are 3 logs: |
To be clear, I found my problem in reproducing the issue. IMO, this issue is still open and the above logs show how it works in 2.7/4.0.24 and the same code fails in 3.7/4.0.30. |
Also, I've attached the function that I'm using in both scenarios |
Your success log appears to be Windows and the fail log is Linux/Mac. If you change more than one variable it becomes harder to diagnose. Please provide more details about your environment. |
This is definitely still a problem for me on Windows 10, running Python 2.7.18 (not anaconda) with SQL Server 2016 and SSMS 18 on the local box. I ran the code in the following example as follows: 24 is perfect, 25 crashes and 30 does not produce output from raw command prompt but if ran inside git bash, results in a segmentation fault. What other information could I provide to make this helpful? |
You can provide an ODBC trace. |
I am sorry, I had to learn how to turn on the ODBC trace. Here is the updated archive with ODBC trace logs. |
24.trace.log:372 (in SQLColumnsW call)
25.trace.log:372 (in SQLColumnsW call)
|
Just FYI, there's a test for |
Hmm. I guess I forgot to add it to tests2 when I submitted that test. At least I don't remember purposely omitting it from tests2 for any particular reason. Oh well, Python_2 is done now so if it works for Python_3 that's good enough for me. |
Is there still a chance to please fix this for Python 2.7.18? |
Even if this was fixed, @gt6989b , the reality is it probably won't be fixed soon. In the meantime, perhaps you can call the metadata tables directly for the information you're looking for, e.g.:
|
@keitherskine thank you so much. For now, I downgraded to version 24, which does not have the bug :) Hope this will get done at some point so we can upgrade the package. Does the same problem now work on Py3? |
@keitherskine thanks for the workaround. I was completely baffled as to why a MSSQL table (using the SQL Native ODBC driver) wasn't showing any columns when it's supposed to have two. Using this workaround to bypass this issue. |
Could we possibly reopen this issue so it may get fixed at some point? Thank you. |
You are free to open a new issue, as the cause may not be the same as in the original one (which was fixed). |
Environment
Issue
This issue came up with version 4.0.25 of pyodbc on Python 2.7.15, 32 bit on Windows 10 and was not present in version 4.0.24. The cursor.columns() call intermittently fails to return the columns in a table. To exercise the bug, when I create table names with the following lengths (and sometimes other lengths) as such:
And then I try and get the list of column names for those tables as such:
The tables of seven characters return an empty result set the first time they are called, then return properly after.
The tables with one character names return empty result sets the first two times they are called, then a proper result set, then repeat failing twice in a row and succeeding once as you make more calls.
Unlike issue #501, I cannot replicate this on Python 3.7.2
The text was updated successfully, but these errors were encountered: