Python - глазами DBA
(Описание модуля cx_Oracle)
Попов О.Б. opopov@yandex.ru г.Самара,май-август 2003 года
Содержание
Почему собственно python.
Администраторы БД очень часто нуждаются в инструментах для автоматизации
разной рутинной работы, например: загрузка, преобразования данных в разного рода
форматы или сбор и анализ различной статистики.Для меня в свое время такой
палочкой-выручалочкой стал perl.
Через некоторое время я набрел на python.Вот все что я могу сказать после 1
года знакомства: Это практичный и полезный инструмент.
Несколько слов об инструментах.Вариантов на самом деле немного: 1)гремучая
смесь из разных утилит командной строки (bash+awk+tar+...), 2)языки
сценариев(perl,tcl/tk... ), 3)классические языки программирования(С,C++)
Первый вариант наиболее часто применяется администраторами.По крайней мере
мой собственный опыт говорит о том, что иногда удается получить неплохое
решение, если только задача не слишком сложна.
Третий вариант позволяет получить великолепное решение при значительных
затратах сил и времени на программирование,отладку и тестирование кода.А если их
нет?
Золотой серединой очевидно является вариант номер 2.
К языкам сценариев по функциональным возможностям приближается java,
поскольку фирма Oracle(к моему большому сожалению )взяла главный курс на
использование языка java везде,где это только возможно, теперь java работает в
контексте ядра Oracle и в принципе может быть использована как инструмент
администратора.
Проблемы я вижу следующие: -слишком сложные API,плохая читабельность кода
-отсутствие встроенных современных типов данных, - крайняя медлительность
выполнения, -необходимость обновлять программы с Oracle Metalink. - Java
API имеет плохую обратную совместимость с предыдущими версиями -java -это не
собственная разработка Oracle corp., а технология , лицензированная у Sun ,
которая кое-что лицензировала еще у нескольких фирм.
Недостатки применения java с RDBMS Oracle проявились особенно отчетливо в
следущем эпизоде:на компьютерах с процессорами Pentium IV, отказался запускатьcя
Oracle Universal Installer(речь идет о Oracle 8.1.7), написанный на Java .
Причина оказалась в JIT compiler, который Sun лицензировал у Symantec. Понятно
почему, фирма Oracle длительное время не могла решить эту проблему. Пользователи
Oracle решали ее самостоятельно -путем удаления одной dll. Не предсталяю,что бы
делал я в этой (и многих других)ситуации без интернета и духа открытости
пользователей Oracle-администраторов, программистов...
С perl обычно все намного лучше.Дух открытости (движение Open Source и
лицензия GPL) позволил Perl прогрессировать с космической
скоростью: -огромное количество великолепных библиотек, -встроенные
структуры данных-списки,hash-таблицы -существование многочисленных книг,
списков рассылки и просто энтузиастов значительно облегчают жизнь профессионалу.
Несмотря на массу положительных свойств, Perl имеет imho ряд существенных
недостатков: -крайне запутанный и к тому же подверженный изменениям
синтаксис. -значительная интеграция с юниксообразными ОС. -не очень
удачные средства поддержки OOP
Первое затрудняет сопровождение (даже собственных) программ.А второе
ограничивает сферу применимости языка на других платформах.
И здесь важно отметить, что разработчики python в значительной степени учли
достоинства и недостатки различных языков программирования и в результате
появился язык программирования, который почти не содержит в себе экзотических
конструкций и позволяет использовать почти все известные на сегодня методы
создания программ.
Справедливости ради нужно заметить, что python отнюдь не является чем
то уникальным, мне известен еще один язык программирования с похожим
функционалом - ruby.Этот язык несколько моложе чем python и для серьезного
промышленного применения он еще, похоже не готов.Не все в порядке с
документацией и набор библиотек совсем невелик. Ruby довольно популярен среди
американских студентов, поэтому, возможно у него впереди яркая судьба.
Достоинства языка python
Python можно охарактеризовать в нескольких словах следующим
образом: многоплатформенный язык сценариев с динамической
типизацией,встроенными структурами данных и поддержкой трех парадигм
программирования-процедурной,объектной, функциональной.
- Многоплатформенность и масштабируемость-работает на большинстве известных
программно-аппаратных платформ от PDA до CRAY и IBM 390
- Гармоничная архитектура языка:
- Простой и удобный для понимания логики програм синтаксис
- Встроенные структуры данных-списки, кортежи,словари
- Огромное количество библиотек:
-XML
парсеры -GUI(Win32,GTK/GNOME,KDE/QT,TK,FOX,WXWindows) - Сетевые
протоколы - Разработка Web приложений
- Переносимость кода между различными платформами
- Мощные интерфейсы к конкретным ОС(особенно к Linux/Unix и win32)
- Поддержка разных стилей
программирования-процедурного,объектного,функционального
- Встроенная поддержка Unicode и множества национальных кодировок
(включая
5 русских)
- Возможность писать самодокументированные программы и автоматическая
генерация документации на модули.
С чем реально сталкивается программист(а админитстратор является таковым
часто, но понемногу ) при использовании python.Это простой и ясный синтаксис. Я
ловлю себя часто на мысли,что просмотр чужого кода зачастую не требует
значительного напряжения.И здесь помогает то, что отступы являются частью
синтаксиса и определяют вложенность операторов.
Библиотеки зачастую имеют очень лаконичную документацию, но этого достаточно
так как API очень выразительны и внятны. Отрадным явлением в мире питона,
является тот факт, что очень многие библиотеки были разработаны после того, как
были согласованы спецификации на конкретные API.Одной из таких спецификаций
является спецификация на Python Database API 2.0. В соответствии с этой
спецификацией разрабатываются модули для доступа к конкретным СУБД.
К сожалению сам стандарт(как в свое время и cпецификация на ODBC) не может
учесть все специфические особенности конкретной СУБД, так как
фирмы-производители СУБД, используют свои фирменные расширения как средство в
конкуретной борьбе. Поэтому перенос кода с одной платформы на другую все-таки
очень трудно сделать безболезненным, если только перед разработчиками не
ставилась задача писать приложения, предназначенное для работы с различными SQL
серверами.
Сам процес кодирования и тестирования достаточно комфортен.Здесь помогает
качественная диагностика ошибок.Оттестированные модули очень легко расширять и
складывать из них приложения.
Для работы с RDBMS Oracle существует несколько различных модулей для Python,
использующих разные механизмы:
- ODBC(Win32,Linux)
- DCOM(Win32)
- PERL_DBI(Win32,Linux,Solaris,HP-UX,AIX)
- NATIVE API (Win32,Linux,Solaris,HP-UX,AIX)
Более подробную информацию Вы можете найти на www.python.org.
Часть I. Выполняем простой запрос.
Я предпочитаю использовать модуль cx_Oracle, который был проверен мною в ОС
NT и Linux(автор-Anthony Tuininga утверждает что он работает в Solaris и похоже,
на всех юниксообразных ОС должен также работать ).В основном этот модуль следует
спецификации Python Database API 2.0 и поддерживает работу с RDBMS Oracle версий
8.1-9.xx.
Модуль написан на языке С, что обеспечивает быструю обработку данных и
позволяет использовать все достоинства языка Python.
Для быстрого погружения в предмет я приведу фрагменты кода, демонстрирующего
основные приемы использования модуля. try: import cx_Oracle except ImportError,info: print "Import Error:",info sys.exit() if cx_Oracle.version<'3.0': print "Very old version of cx_Oracle :",cx_Oracle.version sys.exit()
В этом фрагменте демонстрируется безопасная загрузка модуля cx_Oracle и
проверка версии. Надеюсь, элегантность синтаксиса все-таки заметили :-)
Далее создадим экземпляр класса connect, именно этот объект и обеспечивает
взаимодействие с сервером Oracle try: my_connection=cx_Oracle.connect('system/manager@test_db') except cx_Oracle.DatabaseError,info: print "Logon Error:",info exit(0)
Теперь создаем курсор и выполняем запрос: my_cursor=my_connection.cursor() try: my_cursor.execute(""" SELECT OWNER,SEGMENT_TYPE,TABLESPACE_NAME,SUM(BLOCKS)SIZE_BLOCKS, COUNT(*) SIZE_EXTENTS FROM DBA_EXTENTS GROUP BY OWNER,SEGMENT_TYPE,TABLESPACE_NAME """) except cx_Oracle.DatabaseError,info: print "SQL Error:",info exit(0)
Динамическая типизация и поддержка сложных структур данных позволяет легко
обработать результаты SQL запроса. В результате вызова my_cursor.fetchall()
возращается список записей,каждая из которых явлеяется кортежем(неизменяемым
списком) полей разного типа. Если вы знакомы с PL/SQL, то тогда цикл по списку
записей будет очевидным: for record in my_cursor.fetchall():
<обработка записи record,
record содержит кортеж из полей текущей записи>
Существует еще более любопытная возможность "разобрать" текущую запись по
полям: for OWNER,SEGMENT_TYPE,TABLESPACE_NAME,SIZE_BLOCKS,SIZE_EXTENTS in my_cursor.fetchall():
<теперь можно обращаться к переменным, содержащим значения
одноименных полей курсора>
Печатаем результат на stdout. Замечу здесь, что my_cursor.description
возвращает описание столбцов запроса в виде списка кортежей. Для каждого столбца
возвращаются следующие данные:(name, type_code, display_size, internal_size,
precision, scale, null_ok). Далее следует форматированный вывод на stdout( почти
как printf в языке C). print print 'Database:',my_connection.tnsentry print print "Used space by owner, object type, tablespace " print "----------------------------------------------------------------------------------" title_mask=('%-16s','%-16s','%-16s','%-8s','%-8s') i=0 for column_description in my_cursor.description: print title_mask[i]%column_description[0], i=1+i print '' print "----------------------------------------------------------------------------------" row_mask='%-16s %-16s %-16s %8.0f %8.0f ' for record in my_cursor.fetchall(): print row_mask%record
В результате мы увидим что-то вроде: Database: testdb
Used space by owner, object type, tablespace --------------------------------------------------------------------------------
OWNER SEGMENT_TYPE TABLESPACE_NAME SIZE_BLOCKS SIZE_EXTENTS --------------------------------------------------------------------------------
ADU2 INDEX USERS 784 25 ADU2 TABLE USERS 512 24 ADUGKS INDEX DEVELOP_DATA 984 123 ADUGKS TABLE DEVELOP_DATA 664 83 ADUGPA INDEX USERS 784 25 ADUGPA TABLE USERS 496 23 AGNKS_SG INDEX USERS 352 22 AGNKS_SG TABLE USERS 240 15 ATU INDEX USERS 3968 244 ATU TABLE DEVELOP_DATA 8 1 ATU TABLE USERS 2688 160 ATU1 INDEX DEVELOP_DATA 1600 200 ATU1 INDEX USERS 608 38 ATU1 TABLE DEVELOP_DATA 1032 129 ATU1 TABLE USERS 544 34 BUX INDEX DEVELOP_DATA 64 8 BUX TABLE DEVELOP_DATA 1736 217 DISP INDEX USERS 400 25 DISP TABLE USERS 528 33 EPE INDEX USERS 80 5 EPE TABLE USERS 48 3 EXZ INDEX USERS 1088 61 EXZ TABLE DEVELOP_DATA 8 1 EXZ TABLE USERS 832 41
Часть II. Запросы с параметрами.
Согласно спецификации Python Database API 2.0, для выполнения запросов с
параметрами каждый модуль должен реализовывать переменную paramstyle, которая
определяет каким образом будут передаваться параметры запросов. Текущая версия
cx_Oracle(3.0) поддерживает режим 'named', то есть в модуле установлена
переменная cx_Oracle.paramstyle='named' и можно создавать конструкции в запросах
в виде: select * from all_users where USERNAME LIKE :S
При этом связывание параметров запроса со значениями можно можно выполнить
двумя способами:
- Именованный параметeр метода execute:
cursor2.execute("select * from all_users where USERNAME LIKE :S ",S='S%')
- Словарь {':переменная':значение,...}
cursor2.execute("select * from all_users where USERNAME LIKE :S ",{':S':'S%'})
Часть III. Анонимные блоки PL/SQL
Несмотря на существавания стандартов на язык SQL, реальные потребности
администратора часто требуют использования нестандартных средств сервера(более
того, написать приложение, работающее с сервером на стандартном SQL возможно
только для весьма тривиальных приложений ), для Oracle таким нестандартным, но
очень удобным механизмом является возможность исполнения анонимных блоков
PL/SQL. Модуль сx_Oracle реализует этот механизм, который естественно не описан
в спецификации Python Database API 2.0 .
Чтобы связать переменные блока PL/SQL c переменными языка PYTHON, в модуле
сx_Oracle реализован класс var.Эекземпляр можно создать следующим образом: var=my_cursor.var(cx_Oracle.DATETIME)
Конструктор my_cursor.var(...) в качестве параметра требует указать тип
создаваемой переменной. Варианты:
- BINARY
- DATETIME
- FIXEDCHAR
- LONGBINARY
- LONGSTRING
- NUMBER
- ROWID
- STRING
var=my_cursor.var(cx_Oracle.DATETIME) try: my_cursor.execute("""begin SELECT SYSDATE INTO :p_Value from dual; end;""",p_Value = var) except cx_Oracle.DatabaseError,info: print "SQL Error:",info exit(0)
Очевидно, что теперь var содержит текущее время сервера.Доступ к значениям
переменной выполняется с помощью метода var.getvalue(): CDATE=var.getvalue() print 'Date: %02u/%02u/%4u'%(CDATE.day,CDATE.month,CDATE.year) print 'Time: %02u:%02u:%02u'%(CDATE.hour,CDATE.minute,CDATE.second)
Этот пример демонстрирует также и форматирование даты и времени для
экземпляра var. В результате напечатается нечто вроде: Date: 12/05/2003 Time: 16:42:54
Всвязи с тем, что работа с типами времени и даты внутри сервера Oracle
реализованы особенным образом ( независимо от ОС), модуль сx_Oracle реализует
следующие функции для для преобразования значений дат и времени :
- Date( year, month, day)
- DateFromTicks( ticks)
- Time( hour, minute, second)
- TimeFromTicks( ticks)
- Timestamp( year, month, day, hour, minute, second)
- TimestampFromTicks( ticks)
var=my_cursor.var(cx_Oracle.DATETIME) var.setvalue(0,cx_Oracle.Date( 2002, 02,12)) CDATE=var.getvalue() print 'Date: %02u/%02u/%4u'%(CDATE.day,CDATE.month,CDATE.year) print 'Time: %02u:%02u:%02u'%(CDATE.hour,CDATE.minute,CDATE.second)
Результат будет следующий: Date: 12/02/2002 Time: 00:00:00
Ссылки
cx_Oracle-Python extension module that allows access to Oracle Author:
Anthony Tuininga http://computronix.com/utilities.shtml
Python Database API
specification Python
http://www.python.org/topics/database/DatabaseAPI-2.0.html
Листинги
Листинг 1. Выполняем простой запрос
"" cx_Oracle demo simple query """ __AUTHOR__='POPOV O.' __COPYRIGHT__='POPOV O. 2002 Samara, Russia'
from sys import exit
try: import cx_Oracle except ImportError,info: print "Import Error:",info sys.exit() if cx_Oracle.version<'3.0': print "Very old version of cx_Oracle :",cx_Oracle.version sys.exit()
try: my_connection=cx_Oracle.connect('system/gasdba@sqlmt') except cx_Oracle.DatabaseError,info: print "Logon Error:",info exit(0) my_cursor=my_connection.cursor() try: my_cursor.execute(""" SELECT OWNER,SEGMENT_TYPE,TABLESPACE_NAME,SUM(BLOCKS)SIZE_BLOCKS, COUNT(*) SIZE_EXTENTS FROM DBA_EXTENTS GROUP BY OWNER,SEGMENT_TYPE,TABLESPACE_NAME """) except cx_Oracle.DatabaseError,info: print "SQL Error:",info exit(0) print print 'Database:',my_connection.tnsentry print print "Used space by owner, object type, tablespace " print "-----------------------------------------------------------" title_mask=('%-16s','%-16s','%-16s','%-8s','%-8s') i=0 for column_description in my_cursor.description: print title_mask[i]%column_description[0], i=1+i print '' print "------------------------------------------------------------" row_mask='%-16s %-16s %-16s %8.0f %8.0f ' for record in my_cursor.fetchall(): print row_mask%record for column_description in my_cursor.description: print column_description
Листинг 2.
Запрос с параметрами"" cx_Oracle demo query with parameters """ __AUTHOR__='POPOV O.' __COPYRIGHT__='POPOV O. 2002 Samara, Russia'
from sys import exit
try: import cx_Oracle except ImportError,info: print "Import Error:",info sys.exit() if cx_Oracle.version<'3.0': print "Very old version of cx_Oracle :",cx_Oracle.version sys.exit()
try: my_connection=cx_Oracle.connect('system/manager@test_db') except cx_Oracle.DatabaseError,info: print "Logon Error:",info exit(0) my_cursor=my_connection.cursor() try: my_cursor.execute(""" SELECT OWNER,SEGMENT_TYPE,TABLESPACE_NAME,SUM(BLOCKS)SIZE_BLOCKS, COUNT(*) SIZE_EXTENTS FROM DBA_EXTENTS WHERE OWNER LIKE :S GROUP BY OWNER,SEGMENT_TYPE,TABLESPACE_NAME """,S='SYS%') except cx_Oracle.DatabaseError,info: print "SQL Error:",info exit(0) print print 'Database:',my_connection.tnsentry print print "Used space by owner, object type, tablespace " print "-----------------------------------------------------------" title_mask=('%-16s','%-16s','%-16s','%-8s','%-8s') i=0 for column_description in my_cursor.description: print title_mask[i]%column_description[0], i=1+i print '' print "------------------------------------------------------------" row_mask='%-16s %-16s %-16s %8.0f %8.0f ' for record in my_cursor.fetchall(): print row_mask%record
© Попов О.Б. 2003
|