38.2. Управление подключениями к базе данных #

В этом разделе описывается, как открывать, закрывать и переключать подключения к базам данных.

38.2.1. Подключение к серверу баз данных #

Подключение к базе данных выполняется следующим оператором:

EXEC SQL CONNECT TO цель-подключения [AS имя-подключения] [USER имя-пользователя];

Цель может задаваться следующими способами:

  • dbname[@имя_сервера][:порт]
  • tcp:postgresql://имя_сервера[:порт][/dbname][?параметры]
  • unix:postgresql://localhost[:порт][/dbname][?параметры]
  • строковая константа SQL, содержащая одну из вышеприведённых записей
  • ссылка на символьную переменную, содержащую одну из вышеприведённых записей (см. примеры)
  • DEFAULT

С целью подключения DEFAULT устанавливается подключение к базе данных по умолчанию с именем пользователя по умолчанию. Другое имя пользователя или имя подключения в этом случае указать нельзя.

Если цель подключения задаётся буквально (не в виде текстовой строки и не через переменную), её компоненты разбираются как обычные элементы SQL; это означает, например, что имя_сервера должно выглядеть как один или несколько идентификаторов SQL, разделённых точками, и если они не заключены в кавычки, они будут приведены к нижнему регистру. Значения любых параметров должны быть идентификаторами SQL, целыми числами или ссылками на переменные. Разумеется, практически любую строку можно сделать идентификатором SQL, заключив её в кавычки. На практике же, во избежание ошибок, предпочтительнее использовать текстовые строки (в апострофах) или ссылки на переменные.

Также разными способами можно указать имя пользователя:

  • имя_пользователя
  • имя_пользователя/пароль
  • имя_пользователя IDENTIFIED BY пароль
  • имя_пользователя USING пароль

В показанных выше строках имя_пользователя и пароль могут задаваться идентификатором или строковой константой SQL, либо ссылкой на символьную переменную.

Если указание цели подключения включает какие-либо параметры, они должны записываться в виде имя=значение и разделяться амперсандами (&). В качестве имён параметров принимаются те же, что поддерживает libpq (см. Подраздел 36.1.2). Перед элементами имя или значение пробелы игнорируются, но сохраняются внутри или после этих элементов. Заметьте, что записать & внутри значения нет никакой возможности.

Обратите внимание, что при указании соединения через сокет (с префиксом unix:) имя сервера обязательно должно быть localhost. Чтобы выбрать нестандартный каталог сокета, укажите путь к каталогу как значение параметра host в списке параметров в строке цели.

Указание имя-подключения применяется, когда в одной программе нужно использовать несколько подключений. Его можно опустить, если программа работает только с одним подключением. Соединение, открытое последним, становится текущим и будет использоваться по умолчанию при выполнении SQL-операторов (это описывается далее в этой главе).

Вот некоторые примеры оператора CONNECT:

EXEC SQL CONNECT TO mydb@sql.mydomain.com;

EXEC SQL CONNECT TO tcp:postgresql://sql.mydomain.com/mydb AS myconnection USER john;

EXEC SQL BEGIN DECLARE SECTION;
const char *target = "mydb@sql.mydomain.com";
const char *user = "john";
const char *passwd = "secret";
EXEC SQL END DECLARE SECTION;
 ...
EXEC SQL CONNECT TO :target USER :user USING :passwd;
/* or EXEC SQL CONNECT TO :target USER :user/:passwd; */

В последней форме используется вариант, названный выше ссылкой на символьную переменную. В последующих разделах вы узнаете, как в SQL-операторах можно использовать переменные C, приставляя перед именем двоеточие.

Учтите, что формат цели подключения не описывается в стандарте SQL. Поэтому, если вы хотите разрабатывать переносимые приложения, имеет смысл применить подход, показанный в последнем примере, и сформировать строку подключения отдельно.

Если к базе данных, которая не приведена в соответствие шаблону безопасного использования схем, имеют доступ недоверенные пользователи, начинайте сеанс с удаления схем, доступных всем для записи, из пути поиска (search_path). Например, добавьте options=-c search_path= в options или выполните EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); после подключения. Это касается не только ECPG, но и любых других интерфейсов для выполнения произвольных SQL-команд.

38.2.2. Выбор подключения #

SQL-операторы в программах со встраиваемым SQL по умолчанию выполняются с текущим подключением, то есть с подключением, которое было открыто последним. Если приложению нужно управлять несколькими подключениями, это можно сделать тремя способами.

Первый вариант — явно выбирать подключение для каждого SQL-оператора, например, так:

EXEC SQL AT имя-подключения SELECT ...;

Этот вариант хорошо подходит для случаев, когда приложению нужно использовать несколько подключений в смешанном порядке.

Если ваше приложение выполняется в нескольких потоках, они не могут использовать подключение одновременно. Поэтому вы должны либо явно управлять доступом (используя мьютексы), либо использовать отдельные подключения для каждого потока.

Второй вариант — выполнять оператор, переключающий текущее подключение. Этот оператор записывается так:

EXEC SQL SET CONNECTION имя-подключения;

Этот вариант особенно удобен, когда с одним подключением нужно выполнить несколько операторов.

Следующий пример программы демонстрирует управление несколькими подключениями к базам данных:

#include <stdio.h>

EXEC SQL BEGIN DECLARE SECTION;
    char dbname[1024];
EXEC SQL END DECLARE SECTION;

int
main()
{
    EXEC SQL CONNECT TO testdb1 AS con1 USER testuser;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
    EXEC SQL CONNECT TO testdb2 AS con2 USER testuser;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
    EXEC SQL CONNECT TO testdb3 AS con3 USER testuser;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    /* This query would be executed in the last opened database "testdb3". */
    EXEC SQL SELECT current_database() INTO :dbname;
    printf("current=%s (should be testdb3)\n", dbname);

    /* Using "AT" to run a query in "testdb2" */
    EXEC SQL AT con2 SELECT current_database() INTO :dbname;
    printf("current=%s (should be testdb2)\n", dbname);

    /* Switch the current connection to "testdb1". */
    EXEC SQL SET CONNECTION con1;

    EXEC SQL SELECT current_database() INTO :dbname;
    printf("current=%s (should be testdb1)\n", dbname);

    EXEC SQL DISCONNECT ALL;
    return 0;
}

Этот пример должен вывести следующее:

current=testdb3 (should be testdb3)
current=testdb2 (should be testdb2)
current=testdb1 (should be testdb1)

Третий вариант заключается в объявлении идентификатора SQL, связанного с подключением. Например:

EXEC SQL AT имя-подключения DECLARE имя-оператора STATEMENT;
EXEC SQL PREPARE имя-оператора FROM :динамическая-строка;

Связанный с подключением SQL-идентификатор затем нужно выполнять без предложения AT. Обратите внимание, что этот вариант действует как директивы препроцессора, поэтому это связывание будет работать только в рамках файла.

Следующий пример программы демонстрирует использование данного варианта:

#include <stdio.h>

EXEC SQL BEGIN DECLARE SECTION;
char dbname[128];
char *dyn_sql = "SELECT current_database()";
EXEC SQL END DECLARE SECTION;

int main(){
  EXEC SQL CONNECT TO postgres AS con1;
  EXEC SQL CONNECT TO testdb AS con2;
  EXEC SQL AT con1 DECLARE stmt STATEMENT;
  EXEC SQL PREPARE stmt FROM :dyn_sql;
  EXEC SQL EXECUTE stmt INTO :dbname;
  printf("%s\n", dbname);

  EXEC SQL DISCONNECT ALL;
  return 0;
}

Этот пример должен вывести следующее, даже если подключение по умолчанию установлено к базе testdb:

postgres

38.2.3. Закрытие подключения #

Чтобы закрыть подключение, примените следующий оператор:

EXEC SQL DISCONNECT [подключение];

Подключение можно задать следующими способами:

  • имя-подключения
  • CURRENT
  • ALL

Если имя подключения не задано, закрывается текущее подключение.

Хорошим стилем считается, когда приложение явно закрывает каждое подключение, которое оно открыло.