Мини заметки — выпуск 16
9 декабря 2013
Темы выпуска: автоматическая генерация функции UPSERT в PostgreSQL, почему при работе с деньгами нужно использовать рациональные числа, настройка всплывающих сообщений в Claws Mail и не только. Предыдущие выпуски мини заметок: пятнадцатый, четырнадцатый, тринадцатый, двенадцатый.
1. Русская раскладка в Microsoft Windows 2000
В честь попыток вспомнить WinAPI я тут игрался под VirtualBox c Windows 2000. Оказывается, чтобы под Win2k включить русскую раскладку, нужно зайти в Control Panel, открыть Regional Options, в списке Language settings for the system поставить галочку Cyrillic. Винда запросит установочный диск. После выполнения этих шагов в настройках клавиатуры можно будет выбрать русскую раскладку.
2. Сборка минимального exe-шника с помощью MinGW
Тут основная проблема состоит в том, что при компиляции кода на Си добавляется вызов процедуры __main и, похоже, нет флага, позволяющего отключить это поведение. В результате приходится делать как-то так:
$ $MINGW/cc1 -DUNICODE test.c -o /tmp/test0.s
$ cat /tmp/test0.s | grep -v ___main > /tmp/test.s
$ /usr/i586-mingw32msvc/bin/as /tmp/test.s -o /tmp/test.o
$ $MINGW/collect2 /tmp/test.o -luser32 -lkernel32 -s -o test.exe
Транслируем код на Си в ассемблерный код, grep-ом выпиливаем вызов ___main, затем компилируем ассемблерный код и линкуем все хозяйство в исполняемый файл.
3. Как декомпилировать beam в код на Erlang?
Знаете ли вы, что если эрланговый модуль был собран с флагом debug_info, то можно полностью восстановить его исходный код за исключением разве что комментариев?
Рассмотрим такой модуль:
-export([test_func/1]).
% test comment
test_func(Name) ->
"Hello, " ++ Name ++ "!".
Скомпилируем его с флагом debug_info:
А теперь получим AST кода, используя только beam:
{ok,{test,[{abstract_code,{raw_abstract_v1,[{attribute,1,file,{"test.erl",1}},{attribute,1,module,test},{attribute,3,export,[{test_func,1}]},{function,6,test_func,1,[{clause,6,[{var,6,'Name'}],[],[{op,7,'++',{string,7,"Hello, "},{op,7,'++',{var,7,'Name'},{string,7,"!"}}}]}]},{eof,8}]}}]}}
При желании не представляет большого труда написать программу, получающую исходный код из этого AST. Но, к счастью, за нас уже все написано:
2> beam_lib:chunks("test.beam", [abstract_code]), ok.
ok
3> Src = erl_prettypr:format(erl_syntax:form_list(Abs)).
"-module(test).\n\n-export([test_func/1]).\n\ntest_func(Name) -> \"Hello, \" ++ Name ++ \"!\".\n\n"
4> file:write_file("test_src.erl", Src).
ok
Если теперь посмотреть test_src.erl, вы обнаружите, что он не сильно отличается от оригинального test.erl.
4. Простой FOR-цикл на PL/pgSQL
Если вы хотите выполнить какие-то запросы для каждого id пользователя, используйте следующую конструкцию:
Пригодится, например, при миграции данных.
5. Как в PostgreSQL найти все столбцы определенного типа
Следующий запрос находит все столбцы во всех таблицах базы данных, имеющие тип numeric:
Благодаря этому приему можно делать разные интересные вещи. Например, можно проверить, не записаны ли где-нибудь в базе странные числа типа 0.0064999999:
Функция eval была найдена на StackOverflow:
as
$body$
declare
result integer;
begin
execute expression into result;
return result;
end;
$body$
language plpgsql
Как нетрудно догадаться, она выполняет запрос, переданный первым аргументом в виде строки.
6. Как подрубить PL/Perl к PostgreSQL
Как отмечалось в заметке Некоторые интересные отличия PostgreSQL от MySQL, PostgreSQL позволяет писать тригеры и хранимые процедуры на языках Perl, Python, Tcl и других. В ряде случаев это оказывается весьма полезно.
Для установки PL/Perl на сервере, где крутиться PostgreSQL, говорим:
sudo -u postgres psql
Включаем PL/Perl для требуемой базы данных:
my_database=# CREATE EXTENSION plperl;
CREATE EXTENSION
my_database=# \q
Проверяем, что все работает:
my ($x) = @_;
return $x + 1;
$$ language plperl;
select my_inc(1.23);
my_inc
--------
2.23
(1 row)
А вот пример функции, округляющей числа типа 12.349999999 и -5.43200000001 до 12.35 и -5.432 соответственно:
use strict;
my ($x) = @_;
my ($l, $r) = split /\./, $x;
my $i = index $r, "00000";
if($i >= 0) {
$r = substr $r, 0, $i;
$r = "0" unless $r;
} else {
$i = index $r, "99999";
if($i >= 0) {
$r = substr $r, 0, $i;
$r++;
}
}
return "$l.$r";
$$ language plperl;
Попробуйте написать такое на PL/pgSQL!
7. Как в PostgreSQL сгенерировать функцию MERGE/UPSERT
Дополнение: В более новых версиях PostgreSQL есть встроенный UPSERT. Пример его использования можно найти в посте Пример использования триггеров в PostgreSQL.
Теперь, владея информацией из пунктов 5 и 6, не представляет труда написать функцию, генерирующую код UPSERT-функции для таблицы с заданным именем:
$$
use strict;
my ($tbl) = @_;
my $fun_name = "merge_$tbl";
my $pk_rv = spi_exec_query(qq{
SELECT c.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage AS ccu
USING (constraint_schema, constraint_name)
JOIN information_schema.columns AS c
ON c.table_schema = tc.constraint_schema AND
tc.table_name = c.table_name AND
ccu.column_name = c.column_name
WHERE constraint_type = 'PRIMARY KEY' AND tc.table_name = '$tbl'
ORDER BY c.ordinal_position;
});
my @pk_arr;
push @pk_arr, $pk_rv->{rows}[$_]->{column_name}
for 0..$pk_rv->{processed} - 1;
my $info_rv = spi_exec_query(qq{
select column_name, data_type
from information_schema.columns
where table_name = '$tbl'
order by ordinal_position;
});
my @columns;
my @args;
for (0..$info_rv->{processed} - 1) {
my $col = $info_rv->{rows}[$_]->{column_name};
my $type = $info_rv->{rows}[$_]->{data_type};
push @columns, $col;
push @args, qq{"$col" $type};
}
my $sep = "\n" . (" " x 15);
my $arguments = join ",$sep", @args;
my $insert_fields = join ",$sep", (map { qq{"$_"} } @columns);
my $insert_values = join ",$sep", (map { qq{$fun_name."$_"} }
@columns);
my %pk_filt;
@pk_filt{@pk_arr} = 1;
my @upd_tmp;
for my $c (grep { not exists $pk_filt{$_} } @columns) {
push @upd_tmp, qq{"$c" = $fun_name."$c"};
}
my $update_set = join ",$sep", @upd_tmp;
my @updw_tmp;
for my $c (@pk_arr) {
push @updw_tmp, qq{$tbl."$c" = $fun_name."$c"};
}
my $update_where = join " AND$sep", @updw_tmp;
return qq{
CREATE OR REPLACE FUNCTION $fun_name($sep$arguments) RETURNS VOID AS
\$\$
BEGIN
LOOP
UPDATE $tbl SET $sep$update_set
WHERE $sep$update_where;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO $tbl($sep$insert_fields
) VALUES ($sep$insert_values);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing
END;
END LOOP;
END;
\$\$
LANGUAGE plpgsql;
};
$$ language plperl;
Чтобы не копипастить генерируемый код в скрипты миграции, можно использовать такую конструкцию:
Ну или если вы зомбированы Хабрахабром и испытываете беспричинный страх перед PL/Perl в продакшене, можно получить код UPSERT-функции в тестовом окружении как-то так:
psql -h postgresql.test.example.com mydb myuser -A -t
… а потом копипастить его куда вам нужно.
8. О настройке всплывающих сообщений в Skype и Claws Mail
В Ubuntu для вывода стандартных всплывающих сообщений есть замечательная утилита netify-send:
notify-send --help
В настройках Skype можно отключить стандартные всплывающие сообщения с мелким текстом и использовать вместо них notify-send. Подробности о доступных переменных (имя контакта, сообщение, размер передаваемого файла) можно найти, например, здесь.
Этот же прием работает и с Claws Mail. Идем в Configuration → Preferences → Receiving → Run command и прописываем там что-то вроде:
А еще для меня недавно стало открытием, что в Skype можно включать и отключать уведомления о новых сообщениях в чате при помощи команд /alertson и /alertsoff.
9. Как отличить 32-х битный Erlang от 64-х битного
В REPL говорим:
Если Erlang 64-х битный, увидим 8, иначе — 4.
10. Почему при работе с деньгами нужно использовать rational
В мире Erlang для представления чисел с произвольной точностью часто рекомендуется использовать библиотеку decimal. Эта библиотека эксплуатирует возможность Erlang’а работать с произвольно большими целыми числами, представляя дробные числа в виде кортежа {S, M, E}
, где S представляет собой знак числа, M — мантиссу, а E — порядок. Например, числа 1.23 и -45000 хранятся в виде кортежей {0, 123, -2}
и {1, 45, 3}
соответственно. Проблема этого представления заключается в том, что с его помощью нельзя с произвольной точностью представить такие числа, как 1/3, 100/85 и другие. Как правило, это не представляет собой проблемы, так как при разработке системы оговаривается, что некие расчеты производятся с точностью, например, до 100 знаков после запятой. Однако следует быть готовым к тому, что, например, 1/3 * 3 не будет равно в точности 1:
"0.999999999"
Если в своих вычислениях вы используете только операции сложения, вычитания, умножения, деления и, может быть, возведения в степень, возможно, следует воспользоваться библиотекой для работы с рациональными числами. При этих условиях ваши расчеты будут производится действительно с произвольной точностью, то есть 1/3 * 3 будет равно в точности 1. В мире Haskell для работы с рациональными числами есть модуль Data.Ratio, а реализацию для Erlang можно найти в библиотеке erlang-tools.
Дополнение: Мини заметки — выпуск 17, полностью посвященный Haskell
Метки: Всячина.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.