Не удается загрузить записи с текстовыми полями длиной более 19455 символов в Django с помощью pyodbc

У меня есть такая модель Note (это извлекается из устаревшей базы данных MS SQL Server, поэтому большинство этих записей не были созданы Django):

class Note(models.Model):
    id = models.AutoField(primary_key=True, db_column="note_id")
    content = models.TextField(db_column="note_content", blank=True, null=True)
    date_created = models.DateTimeField(db_column="date_created", auto_now_add=True)
    date_modified = models.DateTimeField(db_column="date_modified", null=True, blank=True)
    date_removed = models.DateTimeField(db_column="date_deleted", null=True, blank=True)

Запуск .get для некоторых записей возвращал DoesNotExist, даже если они существовали в базе данных.

Оказывается, это происходит, когда длина содержимого поля MS SQL Server TEXT (например, CREATE TABLE Foo ( content TEXT null)) превышает определенную величину; конкретно 19455 символов.

Вот как это выглядит в действии:

>>> note = Note.objects.get(pk=1)
>>> note.content = "x" * 19455
>>> note.save()
>>> note = Note.objects.get(pk=1)
>>> note.content = "x" * 19456
>>> note.save()
>>> note = Note.objects.get(pk=1)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/path/to/django/db/models/query.py", line 366, in get
    % self.model._meta.object_name)
DoesNotExist: Note matching query does not exist.

Я использую FreeTDS, и размер текста установлен на 2147483647, что похоже является верхним пределом для версии MS SQL Server, которую я использую.

Согласно этому другому вопросу об усечении, вы должны добавить инструкцию SET TEXTSIZE n, где n является значением в байтах, и это решит проблему усечения данных. Поэтому я задавался вопросом, происходит ли это в моем случае, и исправит ли это это.

Итак, я пошел дальше и написал код, который использует только курсор и команду SET TEXTSIZE.

Во-первых, давайте посмотрим, что должно быть в записи:

print "Длина: %d; Последние 40 символов: %s" % (len(note.content), note.content[-40:]) Длина: 19456; Последние 40 символов: rVEF1cCJeRaTtcdkXMqqQUxEVLZapMGVGSxMfJ2T

А теперь проходим по кругу. Каждый раз мы увеличиваем настройку TEXTSIZE и отображаем запись, если она нравится. Мы также отображаем длину и последние 10 символов возвращаемого поля записи.

>>> for xx in xrange(19450, 19460):
...     cursor = connection.cursor()
...     try:
...         qrys = 'SET TEXTSIZE %d SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1' % xx
...         print qrys
...         qry = cursor.execute(qrys)
...         record = qry.fetchone()
...         if record:
...             record_id, record_content = record
...             print record_id, len(record_content), record_content[-10:]
...         else:
...             print "No record found after TEXTSIZE set to %d" % xx
...             break
...     except Exception, inst:
...         print "Error: %s (%s)" % (inst, type(inst))
...         break
...     finally:
...         cursor.close()
... 
SET TEXTSIZE 19450 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19450 VLZapMGVGS
SET TEXTSIZE 19451 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19451 LZapMGVGSx
SET TEXTSIZE 19452 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19452 ZapMGVGSxM
SET TEXTSIZE 19453 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19453 apMGVGSxMf
SET TEXTSIZE 19454 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19454 pMGVGSxMfJ
SET TEXTSIZE 19455 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
1 19455 MGVGSxMfJ2
SET TEXTSIZE 19456 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1
No record found after TEXTSIZE set to 19456
>>> 

Поэтому, как только мы пытаемся получить запись с TEXTSIZE, установленным на число больше 19456, никакие записи не возвращаются. И вы заметите, что последние 10 символов строки совпадают со строкой выше за вычетом символов, которые были пропущены из-за того, что они слишком короткие. Например, для последней найденной записи последние 10 символов равны MGVGSxMfJ2. В реальной записи отсутствует T, потому что TEXTSIZE 19455 на единицу меньше, чем длина рассматриваемого поля.

Итак, теперь, конечно, мне интересно, что происходит??? Могу ли я предпринять какие-либо дальнейшие действия по устранению неполадок, чтобы определить, является ли это проблемой с django-pyodbc, pyodbc или FreeTDS? Возможно, это также может быть SQL Server, но запуск SET TEXTSIZE 19456 SELECT [Notes].[note_id], [Notes].[note_content] FROM [Notes] WHERE [Notes].[note_id] = 1 непосредственно в Server Management Studio работает правильно и возвращает правильное количество символов.

Также обратите внимание, что сохранение работает:

>>> note.content = (note.content * 10)[:65536] # 65536 is max length allowed for TEXT, apparently
>>> len(note.content)
65536
>>> note.save()
>>> cursor = connection.cursor()
>>> qry = cursor.execute( 'SELECT [Notes].[note_id], DATALENGTH([Notes].[note_content]) FROM [Notes] WHERE [Notes].[note_id] = 1')
>>> record = qry.fetchone()
>>> record
(1, 65536)
>>> 

person Jordan Reiter    schedule 19.11.2012    source источник
comment
Какая версия SQL Server (и других компонентов)? Какой тип данных в таблице; это TEXT или NVARCHAR(MAX) или что-то еще? Использовали ли вы SQL Profiler для проверки того, что SQL, отправляемый на сервер, действительно соответствует вашим ожиданиям?   -  person Pondlife    schedule 20.11.2012
comment
Это TEXT. Я посмотрю на использование SQL Profiler, посмотрим, даст ли это мне больше информации.   -  person Jordan Reiter    schedule 20.11.2012
comment
Я нашел одно исправление, которое заключается в преобразовании поля TEXT в поле NVARCHAR. Это устраняет проблему на 100%, но, очевидно, далеко не идеально, поскольку это означает просмотр схемы всех моих таблиц и их изменение.   -  person Jordan Reiter    schedule 20.11.2012
comment
Ну, это неплохое исправление, так как TEXT устарел в любом случае.   -  person Pondlife    schedule 20.11.2012
comment
Да, я делаю это, но я не в восторге от этого.   -  person Jordan Reiter    schedule 21.11.2012