RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

5.6. MySQLmodule-1.4

Этот модуль был разработан под Linux (RH50), MySQL 3.21.30 и Python 1.5.1. MySQLmodule-1.x основан на mySQLmodule-0.1.4, разработанном:

  • Copyright (C) 1997 Joseph Skinner
  • Copyright (C) 1997 James Henstridge

mySQLmodule-0.1.4 в свою очередь основан на mSQLmodule, разработанном:

  • Portions copyright (C) 1995 Thawte Consulting, cc
  • Portions copyright (C) 1994 Anthony Baxter

5.6.1 Почему другой интерфейс Python/MySQL?

Была обнаружена проблема при сохранении строк, содержащих ASCII-ноль (\0) в базе данных MySQL. После расследования в языке Python (который создал первоначальную ошибку), был проверен код mySQLmodule и там найден ряд проблем. Некоторые подпрограммы не освобождают распределенную память, некоторые функции MySQL API не были доступны, и многих разработчиков совершенно не устраивал тот факт, что mySQLmodule возвратит различные структуры данных в зависимости от метода. Так что я переделал код. Так как эти изменения включают изменение в методах Python и значениях возврата, был повышен код версии и изменено имя модуля на MySQL, чтобы оно не столкнулось с оригиналом (mySQL).

Рационально возвратить список списков вместо набора или списка наборов. Не стоит создавать ситуацию, когда подпрограммы доступа к DB занимаются обработкой типа возврата, и стоит предусмотреть возможность изменить данные в возвращенной таблице напрямую.

Data = DBH['select * from MyTable']
if Data:
   rows = len(Data)
   cols = len(Data[0])
   for i in range(rows):
     for j in range(cols):
       if not Data[i][j]: Data[i][j] = DefaultElement()
   Do_Something(Data)

Никакой потребности усложнять это при наличии наборов внутри внешнего списка нет. Для тех, кто предпочитает словари, метод STH fetchdict() возвратит список словарей. Ключи словаря квалифицированы с соответствующим именем (возможно, не одним) обрабатываемой таблицы.

5.6.2 Компиляция и установка модуля MySQL

  • Скопируйте MySQLmodule.c в подкаталог Modules дерева исходников Python.
  • Добавьте следующие строки в Ваш файл Setup в этом каталоге:
    MySQL MySQLmodule.c -L/usr/local/lib/mysql/ -lmysqlclient \
                        -I/usr/local/include/mysql
    

    Обратите внимание, что расположение библиотеки MySQL и include-каталога может быть иным на Вашей специфической системе. Вы можете формировать разделяемый модуль (вставкой ниже индикатора shared в файле Setup).

  • Если Вы формировали Python ранее, просто выполните make в Вашем основном каталоге Python. Если нет, следуйте командам о том, как компилировать и установить Python.

Чтобы формировать динамически загружаемый модуль без доступа к дереву исходников python, используйте:

gcc -shared -I/usr/include/python1.5 -I/usr/local/include/mysql \
    MySQLmodule.c -lmysqlclient -L/usr/lib/python1.5/config \
    -lpython1.5 -o MySQLmodule.so

Переместите возникающий в результате файл MySQLmodule.so в PYTHONPATH. Замените соответствующие расположения Ваших библиотек и include-файлов.

Обратите внимание, что модуль чувствителен к регистру, и что имя было изменено на MySQL, чтобы не разорвать любой существующий код, который использует старый модуль mySQL.

Экспортируемые типы, функции и классы

Модуль MySQL экспортирует следующее:

  • DBH_Type:
    Тип объекта базы данных.
  • STH_Type:
    Тип ошибки объекта курсора (исключительная ситуация, выдаваемая в некоторых обстоятельствах вместо TypeError).
  • __doc__:
    Версия, доступная из Python
  • connect([host[,user[,pass]]])
    Функциональный возврат объекта базы данных. Факультативные параметры: имя компьютера (host), с которым надлежит соединиться, username для связи с MySQL и соответствуюющий пароль password. Если никакой компьютер не задан, функция примет localhost (и будет использовать Unix-сокет для подключения к нему).
  • escape(string)
    Вернет правильно экранированную строку, чтобы позволить вставку в DB. Эта подпрограмма вызывает mysql_escape_string(), которая в 3.21.29-gamma не работает. Версия 3.21.30 и выше работают нормально.

    Обратите внимание: в следующем "таблица" означает "список списков" (за исключением fetchdict). MySQL.connect() вернет дескриптор базы данных (DBH) со следующими методами:

  • Table = DBH.listdbs([wild])
    Возвращает таблицу, дающую имена баз данных на компьютере MySQL, с которым соединился через вызов MySQL.connect(). Факультативный параметр: MySQL-строка с символами подстановки (синтаксис аналогичен LIKE).
  • DBH.selectdb(DB_Name[,storage])
    Присоединяет этот объект к специфической базе данных. Выполненные запросы будут направлены к этой базе данных, пока не будет сделано другое обращение метода selectdb. Факультативный параметр storage типа integer может использоваться, чтобы хранить наборы результатов запросов на сервере. Обратите внимание, что это отрицательно воздействует на эффективность сервера, но позволяет клиентуре с маленькой памятью работать с записями по мере надобности. Значение по умолчанию: 0, то есть все записи перемещены пользователю сразу.
  • Table = DBH.listtables([wild])
    Возвращает таблицу с именами таблиц в выбранной базе данных. Имеет силу только после того, как было сделано обращение selectdb. Факультативный параметр может использоваться, чтобы ограничить возвращенный набор таблиц (тот же самый синтаксис, какой принят в LIKE).
  • Table = DBH.listfields(table[,wild])
    Возвращает таблицу описаний полей в данной таблице. Факультативный параметр может использоваться, чтобы ограничить возвращенный набор полей (тот же самый синтаксис, какой принят в LIKE).
  • Table = DBH.listprocesses()
    Возвращает информацию относительно работающих процессов MySQL. Требует соответствующих привилегий (иначе возвращает None).
  • String = DBH.stat()
    Возвращает информацию состояния из MySQL.
  • DBH.create(DB_Definition)
    Создает новую базу данных.
  • DBH.drop(DB_Name)
    Удаляет базу данных.
  • DBH.reload()
    Перезагружает таблицы привилегий MySQL.
  • DBH.shutdown()
    Завершает сервер MySQL.
  • DBH.close()
    Закрывает DB-подключение.
  • String = DBH.clientinfo()
    Возвращает информацию о версии MySQLmodule.
  • String = DBH.serverinfo()
    Возвращает информацию о сервере MySQL.
  • String = DBH.hostinfo()
    Возвращает информацию относительно хоста соединения и типа подключения.
  • Integer = DBH.protoinfo()
    Возвращает информацию о версии протокола MySQL.
  • Table = DBH.do(query) или Table = DBH[query]
    Возвращает результат запроса SQL или число обработанных строк. Оба метода используют тип памяти, установленный в DBH.selectdb().
  • Integer = DBH.insert_id()
    Предоставляет доступ к последнему сгенерированному числу auto_increment. Это число может изменяться, если были запросы между обращениями.
  • STH = DBH.query(query[,storage])
    Возвращает операторный дескриптор для методов курсора (см. ниже). Факультативный параметр storage может использоваться, чтобы отменить значение по умолчанию DBH, установленное DBH.selectdb().

Методы для операторных дескрипторов (STH):

  • Table = STH.fetchrows([n])
    Возвращает результаты запроса к DB. Если n<0, все строки будут выбраны. Иначе будут возвращены только следующие n строк. Значение по умолчанию должно возвратить все строки.
  • Table = STH.fetchdict([n])
    Аналогично STH.fetchrows(), за исключением того, что список словарей возвращен с парами tablename.fieldname:data.
  • Table = STH.fields()
    Возвращает описания поля результата запроса STH. В настоящее время MySQLmodule понимает "pri", "notnull", "auto_inc", "ukey" и "mkey" .
  • STH.seek(n)
    Двигает курсор к строке n (0 определяет первую строку). Доступен только, если результат хранится на стороне пользователя (выбрано в DBH.selectdb). Иначе создает исключительную ситуацию.
  • Integer = STH.numrows()
    Возвращает, сколько строк находятся в результате запроса STH. Предупреждение: в действительности это число отражает сколько записей получил клиент. Для методов хранения на стороне сервера, это начинается с 0 и увеличивается по мере того, как пользователь выбирает строки. Для методов хранения на стороне клиента это число немедленно дает общее количество строк для этого запроса.
  • Integer = STH.numfields()
    Возвращает, сколько столбцов находятся в результате запроса STH.
  • Integer = STH.affectedrows()
    Возвращает данные на сколько строк воздействовал последний запрос. Обратите внимание, что MySQL не всегда возвращает правильное значение.
  • Integer = STH.insert_id()
    Возвращает значение auto_increment из STH-запроса insert. Обратите внимание, что это число постоянно, пока STH существует.
  • Integer = STH.eof()
    Возвратит 1, если последняя строка читалась, иначе 0. Всегда 1, если выбрано хранение данных на стороне пользователя (значение по умолчанию).

Использование модуля MySQL

import MySQL
DBH = MySQL.connect()   # localhost
print DBH.listdbs()
DBH.selectdb('test')
print DBH.serverinfo()
print DBH.stat()
DBH["create table pytest (x int, y int, s char(20))"]
DBH["insert into pytest values (1,2,'abc')"]
DBH.do("insert into pytest values (3,4,'def')")
STH = DBH.query("insert into pytest values (5,6,'ghi')")
print STH.affectedrows()
print DBH['select * from pytest']
STH = DBH.query("select * from pytest")
print STH.numrows()
print STH.fields()
print STH.fetchrows(-1)
STH.seek(0)
print STH.fetchrows(1)
print STH.fetchrows(1)
STH.seek(0)
print STH.fetchrows(2)
print STH.fetchrows(2)
print STH.numfields()
STH.seek(0)
print STH.fetchdict(1)
print STH.fetchdict()
STH = DBH.query("select * from pytest",1)
print STH.fetchdict(1)
print STH.fetchdict() # compare to previous dicts
STH = DBH.query("select * from pytest",1)
print STH.fetchrows(1)
print STH.eof()
print STH.fetchrows()
print STH.eof()
DBH['drop table pytest']

5.6.3 Примечания относительно хранения на сервере

MySQL предлагает два немного различных пути доступа к данным из базы данных.

Заданный по умолчанию метод в MySQLmodule состоит в том, чтобы использовать хранение на стороне пользователя, то есть все запросы, включая методы курсора (STH), выбирают все данные с сервера. К строкам обращаются через STH.fetchrows(n) или STH.fetchdict(n) индивидуально (n=1), блочно (n>1), все строки забирают одной порцией (n<0) или не обращаются вообще (n=0). STH.numrows() может сообщать после запроса сколько строк находится в результате. STH.seek(k) может использоваться, чтобы обратиться к строкам в произвольном порядке. Недостаток хранения на стороне пользователя состоит в том, что это использует память пользователя, чтобы сохранить все строки. Этот способ учитывает конструкции типа:

STH = DBH.query("select * from Foo")
N = STH.numrows()
if N > 1000: raise Hell,"You must be joking!"
for i in xrange(N):
  [Data] = STH.fetchdict(1)

Так как пользователь действительно переместил к себе все строки, которые подготовил сервер, все транзакции на этом канале прекратились, и сервер готов принять новые команды. Это также означает, что STH.eof() всегда true (1) для хранения данных на стороне клиента.

Хранение на стороне сервера не требует, чтобы у пользователя было много памяти. Все записи будут перемещены на основании запроса. Однако, этот способ имеет несколько недостатков. Так как теперь возникает возможность того, что пользователь не забрал все строки, каждая новая команда должна проверить, является ли свободным, то есть готов ли он принять новую команду. Если нет, команда должна очистить канал команды, выдавая запросы на чтение в количестве, достаточном, чтобы получить оставшиеся строки. MySQL API 3.21 не предлагает команды abort(). STH.numrows() не знает ничего относительно того, сколько строк были выбраны запросом, так что вышеупомянутый код примера будет падать. STH.numrows() будет, однако, модифицироваться, поскольку строки все-таки читаются, например:

STH = DBH.query("select * from Foo")
Data = STH.fetchrows(-1)
print "Got",STH.numrows(),"rows."   # len(Data) is the same

STH.eof() имеет смысл только при хранении данных на сервере, но даже здесь такой код полезен не всегда:

STH = DBH.query("select 1")
print STH.eof()          # will print 0
Data = STH.fetchrows(1)  # retrieve the row
print STH.eof()          # still 0 :-(
Data = STH.fetchrows(1)  # must repeat. Data will be []
print STH.eof()          # now we get 1, but we already
                         # knew that we've hit the end

Можно было рассматривать это, как ошибку. STH.seek(k) больше не доступен и создает исключение ("cannot seek on server"), то есть строки должны теперь читаться последовательно.

Хранение на стороне сервера также создает дополнительную нагрузку на сервер. В частности, он должен остаться в контакте с пользователем, пока все строки не будут прочитаны. Так что лучше бы быстро получить все строки и не делать длинную обработку или, что еще хуже, позволять пользователю останавливать поиск (например, нажимая клавиши Ctrl-S в интерактивном интерфейсе).

Для тех, кто не может решить, который метод является более подходящим для прикладной программы, MySQLmodule позволяет смешивать оба метода свободно. Заданное по умолчанию поведение может быть установлено через DBH.selectdb() и может быть изменено для индивидуального курсора (STH). Обратите внимание, что незавершенные запросы будут отменены последующими командами:

STH = DBH.query("select * from Foo",1) # use server side storage
Tables = DBH.listtables()              # stomp on previous results
Data = STH.fetchrows()                 # nothing here anymore vs.
STH = DBH.query("select * from Foo",0) # use client side storage
Tables = DBH.listtables()              # won't interfere
Data = STH.fetchrows()                 # no problem...

Хранение на стороне сервера также создает более сложный код в MySQLmodule. Обычно при хранении на стороне клиента STH-курсоры независимы от дескриптора базы данных. Немедленно после запроса все данные будут перекачаны клиенту, и пользователь может делать все, что сочтет необходимым с дескриптором DBH. Но при хранении на стороне сервера это становится сложным. С курсорами стороны сервера указатель на дескриптор сохранен в дескрипторе STH. MySQLmodule удостоверится, что этот кусок памяти не освобожден прежде, чем закроются все ожидающие обработки курсоры. Это означает, что mysql_close() не обязательно вызван, если база данных, обрабатываемая DBH, разрушена:

DBH = MySQL.connect()         # get a DB handle
STH = DBH.query("select 1",1) # server side cursor
del DBH                       # mysql_close() *not* called
STH.fetchrows()               # will succeed!
del STH                       # now mysql_close() will be called
                              # in DBH_dealloc()

Если Вы должны закрыть дескриптор DB немедленно, используйте DBH.close(). Все дальнейшие попытки работать на этом дескрипторе (даже ожидающие обработки курсоры стороны сервера) получат исключительную ситуацию "... server has gone away". Начиная с того момента, как mySQL не может принимать команды из последовательности, все DBH-методы должны проверить незавершенные STH-курсоры. Чтобы обращаться к ним, дескриптор DBH хранит указатель на STH-курсор.

5.6.4 Установка MySQLmodule-1.4 под Windows NT

Следующие инструкции основаны на информации, предоставленной Nigel Head (nhead@houbits.com).

  • Вы должны получить файл libMySQL.dll из клиентского пакета win32-mysql версии 3.22.8 или выше, более ранние версии не будут работать. Это доступно на www.tcx.se (или зеркалах) для свободной загрузки. Вы должны, вероятно, переместить файл в то же самое расположение, что и Ваша библиотека python15.dll.
  • Скомпилируйте MySQLmodule-1.4 с включенным переключателем WIN32. VC5 и файлы проекта MySQL.dsp и MySQL.dsw включены (но, вероятно, их придется изменять, чтобы удовлетворить требованиям Вашей среды).
  • Переместите возникающий в результате файл MySQL.pyd в Ваш каталог Python DLL.

Если что-то пойдет не так, помощь лучше всего поискать в конференции comp.lang.python.

Поиск

 

Найди своих коллег!