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

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

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

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

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

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

EXEC SQL CONNECT TO target [AS connection-name] [USER user-name];

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

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

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

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

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

  • username
  • username/password
  • username IDENTIFIED BY password
  • username USING password

Как указано выше, параметры username и password могут быть идентификатором SQL, строковым литералом SQL или ссылкой на символьную переменную.

Если цель подключения включает какие-либо options, они состоят из спецификаций keyword=value, разделенных амперсандами (&). Разрешенные ключевые слова такие же, как и те, которые распознает libpq (см. Раздел 32.1.2). Пробелы игнорируются перед любым keyword или value, хотя не внутри или после них. Обратите внимание, что нет способа записать & внутри value.

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

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

Вот несколько примеров команд CONNECT:

EXEC SQL CONNECT TO [email protected];

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

EXEC SQL BEGIN DECLARE SECTION;
const char *target = "[email protected]";
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; */

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

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

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

34.2.2. Выбор соединения

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

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

EXEC SQL AT connection-name SELECT ...;

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

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

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

EXEC SQL SET CONNECTION connection-name;

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

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

#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 connection-name DECLARE statement-name STATEMENT;
EXEC SQL PREPARE statement-name FROM :dyn-string;

После того, как вы связали идентификатор SQL с соединением, вы можете выполнять динамический 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

34.2.3. Закрытие соединения

Для закрытия соединения используйте следующий оператор:

EXEC SQL DISCONNECT [connection];

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

  • connection-name
  • CURRENT
  • ALL

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

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