Приложение на Node.js
На этой странице представлено подробное описание кода тестового приложения,
доступого в составе YDB Node.js SDK YDB.
Инициализация соединения с базой данных
Для взаимодействия с YDB создается экземпляр драйвера, клиента и сессии:
- Драйвер YDB отвечает за взаимодействие приложения и YDB на транспортном уровне. Драйвер должен существовать на всем протяжении жизненного цикла работы с YDB и должен быть инициализирован перед созданием клиента и сессии.
- Клиент YDB работает поверх драйвера YDB и отвечает за работу с сущностями и транзакциями.
- Сессия YDB содержит информацию о выполняемых транзакциях и подготовленных запросах и содержится в контексте клиента YDB.
Фрагмент кода приложения для инициализации драйвера:
const authService = getCredentialsFromEnv();
logger.debug('Driver initializing...');
const driver = new Driver({connectionString, authService});
const timeout = 10000;
if (!await driver.ready(timeout)) {
logger.fatal(`Driver did not become ready within ${timeout}ms!`);
process.exit(1);
}
const authService = getCredentialsFromEnv();
logger.debug('Driver initializing...');
const driver = new Driver({endpoint, database, authService});
const timeout = 10000;
if (!await driver.ready(timeout)) {
logger.fatal(`Driver did not become ready within ${timeout}ms!`);
process.exit(1);
}
Фрагмент кода приложения для создания сессии:
const result = await driver.queryClient.do({
...
fn: async (session) => {
...
}
});
Создание строковых таблиц
Выполняется создание строковых таблиц, которые используются в дальнейших операциях тестового приложения. В результате исполнения шага в базе данных будут созданы строковые таблицы модели данных справочника сериалов:
series
- Сериалыseasons
- Сезоныepisodes
- Эпизоды
После создания вызывается метод получения информации об объекте схемы данных, и выводится результат его выполнения.
async function createTables(driver: Driver, logger: Logger) {
logger.info('Dropping old tables and creating new ones...');
await driver.queryClient.do({
fn: async (session) => {
try {
await session.execute({
text: `
DROP TABLE ${SERIES_TABLE};
DROP TABLE ${EPISODES_TABLE};
DROP TABLE ${SEASONS_TABLE};`,
});
} catch (err) { // Ignore if tables are missing
if (err instanceof SchemeError) throw err;
}
await session.execute({
text: `
CREATE TABLE ${SERIES_TABLE}
(
series_id UInt64,
title Utf8,
series_info Utf8,
release_date DATE,
PRIMARY KEY (series_id)
);
CREATE TABLE ${SEASONS_TABLE}
(
series_id UInt64,
season_id UInt64,
title UTF8,
first_aired DATE,
last_aired DATE,
PRIMARY KEY (series_id, season_id)
);
CREATE TABLE ${EPISODES_TABLE}
(
series_id UInt64,
season_id UInt64,
episode_id UInt64,
title UTf8,
air_date DATE,
PRIMARY KEY (series_id, season_id, episode_id),
INDEX episodes_index GLOBAL ASYNC ON (air_date)
);`,
});
},
});
}
Запись данных
Выполняется запись данных в созданные строковые таблицы с использованием команды UPSERT
языка запросов YQL. Применяется режим передачи запроса на изменение данных с автоматическим подтверждением транзакции в одном запросе к серверу.
Фрагмент кода, демонстрирующий выполнение запроса на запись/изменение данных:
async function upsertSimple(driver: Driver, logger: Logger): Promise<void> {
logger.info('Making an upsert...');
await driver.queryClient.do({
fn: async (session) => {
await session.execute({
text: `
UPSERT INTO ${EPISODES_TABLE} (series_id, season_id, episode_id, title)
VALUES (2, 6, 1, "TBD");`,
})
}
});
logger.info('Upsert completed.')
}
Получение выборки данных
Выполняется запрос на получение выборки данных с использованием команды SELECT
языка запросов YQL. Демонстрируется обработка полученной выборки в приложении.
Для выполнения YQL-запросов используется метод QuerySession.execute()
.
В зависимости оп параметра rowMode
данные можно получить в JavaScript форме или как YDB структуры.
async function selectNativeSimple(driver: Driver, logger: Logger): Promise<void> {
logger.info('Making a simple native select...');
const result = await driver.queryClient.do({
fn: async (session) => {
const {resultSets} =
await session.execute({
// rowMode: RowType.Native, // Result set columns and rows are returned as native JavaScript values. This is the default behavior.
text: `
SELECT series_id,
title,
release_date
FROM ${SERIES_TABLE}
WHERE series_id = 1;`,
});
const {value: resultSet1} = await resultSets.next();
const rows: any[][] = []
for await (const row of resultSet1.rows) rows.push(row);
return {cols: resultSet1.columns, rows};
}
});
logger.info(`selectNativeSimple cols: ${JSON.stringify(result.cols, null, 2)}`);
logger.info(`selectNativeSimple rows: ${JSON.stringify(result.rows, null, 2)}`);
}
async function selectTypedSimple(driver: Driver, logger: Logger): Promise<void> {
logger.info('Making a simple typed select...');
const result = await driver.queryClient.do({
fn: async (session) => {
const {resultSets} =
await session.execute({
rowMode: RowType.Ydb, // enables typedRows() on result sets
text: `
SELECT series_id,
title,
release_date
FROM ${SERIES_TABLE}
WHERE series_id = 1;`,
});
const {value: resultSet1} = await resultSets.next();
const rows: Series[] = [];
// Note: resultSet1.rows will iterate YDB IValue structures
for await (const row of resultSet1.typedRows(Series)) rows.push(row);
return {cols: resultSet1.columns, rows};
}
});
logger.info(`selectTypedSimple cols: ${JSON.stringify(result.cols, null, 2)}`);
logger.info(`selectTypedSimple rows: ${JSON.stringify(result.rows, null, 2)}`);
}
Параметризованные запросы
Выполняется запрос к данным с использованием параметров. Этот вариант выполнения запросов является предпочтительным, так как позволяет серверу переиспользовать план исполнения запроса при последующих его вызовах, а также спасает от уязвимостей вида SQL Injection.
async function selectWithParameters(driver: Driver, data: ThreeIds[], logger: Logger): Promise<void> {
await driver.queryClient.do({
fn: async (session) => {
for (const [seriesId, seasonId, episodeId] of data) {
const episode = new Episode({seriesId, seasonId, episodeId, title: '', airDate: new Date()});
const {resultSets, opFinished} = await session.execute({
parameters: {
'$seriesId': episode.getTypedValue('seriesId'),
'$seasonId': episode.getTypedValue('seasonId'),
'$episodeId': episode.getTypedValue('episodeId')
},
text: `
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
DECLARE $episodeId AS Uint64;
SELECT title,
air_date
FROM episodes
WHERE series_id = $seriesId
AND season_id = $seasonId
AND episode_id = $episodeId;`
});
const {value: resultSet} = await resultSets.next();
const {value: row} = await resultSet.rows.next();
await opFinished;
logger.info(`Parametrized select query ${JSON.stringify(row, null, 2)}`);
}
}
});
}
Скан запросы
Выполняется скан запрос данных, результатом исполнения которого является стрим. Стрим позволяет считать неограниченное количество строк и объем данных.
Для получения данных потоком используется метод QuerySession.execute()
.
async function selectWithParametrs(driver: Driver, data: ThreeIds[], logger: Logger): Promise<void> {
logger.info('Selecting prepared query...');
await driver.queryClient.do({
fn: async (session) => {
for (const [seriesId, seasonId, episodeId] of data) {
const episode = new Episode({seriesId, seasonId, episodeId, title: '', airDate: new Date()});
const {resultSets, opFinished} = await session.execute({
parameters: {
'$seriesId': episode.getTypedValue('seriesId'),
'$seasonId': episode.getTypedValue('seasonId'),
'$episodeId': episode.getTypedValue('episodeId')
},
text: `
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
DECLARE $episodeId AS Uint64;
SELECT title,
air_date
FROM episodes
WHERE series_id = $seriesId
AND season_id = $seasonId
AND episode_id = $episodeId;`
});
const {value: resultSet} = await resultSets.next();
const {value: row} = await resultSet.rows.next();
await opFinished;
logger.info(`Parametrized select query ${JSON.stringify(row, null, 2)}`);
}
}
});
}
Управление транзакциями
Выполняются вызовы операторов управления транзакциями TCL - Begin и Commit.
В большинстве случаев вместо явного использования вызовов Begin и Commit лучше использовать параметры контроля транзакций в вызовах execute. Это позволит избежать лишних обращений к YDB и эффективней выполнять запросы.
Фрагмент кода, демонстрирующий явное использование вызовов Session.beginTransaction()
и Session.сommitTransaction()
для выполнения транзакции:
async function explicitTcl(driver: Driver, ids: ThreeIds, logger: Logger) {
logger.info('Running prepared query with explicit transaction control...');
await driver.queryClient.do({
fn: async (session) => {
await session.beginTransaction({serializableReadWrite: {}});
const [seriesId, seasonId, episodeId] = ids;
const episode = new Episode({seriesId, seasonId, episodeId, title: '', airDate: new Date()});
await session.execute({
parameters: {
'$seriesId': episode.getTypedValue('seriesId'),
'$seasonId': episode.getTypedValue('seasonId'),
'$episodeId': episode.getTypedValue('episodeId')
},
text: `
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
DECLARE $episodeId AS Uint64;
UPDATE episodes
SET air_date = CurrentUtcDate()
WHERE series_id = $seriesId
AND season_id = $seasonId
AND episode_id = $episodeId;`
})
const txId = session.txId;
await session.commitTransaction();
logger.info(`TxId ${txId} committed.`);
}
});
}
async function transactionPerWholeDo(driver: Driver, ids: ThreeIds, logger: Logger) {
logger.info('Running query with one transaction per whole doTx()...');
await driver.queryClient.doTx({
txSettings: {serializableReadWrite: {}},
fn: async (session) => {
const [seriesId, seasonId, episodeId] = ids;
const episode = new Episode({seriesId, seasonId, episodeId, title: '', airDate: new Date()});
await session.execute({
parameters: {
'$seriesId': episode.getTypedValue('seriesId'),
'$seasonId': episode.getTypedValue('seasonId'),
'$episodeId': episode.getTypedValue('episodeId')
},
text: `
DECLARE $seriesId AS Uint64;
DECLARE $seasonId AS Uint64;
DECLARE $episodeId AS Uint64;
UPDATE episodes
SET air_date = CurrentUtcDate()
WHERE series_id = $seriesId
AND season_id = $seasonId
AND episode_id = $episodeId;`
})
logger.info(`TxId ${session.txId} will be committed by doTx().`);
}
});
}
Обработка ошибок
Подробно об обработке ошибок написано в разделе Обработка ошибок в API.