🚨 Особенности перехода с «Оракла» на «Постгрес»

В комментариях пообещал поописывать свой опыт перевода продукта с «Оракла» на «Постгрес», особенно в особенностях, которым очень мало уделено внимания на просторах интернета. Начну с простого, зато сразу целой пачкой. Примеры я, естественно, упростил, чтобы максимально выпукло продемонстрировать различия.

У нас довольно много генерируемых простых запросов, иногда они генерировались не очень аккуратно. «Оракл» такое прощает, а «Постгрес» — нет. Запрос, где одни и те же таблицы соединяются дважды «Постгресу» не по вкусу:
SELECT * FROM test INNER JOIN test1 USING(n) INNER JOIN test USING(n);

ERROR:  table name "test" specified more than once
«Постгрес» очень трепетно относится к тому, чтобы у подзапроса был алиас, даже если он потом нигде не используется:
SELECT * FROM (SELECT * FROM test);

ERROR:  subquery in FROM must have an alias
СТРОКА 1: SELECT * FROM (SELECT * FROM test);
                        ^
ПОДСКАЗКА:  For example, FROM (SELECT ...) [AS] foo.
В «Постгресе», в запросах на обновление нельзя указать алиас. Тут это логично, так как синтаксис подзапросов в этом случае у этой СУБД совершенно иной — они указываются в специальном месте.
UPDATE test t SET t.n=1;

ERROR:  column "t" of relation "test" does not exist
СТРОКА 1: UPDATE test t SET t.n=1;
Если «Оракл» спокойно относится к двум и более таблицам с одним алиасом, то «Постгресу» от этого плохеет. Жаль, иногда удобно объединить несколько таблиц алиасом прямо на месте и работать дальше с ним, как с единой таблицой.
SELECT t.n FROM test t, another_test t;

ERROR:  table name "t" specified more than once
В «Оракле» ключевое слово FROM не обязательно в запросах на удаление. У нас было несколько запросов без него.
DELETE test;

ERROR:  syntax error at or near "test"
СТРОКА 1: DELETE test;
В следующий раз буду описывать более комплексные примеры.
4 комментария
24 февраля 2016 10:41

✨ Двойные миграции (Оракл+Постгрес)

У нас идёт замена в наших продуктах сильно платного «Оракла» (это база данных) на бесплатный «Постргес». Несмотря на ударные темпы, продолжаться это ещё будет долго. Продуктов много, несмотря на то, что начали мы ещё в прошлом году, весь этот год будем жить сразу на двух базах.

В этой связи, когда есть различия, приходится миграции писать в двух комплектах — под каждый диалект. Я придумал как писать их так, чтобы каждая БД видела куски кода, предназначенные только ей.

Например:
CREATE UNIQUE INDEX dn_part_num_org_n_cat ON document_n(/*/**/
CASE WHEN d_deleted = 0 AND num IS NULL AND n=0 AND category=0 THEN id END,
CASE WHEN d_deleted = 0 AND num IS NULL AND n=0 AND category=0 THEN org_id END);
--*/ id, org_id) WHERE d_deleted=0 AND num IS NULL AND n=0 AND category = 0;
Часть с /*/**/ и до --*/ видит только «Оракл» — для него это выглядит так: комментарий открывается, сразу закрывается, идёт код, который он и воспринимает, а последняя строка закоментирована при помощи двух минусов — это стандартный коментарий в эскуэле.

«Постгрес» эту часть не видит — он поддерживает вложенные коментарии, поэтому его интерпретация другая: открываются два комментария, первый закрывается сразу, а второй — на последней строке, остаток которой «Постгрес» воспринимает как часть кода.
30 комментариев
20 февраля 2016 16:35

🎱 Сложности при написании «99 бутылок» на «Электронике МК-61»

Электроника МК-61 (127.50КиБ)
Тот самый калькулятор «Электроника МК-61», который попал мне в руки

Как и обещал в прошлый раз, хочу рассказать с какими сложностями мне пришлось столкнуться при написании «Песни о пиве» на программируемый калькулятор «Электроника МК-61».

Калькулятор попал ко мне для передачи в компьютерный музей, который готовится к открытию в высшей школе «ИТИС» и у меня и мыслей-то не было что-то под него писать — хотелось лишь посмотреть что из себя представляет написание программ под этот гаджет, с которыми я много сталкивался в детстве через журнал «Наука и жизнь».

В процессе изучения я наткнулся на заинтриговавшую меня вещь. В некоторых операциях калькулятор переключался в режим шестнадцатеричного счисления, о чём сигнализировала восьмёрка и точка в первом разряде. Шестнадцатеричная система калькулятора (38.55КиБ)
По этой таблице калькулятор отображает шестнадцатеричные числа

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

Тем более мне очень повезло в том, что индикацией этого режима является именно восьмёрка, стоящая спереди — так похожая на первую букву в нужном мне слове. Как вы теперь понимаете, на экране в моей программе написано на «BEEr», с точки зрения калькулятора там одни числа и индикатор режима — «8.EEГ» или «EED» в привычной записи (десятичное «3821»).

Так же я воспользовался тем, что «F» в этой странной нотации — пробел, что позволило мне отделить число от «надписи». Дальнейшее — дело техники.

Листинг из предыдущего поста начинается с подготовки данных в регистрах — нескольких масок на которые битовыми операциями в дальнейшем наложатся числа так, чтобы получилась нужная мне «строка». Настоящая программа начинается с нажатий на «В/О» и «ПРГ», и активно пользуется подготовленными данными.

Маски и оставшиеся числа побитно накладываются двумя подпрограммами — по отдельности на диапазоны 0…5 и 6…9 — способы их получения разнятся. Я широко использую операцию «ИНВ» — она инвертирует биты числа, по логике инвертирования цифры 6…9 превращаются в на свои позициях 9…6, и тут никаких сложностей, а знаки в диапазоне 0…5 получаются сложнее — там накладываются сразу две специальные маски и счётчик.

Для примера кусочек подпрограммы, номер слева соответствует номеру инструкции при вводе в калькулятор и каждую строку я прокомментирую, но следует понимать, что в реальности эти комментарии частью программируемы не являются:
// когда что-то попадает в «X» предыдущее значение смещается в «Y» (там стек из 4 позиций)
45 1 // «1» кладётся в регистр «X», входной параметр сместился в «Y»
46 + // Y + 1, передаётся в «X», это коррекция данных после операций, которые шли выше
47 К П→Х 7 // в «X» помещается число из регистра (маска), указанного в регистре №7
48 + // Y+X → X
49 ИНВ // инвертирование битов в «X», сейчас на экране написано «8.EEГ» плюс некий пока мусорный символ
50 К П→Х 8 // в «X» помещается число из регистра, указанного в регистре №8
51 ^ // операция «И», на экране — «8.EEГ», из некого символа получилось требуемое число
52 В/О // возврат из подпрограммы
Десятки и единицы выбираются адресами масок, которые я передаю при помощи косвенной адресации, хорошо, что такое вообще есть в языке — это позволяет эффективно пользоваться подпрограммами.

Подпрограмма вызывается два раза с разными параметрами — для десятков и единиц, получаются две маски, которые позднее накладываются друг на друга.

Сильно расстраивает отсутствие памяти для долговременного хранения, в устройстве хоть и есть батарейный отсек, но при любом сбое питания (батарейки сели, а блок питания не подключен), память калькулятор сбрасывается и программу придётся набрать заново. То же ожидает и в случае, если одну программу нужно будет заменить другой — хотя программы можно запускать с произвольного адреса, много их в память не уместится — её ёмкость всего 105 инструкций.
6 комментариев
6 февраля 2016 22:41

📱«99 бутылок» на языке «Электроники МК-61»

«Электроника МК-61» (59.82КиБ)
«Электроника МК-61» с запущенной на нём программой

«Электроника МК-61» — устройство из класса «программирумых калькуляторов». Были когда-то такие гаджеты, позволявшие со всеми ограничениями калькуляторов (небольшая клавиатура, типичный для калькулятора экран) писать программы.

В детстве такие калькуляторы я видел только на картинках в журнале «Наука и жизнь», а программы для них были каким-то любопытным закорючками, в которых не разобраться.

Вчера мне в руки попал настоящая «Электроника МК-61» (для передачи компьютерному музею) и я наконец-то на обеде и потом вечером разобрался в этих странных значках.

Так выглядят мои «99 бутылок…» на этом калькуляторе:
8112000 Х→П c 8 + ИНВ Х→П b Вх 10 ÷ [X] Х→П d 8 + ИНВ Х→П e В/О ПРГ
П→Х 0 1 0 ÷ [x] Х→П 1 Вх {x} 1 0 × Х→П 2
1 1 Х→П 8 1 2 Х→П 7 П→Х 2 ПП 34 Х→П a
1 4 Х→П 8 1 3 Х→П 7 П→Х 1 ПП 34 П→Х a
^ С/П
6 - /-/ x<0 45 9 + К П→Х 7 + ИНВ В/О
1 + К П→Х 7 + ИНВ К П→Х 8 ^ В/О
Поскольку у меня в блоге скорее всего отсутствуют любители работать каждый день с калькуляторами советских времён, то я не буду придерживаться принятой нотации с номерами — не думаю, что кто-то будет это набирать.

Вкратце о языке. Больше всего это похоже на крайне примитивный ассемблер с гипертрофированным математическим сопроцессором. Всё то же — переходы на адрес, одно- и двухбайтные инструкции с операндами, регистры с определёнными назначениями, сильно ограниченная память (105 команд), наличествует косвенная адресация и подпрограммы (стек вызовов размером в пять вхождений).

До запуска программы нужно в регистр «0» поместить стартовое значение (например, «99», «Х→П», «0») и запустить программу «В/О», «С/П».

Это я сделал из-за того, что цикла в программе нет — из-за мельтишения разрядов совершенно не понять, что происходит. Хотя добавить его очень просто — до команды «С/П» надо вставить одну проверку с переходом и сдвинуть адреса.

Поэтому на вход подаётся нужное число, например, «42», а на выходе мы можем полюбоваться фразой «BEEr 42». Не весть что, но что вы хотели от калькулятора? Кстати, выводимая фраза — не строка (спойлер: а число), о том как мне пришлось помучаться я ещё расскажу.
4 комментария
5 февраля 2016 07:38

Подпилил переподсоединялку вайфая

Чуть поменял «переподсоединялку вайфая» для Мака — теперь пингуется не 8.8.8.8 (это ДНС Гугла), а адрес гейта, что более надёжно. Правда с конца октября, когда я её написал много воды утекло — вайфай у меня отваливается теперь очень редко, уж не знаю что изменилось.
2 комментария
19 января 2016 16:13

Дерево в цепочках

Дерево в цепочках (120.20КиБ) Я тут в блоге как-то намекал, что современные сервисы ведения родовых деревьев рассчитаны скорее на казуалов, чем на людей, для которых это серьёзное хобби. Даже подумывал написать свой, но оставил эту затею — я хорошо представляю объём работы, надо либо делать это основной своей работой, либо не делать вовсе.

В общем, там где мне совсем сильно жмёт, я пишу иногда небольшие скрипты. Недавно написал очередной и решил поделиться. Этот скрипт (написан на ПХП7) позволяет развернуть дерево в формате GEDCOM (это общепринятый формат для хранения информации о родословной) в цепочки — то есть простраивает все пути от указанного человека до каждого его предка.

Это позволяет быстро увидеть какие ветки у вас проработаны, а какие нет. Классическое дерево на моих объёмах это уже не показывает — его просто невозможно охватить взглядом и понять где есть проблема.

Для запуска требуются два параметра: нужно указать путь до файла дерева и идентификатор человека, от которого нужно простроить цепочки, его надо посмотреть внутри файла (GEDCOM текстовый формат). Для этого нужно найти внутри запись нужной персоны и выше неё — строку вида «0 @буквыцифры@ INDI», вот часть «@буквыцифры@» и будет требуемым идентификатором.
25 комментариев
11 января 2016 21:52

strtr vs. str_replace

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

Кроме того, часто выход какой-то новой версии обесценивал результаты большинства исследований в этой области. Недавний выход «семёрки» с блеском это продемонстрировал.

Среди подобных текстов есть и вредные. Речь об одном из них и пойдёт. Впервые на сравнение производительности функций str_replace и strtr (с одним параметром) я наткнулся очень много лет назад, тогда с ним разобрался и тему для себя закрыл.

Но такие исследования возникают постоянно, а авторы совершенно не утруждают себя ответом на вопрос «почему», так что я решил написать этот небольшой текст.

Итак, есть две функции, которые ведут себя очень похожим образом — заменяют в строке что-то одно на что-то другое. Велик соблазн объявить их совершенно одинаковыми (что часто и происходит), тем более их описание в руководстве по языку не акцентирует внимание на различиях. Но они есть, и очень важные.

Обратите внимание на этот код:
$r = ['xo' => 'xaxе', 'xa' => 'xo'];
$s = 'xoxo';

var_dump(str_replace(array_keys($r), array_values($r), $s)); // xoxеxoxе
var_dump(strtr($s, $r)); // xaxеxaxе
Очевидно результаты разные, но почему? Потому что str_replace делает замену «в лоб» — проходя строку несколько раз, по числу заданных замен. Поэтому получается «xoxo» → «xaxexaxe» → «xoxexoxe».

Функция strtr куда более хитрая, но работает более… естественным что ли образом — т.е. так как обычно человек ждёт от функции множественной замены. Самое главное — в сделанной замене она уже ничего не заменяет.

Есть и другое отличие:
$r = ['user1' => 'one', 'user11' => 'eleven'];
$s = 'user11';

var_dump(str_replace(array_keys($r), array_values($r), $s)); // one1
var_dump(strtr($s, $r)); // eleven
Мой пример выглядит несколько искусственно, но это часть случая из жизни, правда очень упрощённого. Тут беда в том, что str_replace применяет свои замены слева направо — в порядке перечисления, тогда как strtr сначала сортирует их по длине.

Как видите, эти функции совершенно разные по своему действию и сравнивать по производительности их просто некорректно.
3 комментария
4 января 2016 16:00

Void в PHP

ПХП7 только-только вышел, а авторы языка уже приступили к следующим версиям. Если 7.0.1 будет лишь работой над ошибками, в 7.1 язык продолжит своё совершенствование. Первая ласточка — реализовано указание на отсутствие возвращаемого значения у функции (void).

Я сначала не понял зачем вводить в язык новое ключевое слово, можно было бы использовать для такого указания уже существующее ключевое слово null, но оказалось, что авторы под void имели ввиду, что функция не может вернуть никакого значения:
function returns_null(): void {
    return null; // Fatal error: A void function must not return a value
}
включая null, потому что это определённо значение, хоть и со специальным смыслом. При этом вот такое работает:
function returns_nothing(): void {
    return; // valid
}
Таким образом в языке произошло неявное изменение: раньше ситуации с пустым return и с return null не различались, сейчас они будут иметь разный смысл.
4 комментария
8 декабря 2015 09:13

Отрицательное количество элементов (PHP)

Сегодня утром с разработчиками обсуждали фрагмент кода, где автор очень уж параноидально подошёл к проверке:
if (count($this->to) <= 0) {
	$this->validation_errors[] = "Неверный отправитель";
}
Проверять возвращает ли функция count значение меньшее нуля действительно странно, но я вдруг подумал — а можно ли в ПХП в припципе заставить эту функцию вернуть такое значение?

Дело в том, что функцию count можно использовать не только с примитивными типами (чаще всего её используют с массивами), но и с объектами, которые реализуют интерфейс Countable.

Оказалось вполне нормально работает:
$var = new class implements \Countable {
	public function count()
	{
		return -1;
	}
}

var_dump(count($var)); // выведет int(-1)
Выше код написан в синтаксисе ПХП7, который выйдет сегодня, но его можно переписать и на «пятёрку» — надо только дать имя классу.
2 комментария
3 декабря 2015 10:12

«99» на «Эллочке»

Мне написал автор языка программирования «Эллочка» с предложением написать на нём «99 бутылок…», как вы знаете, у меня есть хобби — писать такие программы на разных языках.

Для этого языка есть единственный, авторский интерпретатор под ДОС (и очень важно использовать в файле программы символы перевода ДОС/Виндоуз, иначе ничего не будет работать), а сам язык напоминает странноватый Бейсик, без поддержки функций и процедур. Их можно эмулировать, используя трюк со специальной переменной «@», которая содержит номер текущей строки.

В языке есть ряд интересных возможностей, но их затмевают недостатки разного калибра, что характерно для языков без сообщества. Даже в таком простом примере мне пришлось побороться за работоспособность программы. Например, перед числами зачем-то всегда выводится символ табуляции (я его срезаю функцией %MID), а когда вывод достигает размера экрана (80×25), то прокрутки не происходит, программа просто жалуется, что «операция не позволяется» (поэтому я просто жду нажатия клавиши после каждой итерации и очищаю экран).
!99 beer song by Evgeny Stepanischev
!Used variables: B, R, $1
B=99
@cycl
    R=@+2 
    goto @beer
    list ' of beer on the wall, '
    R=@+2
    goto @beer
    list '.'\
    list 'Take one down and pass around'\
    decr B
    R=@+2
    goto @beer
    list ' of beer on the wall.'\
    list ''\
    wait
    clsc
esli B >> 0; @cycl
list 'No more bottles of beer on the wall, no more bottles of beer.'\
list 'Go to the store and buy some more, 99 bottles of beer on the wall.'
exit
@beer
esli B >> 0; +3\
list 'No bottles'
goto R
esli B == 1; +5\
$1=B
$1=%MID($1,2,10)
list $1+' bottles'
goto R
list '1 bottle'
goto R
1 комментарий
2 декабря 2015 12:36

Сложение массивов в PHP

Некоторые ребята не знают, что в ПХП есть операция «плюс» над массивами, а открыв её для себя случайным чтением инструкции по языку, не знают чем она отличается от array_merge. Между тем отличия разительные, если их не знать, легко наплодить ошибок в коде.

Операция сложения («плюс») с массивами работает проще всего: если в левом массиве уже есть значение с таким ключом, оно и остаётся:
["L"] + ["R"]; // будет ["L"]
["a" => "L"] + ["a" => "R", "b" => "R"]; // будет ["a" => "L", "b" => "R"]
Операция слияния массивов (array_merge) работает по-разному в зависимости от типа ключей массива. Значения со строковыми ключам правого массива перезаписывают значения левого с тем же ключём, а значения с целыми добавляются в конец левого.
array_merge(["L"], ["R", 4 => "R"]); // ["L", "R", "R"], значения целых ключей правого массива потерялись, сам массив добавился в конец
array_merge(["a" => "L"], ["a" => "R"]); // ["a" => "R"], совпадение строковых ключей
array_merge(["L", "a" => "L"], ["R", "a" => "R", 4 => "R"]); // ["L", "a" => "R", "R", "R"], разные типы ключей вместе
Операция «плюс» очень полезна для добавления группы ключей строковых со значениями (для объединения ассоциативных массивов):
$arr = ["key1" => "value1", "key2" => "value2"];

// вместо
$arr["key3"] = "value3";
$arr["key4"] = "value4";

// проще написать так:
$arr += ["key3" => "value3", "key4" => "value4"];
Но на этом её применение не заканчивается, конечно.
9 комментариев
19 ноября 2015 09:37

«Песенка про пиво» на CMake

Писать «Песни про пиво» на различных языках программирования — давнее моё увлечение, ему столько же лет, сколько этому блогу. В этот раз «поёт» «Си-мэйк».

CMake — автоматизированная система сборки пакетов, имеющая развитый, хотя и несколько уродливый макроязык. Инструкции, большей частью, знакомые, разве что способ возврата значений несколько из макросов и функций несколько странный — через параметры и глобальные переменные.

Никакого ООП, само собой, и всё весьма примитивно по меркам развитых языков
# 99.cmake
# to run: cmake -P 99.cmake
# Written by Evgeny Stepanischev, 2015

cmake_minimum_required(VERSION 3.0)

macro(bottles beer ret)
	if(${beer} EQUAL 0)
		set(${ret} "No bottles")
	elseif(${beer} EQUAL 1)
		set(${ret} "1 bottle")
	else()
		set(${ret} "${${beer}} bottles")
	endif()
endmacro()

foreach(beer RANGE 99 1 -1)
	bottles(beer bottles)
	math(EXPR beer "${beer}-1")
	bottles(beer bottless)

	message("${bottles} of beer on the wall, ${bottles}.")
	message("Take one down and pass it around, ")
	message("${bottless} of beer on the wall.")
	message("")
endforeach()

message("No more bottles of beer on the wall, no more bottles of beer.")
message("Go to the store and buy some more, 99 bottles of beer on the wall.")
Строка для запуска во второй строке файла.
3 комментария
2 ноября 2015 22:00

Тег HYPE (Netscape HYPE tag sound)

MacOS 9 (88.80КиБ) Пятнадцать лет назад, ковыряя исходники браузера «Нетскейп», я наткнулся, в частности, на тег HYPE — в интернете того времени писали, что он воспроизводит какой-то звук, но только в старых версиях браузера и не под «Виндоуз». «Мака» у меня тогда не было, как и доступа к «Линуксу» с графическим интерфейсом, поэтому безуспешно поискав по интернету подробности, я исследование забросил.

Сегодня за завтраком, по другому случаю наткнувшись на свою же статью об этом теге, я подумав, что теперь-то в интернете найдётся всё, попробовал разыскать тот самый звук. По-прежнему безуспешно. Есть только несколько чудом сохранившихся страничек, где этот тег упоминается в контексе перечня «пасхальных яиц» «Нетскейпа» и это всё.

Но времена же изменились! Теперь у нас есть виртуализация, образы виртуальных машин и быстрый интернет! Где-то полчаса ушло на поиск и скачивание второй версии «Нетскейпа», готовой виртуалки с девятой «МакОСью», установку всего найденного, разбирательство как всё запускается. Но при вводе заветного тега меня ждало препятствие, к счастью, устранимое — виртуалка заявила, что у меня нехватает какой-то Sound Machine и, вместо проигрывания звука, предложила скачать звуковой файл.

Файл оказался в формате .snd, который нормально открылся и проигрался уже в современном «Маке». Его содержимое — фраза «What is global hypermedia?» (из документа NCSA Mosaic Demo 1993 года, как объясняется на одной из страниц в интернете). Я сконвертировал его в обычный МП3 и, если ваш браузер достаточно современный, можете послушать как звучит тот самый секретный тег:


Кстати, жаль, что я не догадался проиграть его на оригинальном «Макинтоше» в музее Эпл, в Москве. Если представиться случай, обязательно попробую!
2 комментария
28 октября 2015 10:08

Переподсоединялка вайфая

Переподсоединение (25.92КиБ) Вышло очередное обноление «Эль Капитана» на «Маке», а проблема с вайфаем на «Макбуках Про» так никуда и не делась. Я планирую в какие-нибудь выходные переставить систему с нуля, но когда это будет, неясно — пока я не могу себе позволить оторваться от ноута так надолго, для меня он важный рабочий инструмент.

В общем, сделал себе на «Баше» утилиту переподключающую вайфай при пропадании пингов до 8.8.8.8 (это ДНС «Гугла»). Сегодня уже целый день её использую, так гораздо комфортнее.
17 комментариев
23 октября 2015 15:29

Блокировка экрана на «Маке» из Си

Как-то я программировал на Си под «Мак» блокировку жестом перед датчиком освещения. Сегодня показывал его коллегам, оказалось ничего не работает — метод, который я использовал для вызова экрана блокировки перестал принимать на вход NULL. Тогда как в интернетах только этот способ везде и упоминается:
CGSCreateLoginSession(NULL);
// теперь выдаёт
// Assertion failed: (sid != NULL), function CGSCreateLoginSession, file Services/Connection/CGSSession.c, line 418.
Возможно в «Эль-Капитане» (последней версии «МакОСи») что-то поменялось — функция CGSCreateLoginSession считается недокументированной, никакой информации о ней мне найти не удалось.

В общем, покапался я в заголовочных файлах, подбором и интуицией родил код, который блокирует экран и под «Эль-Капитаном», заодно и утилиту свою поправил:
// получаем словарик с атрибутами текущей сессии
CFDictionaryRef dict = CGSCopyCurrentSessionDictionary();
// выбираем из него идентификатор текущей сессии, получаем его в виде int
CFNumberRef number = (CFNumberRef) CFDictionaryGetValue(dict, CFSTR("kCGSSessionIDKey"));
CGSSessionID currentSession;
CFNumberGetValue(number, kCFNumberIntType, &currentSession);
// блокируем текущую сессию
CGSCreateLoginSession(&currentSession);
Обновлённая версия лежит там же — на «Гитхабе».
3 комментария
21 октября 2015 18:23