Используем IronPython из Transact SQLИсточник: habrahabr bocharovf
Transact SQL великолепный язык, функциональности которого более чем достаточно для решения большинства часто возникающих задач. Однако иногда возникают задачи, которые с его помощью решать долго и/или неудобно. Пожалуй, самым ярким примером является продвинутый парсинг строк, в котором приходится использовать регулярные выражения или просто хитрый и закрученный алгоритм. Начиная с SQL Server 2005, эта проблема решается созданием хранимой процедуры/функции CLR. Но этот подход требует перекомпиляции и развертывания сборки при внесении изменений. А так хочется, не покидая Management Studio, изменять поведение своих процедур.
Каким должен быть результатЯ хочу получить функцию вида
Неплохо было бы иметь так же агрегирующую функцию и хранимую процедуру, использующую код на python.
Что нам потребуетсяДля реализации задуманного мы будем использовать SQL Server 2008 R2, Visual Studio 2010 и IronPython 2.6.2. IronPython придется собирать из исходниковисправив всего одну строчку кода (об этом чуть позже).
Настройка сервераДля начала создадим отдельную базу для экспериментов. В дальнейших примерах я использую базу с именем CLR.
Сборка IronPython из исходниковИнициализация движка python в контексте Sql Server будет вызывать ошибку, исправить которую можно немного подкорректировав исходники. Для этого скачиваем исходные коды IronPython 2.6.2 и открываем проект. Находим в проекте IronPython файл Modules\sys.cs и в функции GetPrefix повторяем код, использующийся для сборки под Silverlight. Таким образом функция GetPrefix будет всегда возвращать пустую строку.
Собираем проект и получаем сборки IronPython.dll, IronPython.Modules.dll, Microsoft.Dynamic.dll, Microsoft.Scripting.dll, Microsoft.Scripting.Core.dll, Microsoft.Scripting.Debugging.dll, Microsoft.Scripting.ExtensionAttribute.dll. Советую скопировать их в отдельную папку, так как они нам в дальнейшем еще понадобятся.
Создание наших сборокТеперь мы можем смело открывать Visual Studio и создавать наши сборки. Нам потребуется решение с двумя проектами. Первый проект pyCore - это библиотека классов, непосредственно исполняющая код на языке IronPython. Второй проект pySQL - проект базы данных для CLR, использующий сборку pyCore и содержащий код наших функций и хранимых процедур.
pyCoreЦелевым фреймворком выбираем .net 3.5. Добавляем в проект ссылки на сборки IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll, Microsoft.Scripting.Core.dll. Напомню, что эти библиотеки мы получаем после сборки IronPython из исходников. Наш проект будет содержать всего один статический класс pyCore, ответственный за создание и инициализацию движка IronPython, управление контекстом (scope) и выполнение переданного скрипта. Код библиотеки pyCore Основной интерес представляет функция py_exec, которая принимает текст скрипта, контекст выполнения и аргументы, которые должны быть переданы в скрипт. Теперь необходимо создать сборку pyCore в базе данных CLR. Для этого выполним следующий скрипт:
Скорее всего, Вы получите ошибку, следующего вида: Assembly 'pyCore' references assembly 'system.runtime.remoting, version=2.0.0.0, culture=neutral, publickeytoken=b77a5c561934e089.', which is not present in the current database. Иными словами - не все библиотеки, необходимые для работы pyCore, присутствуют в базе. Чтобы не утомлять читателя, я приведу сразу скрипт, загружающий все необходимое. После ключевого слова FROM необходимо указать полный путь к сборке. Большинство сборок получаем собрав IronPython из исходников. Сборку System.Runtime.Remoting.dll можно найти в C:\Windows\Microsoft.NET\Framework\v2.0.50727\ Скрипт создания всех необходимых сборок pySQLЧерная работа сделана, и самое время начать реализовывать процедуры и функции доступные из SQL Server. Создадим проект баз данных CLR и в строке соединения укажем нашу базу данных для тестов. Теперь необходимо добавить ссылку на сборку pyCore. Если вы правильно указали строку соединения с базой данных в проекте, то при добавлении новой ссылки вы увидите все сборки, существующие в базе данных. Среди них выбираем pyCore, Scripting и ScriptingCore.
Функция CLRДобавим в проект новый элемент - пользовательскую функцию.
В функциях не происходит ничего интересного - прямой вызов py_exec и перенаправление аргументов. Здесь предусмотрено два варианта использования: передача параметров в скрипт при формировании текста скрипта и явная передача параметров через массив args. Второй способ, на мой взгляд, более читабелен и безопасен.
При объявлении функции CLR в SQL Server происходит сравнение сигнатур, которое не понимает или по каким-то другим причинам не учитывает ключевого слова params. В результате приходится объявлять несколько функций с различным числом параметров. В реальности, редко встречается необходимость создавать функции с числом параметров больше трех-четырех, так что это не очень существенное ограничение.
Процедура CLR
Внутреннее устройство процедуры немного отличается от функции. Дополнительно передаем в скрипт ссылку на экземпляр объекта SqlPipe, чтобы можно было возвращать табличный результат и выводить сообщения.
Агрегирующая функцияАгрегирующую функцию нельзя создать, используя Transact SQL. Единственный вариант - использование сборок CLR. Почему это так становится ясно при первом взгляде на структуру агрегирующей функции CLR. Код агрегирующей функции Мы реализуем интерфейс IBinarySerialize чтобы предоставить скрипту возможность сохранять свое состояние и промежуточный результат вычислений. Так как функция Init не принимает аргументов, скрипт инициализации приходится выполнять при первом запуске функции Accumulate. Наша агрегирующая функция принимает тексты скриптов для обработки каждого события. Сами тексты скриптов сохраняются во внутренних полях объекта и сериализуются.
Создание сборки pySQL в базе и объявление функцийТеперь, когда сборка готова, ее необходимо развернуть в базе CLR.
Теперь объявим наши функции и процедуры.
РезультатыЕсли вы дочитали до этого момента, значит, вы вправе наградить себя, увидев результаты своего труда.
ФункцияДля начала решим задачу с регулярными выражениями - найдем email адреса в строке. Саму строку будем вставлять непосредственно в скрипт при его формировании.
Результат: Найдено: somebody@gmail.com Найдено: person1@mail.ru,person2@list.ru То же самое, но с использованием параметров. На мой взгляд - более красивый способ, однако требующий объявления функции с нужным числом аргументов.
Результат естественно тот же. Такую функцию можно использовать и для вычисления сложных математических функций, не встроенных в SQL Server и для динамического вычисления выражений (этого можно добиться и с помощью sp_execute).
Хранимая процедураПриведу сразу полный пример. Здесь мы пишем текстовое сообщение, используя объект SqlPipe, заботливо переданный нашему скрипту, потом формируем таблицу, заполняем данными и возвращаем в качестве результата.
Результат выполнения: Пишем сообщение: поехали!
(1 row(s) affected)
Агрегирующая функцияПеречислим через запятую языки, использованные в статье, с использованием нашей агрегирующей функции. В скриптах сериализации и десериализации используем ссылки на экземпляры классов BinaryReader и BinaryWriter, переданные нашему скрипту. Результат накапливается в переменной data.
Результат:
(2 row(s) affected)
ПроизводительностьКак и следовало ожидать, производительность невелика. Например, скорость вывода строки справа налево стандартной функцией T-SQL REVERSE и с помощью операции среза в python отличается почти в 80 раз.
Безопасность
Так как наши возможности ничем не ограничены, мы можем выполнить любой код, в том числе обратится к ресурсу в интернете или удалить файл с диска. Поэтому раздавать права нужно с осторожностью и использовать преимущественно передачу параметров в функцию вместо составления скрипта по частям.
Вместо заключенияКаждый инструмент нужно использовать только там, где это действительно имеет смысл. Не стоит пытаться переписывать все ваши хранимые процедуры и функции с использованием IronPython. Скорее функции на IronPython подойдут для реализации сложных алгоритмов, использующих функциональность, отсутствующую в Transact SQL или обработки данных из внешних источников (файловая система, интернет). Помимо IronPython желающие могут встроить поддержку IronRuby или, например, Javascript .NET. |