2015-07-15

Postgres + Python = Awesome

Чем больше работаю с Postgres, тем больше радует меня эта СУБД. Шикарная работа с JSON, позволяющая хранить структуры разнотипных данных в полях JSON и JSONB, и даже вешать на них индексы для быстрого поиска внутри них - находка для архивирования твитов и им подобных. Отличный набор функций для обработки текста, включая regexp. Типы-массивы (тоже с индексами). Переменных-таблиц нет, но их с успехом заменяют переменные-массивы или временные таблицы. Вот некоторые выражения, приводящие меня в щенячий восторг:


    -- Новая таблица по образцу выборки
    CREATE TABLE new_table AS SELECT field1, field2 FROM old_table LIMIT 10;

    -- Временная самоликвидирующаяся таблица
    CREATE TEMP TABLE IF NOT EXISTS new_table(id INT, data JSON) ON COMMIT DROP;

    -- Одно и то-же, во втором случае используется массив
    SELECT * FROM old_table WHERE id IN (1,2,3);
    SELECT * FROM old_table WHERE id = ANY('{1,2,3}');

    -- Создание массива в процессе группировки
    SELECT array_agg(text_id) FROM old_table WHERE id IN (1,2,3);

    -- JSON-массив JSON-объектов
    SELECT json_agg(t.jobj) FROM (
        SELECT json_build_object('text_id', id, 'record_id', _id) jobj
        FROM old_table
    ) t;

    -- Cоотношение твитов с вложенными чувствами и черствых:
    -- на выходе что-то вроде "{ "false" : 9115, "true" : 166 }"
    SELECT json_object_agg(t.ps, t.c) FROM (
        SELECT src#>>'{possibly_sensitive}' ps, COUNT(_id) c
        FROM tweet_archive
        WHERE src#>>'{possibly_sensitive}' = ANY('{false,true}') -- а тут массив с текстами
        GROUP BY src#>>'{possibly_sensitive}'
    ) t;

Для работы с Postgres используется Python и стандартная библиотека json. Для компактности и корректной работы с не-ASCII символами внутри JSON-полей Postgres при упаковке твитов используются такие параметры:

    json.dumps(tweet, separators=(',',':'), encoding='utf-8', ensure_ascii=False)

Еще одна возможность, которая может пригодиться - создание процедур в Postgres, используя Python. Думаю, примера будет достаточно, чтобы понять, как это делается (конечно официальной документации никто не отменял):


    CREATE OR REPLACE FUNCTION outer_procedure(p_limit INT) RETURNS VOID AS $$
        # Добавление пути для импорта нашего модуля
        import sys
        sys.path.insert(0, '/home/developer/plpython_func/')

        # Загрузка модуля
        import test_functions

        # На время разработки, пока модуль будет меняться,
        # его нужно насильно перезагружать, т.к. он кешируется
        reload(test_functions)

        # Собственно, запуск функции из модуля.
        # plpy - это объект для взаимодействия с БД
        test_functions.run(plpy, p_limit)

        return None
    $$ LANGUAGE plpythonu VOLATILE STRICT;

Внешняя процедура на Python (test_functions.py), которую мы загружаем:


    # -*- coding: utf-8 -*-

    import traceback

    def run(plpy, limit):

        # Готовим план выполнения
        sql = 'INSERT INTO new_table(id, text) VALUES($1, $2)'
        plan = plpy.prepare(sql, ["int", "text"])

        # Выполняем вставку внутри транзакции, ловим ошибки
        try:
            with plpy.subtransaction():
                plpy.execute(plan, [1, "text"])
        except plpy.SPIError as e:
            plpy.error("Error inside transaction: %s" % e.args)
        except:
            plpy.error(traceback.format_exc(10))

2015-06-23

Муки выбора

Наверное, это свойство присуще многим. Я имею ввиду поиск нового и лучшего в той сфере, которая тебе интересна. Но иногда этот поиск и муки выбора просто выводят из равновесия. Знакомо ли вам страдание по поводу выбора нового языка программирования? Долгие размышления о том, насколько он перспективен, как широки сферы его применения, насколько востребован на рынке и сколько платят разработчикам. Сканирование рынка вакансий, изучение рейтинга github.com, чтение статей на habrahabr.ru, сравнительных постов типа "A vs B" и т.п. Написание мегапроектов типа "Hello world", решений "вот оно!" и через день - возвращение сомнений "а оно ли?" - и все начинается сначала.

И вроде бы зачем выбирать, выучи всё, что нравится. Но без хорошей практики профи не станешь, а для практики во всём жизни не хватит. А годы идут без остановки, и новые технологии появляются постоянно. И многие весьма интересны, как новые игрушки для ребенка - хочется все их попробовать, повертеть, разобраться как работают. Да сколько можно то!

Конечно, время от времени приходит какое-то просветление, быть может это включается рациональное мышление. Есть сфера, в которой немало опыта, и которая нравится, стоит сделать упор на нее - а остальные ниши оставить другим. Только червячок сомнений "а вдруг это тоже очень интересно, а ты упустишь" заставляет иногда пускаться в хоровод.

Пост философский, просто вдруг найдется родственная душа со схожими переживаниями, и сможет поделиться, как с этим справляется.

P.S. Сейчас основным языком разработки у меня Python, а из нового в любимчики выбился Go. А почему именно он - может быть опишу отдельно.

2015-02-26

PostgreSQL: как получить несколько выборок (роусетов)

Переводя проект с использования AzureSQL (MSSQL) на PostgreSQL я столкнулся с тем, что последний не умеет возвращать результаты нескольких запросов SELECT (так называемые роусеты, т.е. наборы строк). К примеру, некая процедура генерирует SELECT из нескольких таблиц, чтобы отобразить отчет по этой сборной солянке. Так вот, в отличии от MSSQL, PostgreSQL возвращает только последий из них, такая уж особенность. Странно, но факт.

Что делать? Мне в голову приходили три варианта.

Один из них - создать в процедуре временные таблицы с гарантированно уникальными именами, сложить наборы строк в них и вернуть их список. На клиенте соответственно понадобится сделать запросы к этим таблицам и при необходимости их уничтожить. Минусов у такого подхода полно, взять хотя бы увеличение количества запросов к БД и возможные коллизии в случае забывания удаления временных таблиц (хотя это решаемо).

Второй способ - генерировать бинарные строки специфического формата (к примеру, Паскалевскую строку) из всех выборок и вернуть один SELECT с этими бинарниками. Минус - сложность при создании и распаковке на клиенте, хотя и позволяет получить сразу все данные одним запросом, к тому же очень компактно.

И наконец, третий вариант, который работает в PostgreSQL благодаря поддержке типа данных JSON. Думаю, простой демонстрации кода будет достаточно, чтобы понять, как это работает:

SELECT json_agg(json_build_array(r.id, r.title))
FROM (SELECT id, title FROM queries LIMIT 3) r

Результат:

[
    [1, "Query 1"],
    [2, "Query 2"],
    [3, "Query 3"],
]

Запрос:

SELECT json_build_array(
    (SELECT json_agg(json_build_array(r.id, r.title))
    FROM (SELECT id, title FROM queries LIMIT 2) r),
    (SELECT json_agg(json_build_array(r.id, r.name))
    FROM (SELECT id, name FROM companies LIMIT 2 OFFSET 2) r)
);

Результат:

[
    [
        [1, "Query 1"],
        [2, "Query 2"]
    ],
    [
        [3, "Company 1"],
        [4, "Company 2"]
    ]
]

2015-01-30

Как узнать пароль в форме

Иногда такое случается - забыть пароль. А вот браузер его помнит. И даже заполняет форму звездочками. Но вот скопировать эти звездочки в виде текста не удается.

А вот решение. Открываем панель просмотра элементов, в Google Chrome это происходит по клавише F12. Находим элемент с паролем и меняем его атрибут "type" с "password" на "text":



Вот собственно, и всё. Не оставляйте ваши компьютеры в чужих руках, ведь для вышеописанных действий достаточно 10-15 секунд!

2014-10-16

Python: how to start independent process

Задача: запустить полностью независимый процесс из Python. Попробуем стартовать в Linux простой HTTP сервер и отправить его в свободное плавание:

import subprocess
subprocess.Popen(['python', '-m', 'SimpleHTTPServer', '5000'], close_fds=True)
Однако параметр "close_fds" в Windows не работает: "Note that on Windows, you cannot set close_fds to true". Как вариант, можно использовать стандартные компоненты Windows, cmd.exe и команду start:

import subprocess
subprocess.Popen([‘C:\Windows\System32\cmd.exe’, ‘/C’, ’start’, ‘C:\Python27\Python.exe’, ‘-m’, ’SimpleHTTPServer’, ‘5000’])
Это создаст новое окно cmd.exe, где будет запущен http-сервер. Что и требовалось.