1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/jovercao-lubejs

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Lubejs

Lubejs — это библиотека для node.js, предназначенная для удобной работы с соединением SQL-баз данных. Название lube происходит от английского слова «lubricant», что означает «смазка». Таким образом, Lubejs выступает в роли смазки между JavaScript и SQL, позволяющей использовать элегантный JavaScript/TypeScript вместо конкатенации строк SQL.

Настоящая библиотека частично вдохновлена EF и TypeORM, за что мы благодарны.

Английская версия

Что такое Lubejs

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

  • Поддержка мощных типов TypeScript, включая обратное вывод типов, что гарантирует четкие типы выходных данных и полную систему типовой безопасности, а также улучшенные возможности автодополнения, повышающие производительность разработки и снижающие риск ошибок типа. Мы настоятельно рекомендуем использовать Lubejs в проектах на TypeScript.

  • Инструменты ORM, такие как Code First и управление миграциями данных.

  • Совместимость с несколькими базами данных (в настоящее время поддерживаются только MSSQL).

  • Кросс-платформенная совместимость, для которой Lubejs предлагает стандартную библиотеку поведений, которая объединяет большинство часто используемых операций, отличающихся в различных базах данных.## Концепция Lubejs

  • Минимальизм, простой API, легкий вход.

  • Природность, синтаксис максимально приближен к стандартному SQL, что значительно снижает затраты времени на обучение.

  • Пошаговое внедрение, Lubejs состоит из двух уровней использования: core и полный набор функций.

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

  • Полная система типовой безопасности на TypeScript

Быстрый старт

Установка

Установите библиотеку с помощью npm:

# Установка библиотеки Lubejs
npm install lubejs --save

# Установка драйвера Lubejs-MSSQL
npm install lubejs-mssql

Начало работы

Пример Hello World!

// hello-world.ts
import { connect, SQL } from 'lubejs';
// импорт драйвера mssql
import 'lubejs-mssql';

(async () => {
  // Создание соединения
  const db = await connect('mssql://user:password@localhost:1433/database');
  // SELECT 'hello world'
  console.log(await db.queryScalar(SQL.select('hello world!')));  // => 'hello world'

  await db.close();
})();

Полный пример

// example.ts
import {
  connect,
  SQL,
  Decimal,
  Uuid,
  Connection,
  DbType,
  outputCommand,
} from "lubejs";
import "lubejs-mssql";

interface Table1 {
  id: number;
  name: string;
  stringField?: string;
  floatField?: number;
  dateField?: Date;
  decimalField?: Decimal;
  uuidField?: Uuid;
  updatedAt: Date;
  binaryField?: ArrayBuffer;
  createdAt: Date;
  operator?: string;
}
interface Pay {
  id?: number;
  year: number;
  month: number;
  amount: Decimal;
  personId: number;
}

interface Person {
  id?: number;
  name: string;
  age: number;
}

/**
 * Инициализация базы данных
 */
async function initDb(db: Connection) {
  await db.query(
    SQL.if(SQL.existsTable('table1')).then(SQL.dropTable('table1'))
  );
}
```  await db.query(
    SQL.createTable("table1").as(({ column }) => [
      column("id", DbType.int32).identity().primaryKey(),
      column("name", DbType.string(100)).not_null(),
      column("string_field", DbType.string(100)).nullable(),
      column("float_field", DbType.float).nullable(),
      column("date_field", DbType.datetimeoffset).nullable(),
      column("decimal_field", DbType.decimal(18, 6)),
      column("uuid_field", DbType.uuid),
      column("updated_at", DbType.datetimeoffset).default(SQL.now()),
      column("binary_field", DbType.binary(DbType.MAX)),
      column("created_at", DbType.datetimeoffset).default(SQL.now()),
      column("operator", DbType.string(100)).nullable(),
    ])
  );

  await db.query(
    SQL.if(SQL.exists_table('pay')).then(SQL.drop_table("pay"))
  );

  await db.query(
    SQL.createTable("pay").as(({ column }) => [
      column("id", DbType.int32).identity().primary_key(),
      column("year", DbType.int32),
      column("month", DbType.int32),
      column("amount", DbType.decimal(18, 2)),
      column("person_id", DbType.int32),
    ])
  );

  await db.query(
    SQL.if(SQL.exists_table('person')).then(SQL.drop_table("person"))
  );

  await db.query(
    SQL.createTable("person").as(({ column }) => [
      column("id", DbType.int32).identity().primary_key(),
      column("name", DbType.int32).not_null(), // Ошибка в типе данных, должно быть DbType.string(100)
      column("age", DbType.int32),
    ])
  );
}```markdown
/**
 * Декларация таблицы Table1
 */
// Это пример
async function example(db: Connection) {
  //---------------Вставка данных------------------
  /*
   * INSERT INTO table1 (stringField, floatField, dateField)
   * VALUES ('value1-1', 2, CONVERT(DATETIMEOFFSET, '2019-11-18 00:00:00'))
   * , ('value1-2', 1, CONVERT(DATETIMEOFFSET, '2019-11-18 00:00:00'))
   * , ('value1-3', 45, CONVERT(DATETIMEOFFSET, '2019-11-18 00:00:00'))
   */
  const insertSql = SQL.insert<Table1>("table1").values([
    {
      name: "item1",
      stringField: "value1-1",
      floatField: 3.14,
      dateField: new Date(),
      decimalField: new Decimal("3.1415"),
      uuidField: Uuid.new(),
      binaryField: Buffer.from('abcdefeg')
    },
    {
      name: "item2",
      stringField: "value1-2",
      floatField: 1.132,
      dateField: new Date(),
      decimalField: new Decimal("3.1415"),
      uuidField: Uuid.new(),
      binaryField: Buffer.from('abcdefeg')
    },
    {
      name: "item3",
      stringField: "value1-3",
      floatField: 45.2656,
      dateField: new Date(),
      decimalField: new Decimal("3.1415"),
      uuidField: Uuid.new(),
      binaryField: Buffer.from('abcdefeg')
    },
  ]);
}

await db.query(insertSql);
```// Также можно использовать следующий способ вставки, эквивалентный вышеописанному методу
await db.insert<Table1>("table1", [
  {
    name: "item1",
    stringField: "значение1-1",
    floatField: 3.14,
    dateField: new Date(),
    decimalField: new Decimal("3.1415"),
    uuidField: Uuid.new(),
    binaryField: Buffer.from('abcdefeg')
  },
  {
    name: "item2",
    stringField: "значение1-2",
    floatField: 1.132,
    dateField: new Date(),
    decimalField: new Decimal("3.1415"),
    uuidField: Uuid.new(),
    binaryField: Buffer.from('abcdefeg')
  },
  {
    name: "item3",
    stringField: "значение1-3",
    floatField: 45.2656,
    dateField: new Date(),
    decimalField: new Decimal("3.1415"),
    uuidField: Uuid.new(),
    binaryField: Buffer.from('abcdefeg')
  },
]);

//----------------Обновление данных------------------
// UPDATE t SET updatedAt = CONVERT(DATETIME, '2019-11-18 00:00:00') FROM table1 t WHERE id = 1
const t = SQL.table<Table1>("table1").as("t");
const updateSql = SQL.update(t)
  .set({ updatedAt: new Date(), оператор: "ваше имя" })
  .where(t.id.eq(1));
await db.query(updateSql);

// Также можно использовать следующий способ обновления, эквивалентный вышеописанному методу
await db.update<Table1>(
  "table1",
  { updatedAt: new Date(), оператор: "ваше имя" },
  { id: 1 }
);

//----------------Удаление данных-------------------
// DELETE t FROM table1 t WHERE t.id = 1
const deleteSql = SQL.delete(t).from(t).where(t.id.eq(1));
await db.query(deleteSql);

// Также можно использовать следующий способ удаления
// DELETE table1 WHERE id = 1
await db.delete("table1", { id: 1 });

//----------------Запрос данных----------------------
// SELECT t.* FROM table1 AS t WHERE t.id = 1 AND t.name = 'name1'
const selectSql = SQL.select(t.star)
  .from(t)
  .where(SQL.and(t.id.eq(1), t.name.eq("name1")));
console.log((await db.query(selectSql)).rows);```md
// Вы также можете выполнить запрос таким образом
// SELECT * FROM table1 WHERE id = 1 AND name = 'name1'
console.log(await db.select("table1", {
  where: {
    id: 1,
    name: "item1",
  },
}));
// ---------------Ниже приведен составной запрос--------------
const p = SQL.table<Person>("person").as("p");
const pay = SQL.table<Pay>("pay");
const sql = SQL.select({
      год: pay.год,
      месяц: pay.месяц,
      имя: p.имя,
      возраст: p.возраст,
      общая_сумма: SQL.sum(pay.сумма),
})
  .from(pay)
  .join(p, pay.personId.eq(p.id))
  .where(p.возраст.lte(18))
  .groupBy(p.имя, p.возраст, pay.год, pay.месяц)
  .having(SQL.sum(pay.сумма).gte(new Decimal(100000)))
  .orderBy(pay.год.asc(), pay.месяц.asc(), SQL.sum(pay.сумма).asc(), p.возраст.asc())
  .offset(20)
  .limit(50);

Введение```ts

console.log((await db.query(sql)).rows);



```ts
(async () => {
  // Создание соединения Lube
  const db = await connect("mssql://sa:!crgd-2021@rancher.vm/Test");
  // Открытие соединения
  await db.open();
  // Вывод логов
  db.on('command', (cmd) => outputCommand(cmd, process.stdout));
  try {
    await initDb(db);
    await example(db);
  } finally {
    await db.close();
  }
})();

Версия

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

Пошаговое разделение

  • lubejs/core — это базовый пакет, который включает средства построения SQL и инструменты выполнения SQL.
  • lubejs — полный пакет, который включает все содержимое lubejs/core, а также ORM-функциональность, командную строку для миграций данных и т.д.

Поддерживаемые базы данных

  • mssql — поддерживает Microsoft SQL Server 2012 или более поздние версии, библиотека основана на node-mssql.
  • mysql — разработка в процессе.
  • postgresql — планируется к разработке до конца 2021 года.

Поддержка версий Node.js

Node.js >= 12.0

Концепция

Построитель SQL

Все SQL-запросы могут быть созданы с помощью построителя запросов. lubejs экспортирует глобальный объект-построитель SQL. Почти все SQL-запросы можно создать с использованием этого объекта, например SQL.select, SQL.update, SQL.delete.

// Импорт объекта SQL
import { SQL } from 'lubejs';
```Для большего удобства вы можете использовать деконструкцию для импорта необходимых ключевых слов SQL:

```ts
const {
    insert,
    delete: $delete // "delete" является зарезервированным словом в JavaScript, поэтому используется псевдоним "$delete"
} = SQL;

// Создание SQL-запроса для вставки двух записей в таблицу table1
const sql = insert('table1').values([{ name: '张三', age: 19, sex: '' }, { name: '李四', age: 25, sex: '' }]);

// Создание SQL-запроса для удаления записи с ID 1 из таблицы table1
const sql = $delete('table1').where({ id: 1 });

Дополнительные примеры использования объекта SQL представлены в руководстве по API.

Обратите внимание: "delete" является зарезервированным словом в JavaScript, поэтому требуется использование псевдонима.

Стандартные функции

Чтобы обеспечить лучшую совместимость с различными базами данных, lubejs определяет стандартные функции, которые используются для унификации действий при работе с несколькими базами данных. Эти функции находятся внутри объекта-построителя SQL.

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

| ---------------------- | ------------------------------------------------------------- | ---------- |
| Преобразование типов   | `SQL.convert(expr, dbType)`, `Expression.prototype.to(dbType)`<br>**Пример:**<br>`SQL.convert('abc', DbType.string(100))`<br>`SQL.literal('100').to(DbType.int32)` |          |
| Возврат значения по умолчанию при пустоте | `SQL.nvl(value, defaultValue)`                                |          | 
```**Агрегирующие функции**

| Описание      | Функция           | Примечание |
| ------------- | ------------------ | ---------- |
| Подсчет       | `SQL.count(expr)`  |          |
| Среднее значение | `SQL.avg(expr)`    |          |
| Сумма         | `SQL.sum(expr)`    |          |
| Максимум      | `SQL.max(expr)`    |          |
| Минимум       | `SQL.min(expr)`    |          |**Функции работы с датами**| Описание                   | Функция                                   | Примечание |
| -------------------------- | ----------------------------------------- | ---------- |
| Текущее время              | `SQL.now(expr)`                          |           |
| Время UTC                  | `SQL.utcNow(expr)`                       |           |
| Переключение часового пояса | `SQL.switchTimezone(date, offset)`       |           |
| Форматирование даты        | `SQL.formatDate(date, format)`           |           |
| Получение года из даты     | `SQL.yearOf(date)`                        |           |
| Получение месяца из даты   | `SQL.monthOf(date)`                       |           |
| Получение дня из даты      | `SQL.dayOf(date)`                         |           |
| Разница в днях между двумя датами | `SQL.daysBetween(start, end)`       |           |
| Разница в месяцах между двумя датами | `SQL.monthsBetween(start, end)`    |           |
| Разница в годах между двумя датами | `SQL.yearsBetween(start, end)`      |           |
| Разница в часах между двумя датами | `SQL.hoursBetween(start, end)`      |           |
| Разница в минутах между двумя датами | `SQL.minutesBetween(start, end)`   |           |
| Разница в секундах между двумя датами | `SQL.secondsBetween(start, end)`  |           |
| Дата после добавления дней  | `SQL.addDays(date, days)`               |           |
| Дата после добавления месяцев | `SQL.addMonths(date, months)`          |           |
| Дата после добавления лет   | `SQL.addYears(date, years)`             |           |
| Дата после добавления часов  | `SQL.addHours(date, hours)`             |           |
| Дата после добавления минут  | `SQL.addMinutes(date, minutes)`         |           |
| Дата после добавления секунд | `SQL.addSeconds(date, seconds)`         |           |

**Строковые функции**| Описание                             | Функция                                  | Примечание |
| ------------------------------------ | ----------------------------------------- | ---------- |
| Получение количества символов строки | `SQL.strlen(str)`                        |            |
| Получение размера строки в байтах     | `SQL.strsize(str)`                       |            |
| Вырезка строки                       | `SQL.substr(str, start, len)`           |            |
| Замена строки                        | `SQL.replace(str, search, text)`        |            |
| Удаление пробелов с обоих концов      | `SQL.trim(str)`                          |            |
| Удаление пробелов справа             | `SQL.trimEnd(str)`                       |            |
| Преобразование в нижний регистр       | `SQL.lower(str)`                         |            |
| Преобразование в верхний регистр       | `SQL.upper(str)`                         |            |
| Получение позиции строки внутри другой строки | `SQL.strpos(str, search, startAt)` |            |
| Получение ASCII-кода символа         | `SQL.ascii(str)`                         |            |
| Преобразование ASCII-кода в символ    | `SQL.asciiChar(code)`                    |            |
| Получение Unicode-кода символа       | `SQL.unicode(str)`                       |            |
| Преобразование Unicode-кода в символ  | `SQL.codecChar(code)`                    |            |

**Математические функции**| Описание | Функция | Примечание |
| --------- | ------- | ----------- |
| Абсолютное значение | `SQL.abs(value)` |           |
| Экспонента | `SQL.exp(value)` |           |
| Вверх до целого числа | `SQL.ceil(value)` |           |
| Вниз до целого числа | `SQL.floor(value)` |           |
| Натуральный логарифм | `SQL.ln(value)` |           |
| Логарифм | `SQL.log(value)` |           |
| Число π | `SQL.pi()` |           |
| Возведение в степень | `SQL.power(value, exponent)` |           |
| Перевод градусов в радианы | `SQL.radians(value)` |           |
| Перевод радианов в градусы | `SQL.degrees(value)` |           |
| Генерация случайного числа | `SQL.random(value)` |           |
| Округление до указанной цифры | `SQL.round(value)` |           |
| Функция знака | `SQL.sign(value)` |           |
| Квадратный корень | `SQL.sqrt(value)` |           |
| Косинус | `SQL.cos(value)` |           |
| Синус | `SQL.sin(value)` |           |
| Тангенс | `SQL.tan(value)` |           |
| Арккосинус | `SQL.acos(value)` |           |
| Арксинус | `SQL.asin(value)` |           |
| Арктангенс | `SQL.atan(value)` |           |
| Котангенс | `SQL.cot(value)` |           |**Основные операции**|| Описание                       | Метод                                                                 | Примечание |
| ------------------------------ | -------------------------------------------------------------------- | ---------- |
| Проверка наличия таблицы        | `SQL.existsTable(tableName)`                                         |            |
| Проверка отсутствия базы данных | `SQL.existsDatabase(dbName)`                                         |            |
| Проверка отсутствия представления | `SQL.existsView(viewName)`                                          |            |
| Проверка отсутствия функции     | `SQL.existsFunction(functionName)`                                   |            |
| Проверка отсутствия хранимой процедуры | `SQL.existsProcedure(procedureName)`                           |            |
| Проверка отсутствия последовательности | `SQL.existsSequence(sequenceName)`                              |            |
| Получение текущей базы данных   | `SQL.currentDatabase()`                                             |            |
| Получение текущей схемы по умолчанию | `SQL.defaultSchema()`                                            |            |
| Получение следующего значения последовательности | `SQL.sequenceNextValue(sequenceName)`                    |            |

### Типы базы данных (DbType)

Для обеспечения совместимости между различными базами данных lubejs определяет промежуточные типы данных DbType. В большинстве случаев рекомендуется не использовать нативные типы данных конкретной базы данных.

#### Использование DbType

```ts
import { DbType, SQL } from 'lubejs';

// Преобразование литерала 1 в boolean
const sql = SQL.select(SQL.literal(1).to(DbType.boolean));
// => SELECT CAST(1 AS bit) AS [#column_1]
```Нативные типы

Использование нативных типов данных базы данных для создания SQL запросов:

```ts
const sql = SQL.createTable('Person').as(builder => {
    builder.column('name', DbType.raw('text'));
});
```#### Таблица соответствия типов DbType| Тип                             | Соответствует JS типу             | Соответствует SQL Server типу | Описание                |
 | -------------------------------- | ---------------------------------- | ------------------------------ | ----------------------- |
 | DbType.int8                      | Number                            | tinyint                       |                       |
 | DbType.int16                     | Number                            | smallint                      |                       |
 | DbType.int32                     | Number                            | int                           |                       |
 | DbType.int64                     | BigInt (ES2019)                  | bigint                        |                       |
 | DbType.decimal(precision, digit) | Decimal (из библиотеки `decimal.js`) | decimal(precision, digit)    |                       |
 | DbType.float32                   | Number                            | float                        |                       |
 | DbType.float64                   | Number                            | real                         |                       |
 | DbType.string(length)            | String                            | nvarchar(length)            |                       |
 | DbType.date                      | Date                              | date                         |                       |
 | DbType.datetime                  | Date                              | datetime                     |                       |
 | DbType.datetimeoffset             | Date                              | datetimeoffset               |                       |
 | DbType.time                      | Time (встроен в lubejs)          | time                         | Дата                  |
 | DbType.binary                    | ArrayBuffer/Buffer                | varbinary(x)                |                       |
 | DbType.boolean                   | Boolean                           | bit                          |                       |
 | DbType.uuid                      | Uuid (встроен в lubejs, основан на библиотеке `uuid`) | UNIQUEIDENTIFIER     |                       |rowflag                   | ArrayBuffer                        | TIMESTAMP                      |                       | json                     | Объект или `custom type`            | nvarchar(max)             | Сохраняется в MSSQL как JSON-строка |
  | DbType.list(dbType)                 | Array&lt;dbType&gt;                | nvarchar(max)              | Сохраняется в MSSQL как JSON-строка |### Набор строк (Rowset)|Все объекты, доступные через `SELECT FROM`, могут называться наборами строк. В частности:

- Таблицы/представления
- Алиасы подзапросов SELECT
- Элементы запроса с использованием WITH
- Возвращаемое значение таблицной функции
- Таблиценные переменные


**Объявление таблиц/представлений**

```ts
const personTable = SQL.table<Person>('Person');
const houseTable = SQL.table<House>('House')

Добавление алиаса для таблиц

const p = SQL.table<Person>('Person').as('p');
const h = SQL.table<House>('House').as('h');

Доступ к полям таблиц

const sql = SQL.select(p.name).from(p);
// => SELECT p.name FROM Person p

Добавление алиаса для полей

const sql = SQL.select(p.name.as('first_name')).from(p);
// => SELECT p.name AS first_name FROM Person p

Примечание: Объявление объектов таблиц не является обязательным, но если не использовать объекты таблиц, то при каждом использовании таблицы требуется явное указание типа (например: SQL.select<Person>(SQL.star).from('Person')). В противном случае оператор SELECT будет лишён возвращаемого типа и вместо него будет использован тип any.

Выражение (Expression)

В lubejs все объекты выражений наследуются от класса Expression. Это включает следующие компоненты:

  • Поле (Field)
  • Одночленное выражение (UnaryOperation)
  • Двоичное выражение (BinaryOperation)
  • Переменная (Variable)
  • Вызов скалярной функции (ScalarFuncInvoke)
  • Литерал (Literal)

Литерал

В большинстве случаев можно использовать значения JavaScript для передачи литерала, как показано ниже:```ts SQL.update(p).set({ name: 'Имя' }) // => UPDATE p SET name = 'Имя'


Здесь `'Имя'` представляет собой строковый литерал, который `lubejs` автоматически распознаёт как структурированный литерал. Однако, когда требуется использовать литерал в вычислениях, поскольку значения JavaScript не имеют таких методов, можно создать литерал SQL с помощью `SQL.literal(1)`.

```ts
// Поиск людей старше 18 лет
const sql = SQL.select(p.star).from(p).where(SQL.literal(18).lt(p.age));
// => SELECT p.* FROM Person p WHERE 18 < p.age

Переменная

Объявление переменной

const name = SQL.var('name', DbType.string);
// DECLARE @name NVARCHAR(MAX)
name.set('Иванов')
// SET @name = 'Иванов';

Объявление и одновременное присвоение значений

const name = SQL.var('name', 'Петров');
// DECLARE @name NVARCHAR(MAX)
// SET @name = N'Петров'

Использование переменной в запросах

const sql = SQL.select(p.star).from(p).where(p.name.eq(name))
// => SELECT p.* FROM Person p WHERE p.name = @name

Вызов скалярной функции

См. Вызов функции

Бинарные операторы

| -------- | ---------------------------------------------------------------------------------------------- | -------------- |
| +        | `SQL.add(left, right)`、 `Expression.prototype.add(value)`                                      | Сложение       |
| -        | `SQL.sub(left, right)`、 `Expression.prototype.sub(value)`                                      | Вычитание     |
| *        | `SQL.mul(left, right)`、 `Expression.prototype.mul(value)`                                      | Умножение     |
| /        | `SQL.div(left, right)`、 `Expression.prototype.div(value)`                                      | Деление       |
| +(mssql) | `SQL.concat(left, right)`、 `Expression.prototype.concat(value)`                                | Конкатенация  |
| %(mssql) | `SQL.mod(left, right)`、 `Expression.prototype.mod(value)`                                      | Взятие остатка|
| &(mssql) | `SQL.and(left, right)`、 `Expression.prototype.and(value)`                                      | Логическое И  |
| \|(mssql)| `SQL.or(left, right)`、 `Expression.prototype.or(value)`                                        | Логическое ИЛИ|
| ^(mssql) | `SQL.xor(left, right)`、 `Expression.prototype.xor(value)`                                      | Исключающее ИЛИ|
| >>(mssql)| `SQL.shr(left, right)`、 `Expression.prototype.shr(value)`                                      | Сдвиг вправо  |
| <<(mssql)| `SQL.shl(left, right)`、 `Expression.prototype.shl(value)`                                      | Сдвиг влево   |
| XOR(mssql)| `SQL.xor(left, right)`、 `Expression.prototype.xor(value)`                                     | Исключающее ИЛИ|#### Унарные операторы

| Оператор | Метод                          | Описание            |
| -------- | ------------------------------- | -------------------- |
| -        | `SQL.neg(expr)`                 | Отрицательное число |
| ~ (mssql)| `SQL.not(expr)`                 | Логическое НЕ       |

#### Операции с выражениями

Используя выражения можно легко выполнять арифметические и сравнительные операции, например:

- Сложение

```ts
// Сложение
p.age.add(1)
// => p.age + 1
  • Генерация условий для запроса
// Генерация условий для запроса
p.age.lte(18)
// => p.age <= 18

Дополнительные детали см. в разделе Условия WHERE

Условия (Condition)

Условие в JSON формате

Формат JSON используется только для простых условий = и in для одиночных таблиц, соединяемых логическим И, обычно для быстрого поиска объекта.

const sql = SQL.select(p.star).from(p).where({ name: '张三', age: [18, 19, 20] })
// => SELECT p.* FROM Person p WHERE p.name = '张三' AND p.age IN (18, 19, 20)

Для использования более сложных условий запроса продолжайте чтение.

Сравнительные условия

Вышеупомянутый SQL-запрос можно записать следующим образом:

const sql = SQL.select(p.star).from(p).where(
    p.name.eq('张三').and(p.age.in(18, 19, 20))
)
// => SELECT p.* FROM Person p WHERE p.name = '张三' AND p.age IN (18, 19, 20)

Операторы сравнения| Оператор | Метод | Описание | | -------- | ------------------------------------------------------------------------------------------ | -----------------| | = | SQL.eq(left, right)Expression.prototype.eq(value) | равно | | <> | SQL.neq(left, right)Expression.prototype.neq(value) | не равно | | < | SQL.lt(left, right)Expression.prototype.lt(value) | меньше | | > | SQL.gt(left, right)Expression.prototype.gt(value) | больше | | <= | SQL.lte(left, right)Expression.prototype.lte(value) | меньше или равно | | >= | SQL.gte(left, right)Expression.prototype.gte(value) | больше или равно | | LIKE | SQL.like(left, right)Expression.prototype.like(value) | похожее значение | | NOT LIKE | SQL.notLike(left, right)Expression.prototype.notLike(value) | не похожее значение | | IN | SQL.in(left, right)Expression.prototype.in(value) | входит в список | | NOT IN | SQL.notIn(left, right)Expression.prototype.notIn(value) | не входит в список |Логические условия

// AND-запрос
const sql = SQL.select(p.star).from(p).where(
    p.age.lte(18)
        .and(p.sex.eq(''))
        .and(p.name.like('张%'))
)
// => SELECT p.* FROM Person p WHERE p.age >= 18 AND p.sex = '男' AND p.name like '张%'
// OR-запрос
const sql = SQL.select(p.star).from(p).where(p.sex.eq('').or(p.sex.eq('')))
// => SELECT p.* FROM Person p WHERE p.sex = '女' OR p.sex = '男'

Множество условий соединяются с помощью and, что позволяет использовать SQL.and для повышения читаемости:

// Поиск мужчин фамилии Zhang старше 18 лет
const sql = SQL.select(p.star).from(p).where(
    SQL.and(
        p.age.lte(18),
        p.sex.eq(''),
        p.name.like('张%')
    )
)
// => SELECT p.* FROM Person p WHERE (p.age >= 18 AND p.sex = '男' AND p.name like '张%')

Примечание: Возвращаемое условие запроса, построенного с помощью SQL.and/SQL.or, является группирующим условием

Логические операторы

Оператор Метод Описание
AND SQL.and(...условия) , Condition.prototype.and(условие) Логическое И
OR SQL.or(...условия) , Condition.prototype.or(условие) Логическое ИЛИ
NOT SQL.not(условие) Отрицание

Группирующие условия

//
const sql = SQL.select(p.star).from(p).where(
    SQL.and(
        p.пол.eq('мужской'),
        p.имя.like('Чжан%'),
        SQL.group(
            p.возраст.lt('18').or(p.возраст.gte('60'))
        )
    )
)
// => SELECT p.* FROM Person p
//    WHERE (p.пол = 'мужской' AND p.имя LIKE 'Чжан%' AND (p.возраст < 18 OR p.возраст >= 60))
```**Условие EXISTS**

```ts
// Поиск людей, имеющих дом
const sql = SQL.select(h.id).from(h).where(
    SQL.exists(SQL.select(p.id).from(p).where(p.houseId.eq(h.id)))
)
// => SELECT h.id FROM House h WHERE EXISTS(SELECT p.id FROM Person p WHERE p.houseId = h.id)

Построение SQL

В этом разделе мы рассмотрим, как использовать lubejs, чтобы создать SQL-запросы. Обратите внимание, что в данном разделе рассматривается только построение SQL-запросов, а выполнение этих запросов будет подробно объяснено в последующих разделах.

Определение моделей таблиц (только для TypeScript)

interface Person {
    // Поскольку это автоинкрементируемый столбец, он может быть пустым
    id?: number;
    имя: string;
    возраст?: number;
    пол?: 'мужской' | 'женский';
    описание?: string;
}

interface Дом {
    id?: number;
    заголовок: string;
    местоположение?: string;
    описание?: string;
}

Объявление объектов таблиц

const p = SQL.table<Person>('Person').as('p');
const h = SQL.table<Дом>('Дом').as('h');

Построение запроса SELECT

Ниже приведен пример полного запроса SELECT:

const sql = SQL.select({
    имя: p.имя,
    возраст: p.возраст
}).from(p).where(p.id.eq(1))
// => SELECT p.имя AS имя, p.возраст AS возраст FROM Person p WHERE p.id = 1

Определение возвращаемого содержимого

Формат JSON

const sql = SQL.select({
    имя: p.имя,
    возраст: p.возраст
}).from(p).where(p.id.eq(1))
// => SELECT p.имя AS имя, p.возраст AS возраст FROM Person p WHERE p.id = 1

Созданный таким образом SQL-запрос будет содержать тип данных, который также будет использоваться при получении результатов запроса. Полное возвращение таблицы (используя звездочку)```ts const sql = SQL.select(p.star).from(p) // => SELECT p.* FROM Person p


Использование `p.star` позволяет вернуть все столбцы таблицы `p`, что будет передано методом `select`.

#### Условия WHERE

Для получения информации о условиях запроса обратитесь к разделу [Условия (Conditions)](#Условия-(Conditions)).

#### Запросы с соединением нескольких таблиц

**Запрос с несколькими таблицами**

```ts
const sql = SQL.select({
    personName: p.name,
    houseTitle: h.houseTitle
})
	.from(p, h)
	.where(h.personId.eq(p.id))
// => SELECT p.name AS personName, h.title AS houseTitle FROM Person p, House h
//    WHERE h.personId = p.id

Запросы с соединением таблиц

Запросы с INNER JOIN / LEFT OUTER JOIN

Внутреннее соединение таблиц

// Запрос для получения данных о домах, связанных с человеком
const sql = SQL.select({
    personName: p.name,
    houseTitle: h.houseTitle
})
	.from(p)
	.join(h, h.personId.eq(p.id))
// => SELECT p.name AS personName, h.title AS houseTitle
//    FROM Person p INNER JOIN House h ON h.personId = p.id

Левое внешнее соединение таблиц

// Запрос для получения данных о домах, связанных с человеком
const sql = SQL.select({
    personName: p.name,
    houseTitle: h.houseTitle
})
	.from(p)
	.leftJoin(h, h.personId.eq(p.id))
// => SELECT p.name AS personName, h.title AS houseTitle
//    FROM Person p LEFT OUTER JOIN House h ON h.personId = p.id

Примечание: В связи с правилами использования SQL, поддерживаются только соединения типа INNER JOIN и LEFT OUTER JOIN. Соединения типа RIGHT JOIN не поддерживаются.

Подзапросы

Вложенные запросы

// Запрос для получения количества домов, связанных с человеком
const sql = SQL.select({
    personName: p.name,
    houseCount: SQL.select(SQL.count(h.id)).from(h).where(h.personId.eq(p.id))
})
	.from(p)
// => SELECT
//       p.name AS personName,
//       (SELECT count(h.id) FROM House h WHERE h.personId = p.id) AS houseCount
//    FROM Person p
```**Алгоритмическое имя для подзапроса**

По сравнению с нативным SQL, синтаксис LubeJS более чистый и удобочитаемый.

```ts
const hc = SQL.select({
    personId: h.personId,
    houseCount: SQL.count(h.id)
}).from(h).groupBy(h.personId).as('hc')

const sql = SQL.select({
    personName: p.name,
    houseCount: hc.houseCount
})
	.from(p)
	.join(hc, hc.personId.eq(p.id))

// => SELECT p.name AS personName, hc.houseCount as houseCount
//    FROM Person p
//    JOIN (SELECT personId, count(h.id) AS houseCount FROM h GROUP BY h.personId) as hc

Подзапрос IN

const sql = SQL.select(h.star).from(h).where(h.personId.in(
    SQL.select(p.id).from(p).where(p.name.like('张%'))
))
// => SELECT h.* FROM House h WHERE h.personId IN (SELECT p.id FROM Person p WHERE p.name LIKE '张%')

Группировка данных (GROUP BY)

GROUP BY

// Подсчет количества домов для каждого человека
const sql = SQL.select({
    personName: p.name,
    houseCount: SQL.count(h.id)
})
    .from(p)
    .join(h, h.personId.eq(p.id))
    .groupBy(p.name)
// => SELECT p.name AS personName, COUNT(h.id) AS houseCount
//    FROM Person p JOIN House h ON h.personId = p.id
//    GROUP BY p.name

Использование предложения HAVING

// Поиск людей с более чем двумя домами
const sql = SQL.select({
    personName: p.name,
    houseCount: SQL.count(h.id)
})
    .from(p)
    .join(h, h.personId.eq(p.id))
    .where(h.location.eq('Гуанчжоу'))
    .groupBy(p.name)
    .having(SQL.count(h.id).gte(2))

// => SELECT p.name AS personName, COUNT(h.id) AS houseCount
//    FROM Person p JOIN House h ON h.personId = p.id
//    WHERE h.location = 'Гуанчжоу'
//    GROUP BY p.name
//    HAVING COUNT(h.id) >= 2

Вставка данных с использованием WITH

const adult = SQL.select(p.star).from(p).where(p.age.gte('18')).asWith('adult')
const sql = SQL.with(adult).select(a.star).from(adult.as('a'))
// => WITH adult as (SELECT p.* FROM Person p WHERE p.age >= 18)
//      SELECT a.* FROM adult a
```### Создание вставочных запросов

#### Одиночная вставка

```typescript
// Одиночная вставка
const sql = SQL.insert(personTable).values({
    name: 'Чжан Сань',
    age: 23,
    sex: 'мужской',
    description: 'Это может быть лишним'
});

// => INSERT INTO Person(name, age, sex, description) VALUES ('Чжан Сань', 23, 'мужской', 'Это может быть лишним')

Множественная вставка

// Множественная вставка
const sql = SQL.insert(personTable).values([
    {
        name: 'Чжан Сань',
        age: 23,
        sex: 'мужской',
        description: 'Это может быть лишним'
    },
    {
        name: 'Ли Сзи',
        age: 43,
        sex: 'мужской',
        description: 'Это может быть лишним'
    }
]);
// => INSERT INTO Person(name, age, sex, description)
//    VALUES ('Чжан Сань', 23, 'мужской', 'Это может быть лишним'),
//           ('Ли Сзи', 43, 'мужской', 'Это может быть лишним')

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

Создание запроса UPDATE

Обновление с использованием имени таблицы

const sql = SQL.update(personTable).set({
    age: personTable.age.add(1) // Возраст увеличен на один год
}).where(personTable.name.eq('Зhang San'))

// => UPDATE Person SET age = p.age + 1 WHERE Person.name = 'Зhang San'

Обновление с использованием псевдонима

// Эти люди, у которых есть недвижимость в Гуанчжоу, стали старше на один год
const sql = SQL.update(p).set({
    age: p.age.add(1) // Возраст увеличен на один год
})
	.from(p)
    .join(h, h.personId.eq(p.id))
    .where(h.location.eq('Гуанчжоу'))
```// => UPDATE p SET age = p.age + 1
//    FROM Person p
//    JOIN House h ON h.personId = p.id
//    WHERE h.location = 'Гуанчжоу'


### Создание запроса DELETE

#### Удаление с использованием имени таблицы

```ts
const sql = SQL.delete(personTable).where(personTable.age.gt(60));
// => DELETE Person WHERE Person.age > 60

Удаление с использованием псевдонима

// Удаление данных людей, имеющих недвижимость в Гуанчжоу
const sql = SQL.delete(p)
	.from(p)
	.join(h, h.personId.eq(p.id))
	.where(h.location.eq('Гуанчжоу'));
// => DELETE p
//    FROM Person p
//    JOIN House h ON h.personId = p.id
//    WHERE h.location = 'Гуанчжоу'

Вызов функций

Быстрый вызов (скалярная функция)

Предположим, что у нас есть скалярная функция с именем Hello, которая возвращает тип varchar(100) и имеет один параметр @name varchar(50). Она может быть вызвана следующим образом:

SQL.select(SQL.func('Hello').invokeAsScalar('мир')); // => SELECT hello('мир')

Декларативный вызов как JavaScript-функция (скалярная функция)

Можно также объявить функцию как JavaScript-функцию для более полной проверки типов и повторного использования.

// Объявление функции
const hello = SQL.makeInvoke<string, ['name']: string>('Hello');

// Вызов функции
const sql = SQL.select(hello('мир')); // => SELECT hello('мир')

Быстрый вызов (табличная функция)

Предположим, что у нас есть табличная функция с именем get_person, которая возвращает тип Person и имеет один параметр @name varchar(50). Она может быть вызвана следующим образом:

const p = SQL.func('get_person').invokeAsPerson<Person>('Зhang San').as(p);
const sql = SQL.select(p.star).from(p); // => SELECT p.* FROM get_person('Зhang San') AS p
```#### Декларативный вызов как JavaScript-функция (табличная функция)

```ts
// Объявление функции
const getPerson = SQL.makeInvoke<Person, [['name']: string]>('get_person');

const p = getPerson('Чжан Сан').as('p');
// Вызов функции
const sql = SQL.select(p.star).from(p)); // => SELECT p.* FROM get_person('Чжан Сан') AS p

Вызов хранимых процедур

Предположим, что у нас есть следующая хранимая процедура:

CREATE PROCEDURE sp_get_person
(
	@type VARCHAR(20) = 'all',
    @total INT OUTPUT
)
AS
BEGIN
	SELECT @total = COUNT(p.id) FROM Person;
	IF (@type = 'adult')
		SELECT * FROM Person p WHERE p.age >= 18;
	ELSE IF (@type = 'children')
		SELECT * FROM Person p WHERE p.age < 18;
	ELSE IF (@type = 'aged')
		SELECT * FROM Person p WHERE p.age >= 50;
	ELSE
		SELECT * FROM Person;
	END

	RETURN  100;
END

Быстрый вызов

const sql = SQL.execute<number, Person>('sp_get_person', ['children',  Yöntem]);

Вызов с параметрами (когда требуется получить выходной параметр)

const params: Parameter[] = [
    SQL.input('type', 'adult'),
    SQL.output('total', DbType.int32)
];
const sql = SQL.execute<number, Person>('sp_get_person', ['children', 0]);
const result = await db.query(sql);

console.log(result.returnValue); // => 100
console.log(result.rows); // Результат выполнения SELECT запроса
console.log(result.output.total); // => Количество строк в таблице Person

Объявление как JS функции

Можно также объявить хранимую процедуру как JavaScript функцию, чтобы иметь более полную проверку типов и повторное использование.

// Объявление SQL хранимой процедуры как TypeScript функции
const sp_get_person = SQL.makeExec<'adult' | 'children' | 'aged' | 'all', number, [Person]>('sp_get_person');

// Затем мы можем вызвать её таким образом, чтобы достичь того же эффекта, что и быстрый вызов.
const sql = sp_get_person('children', 0);
```### Использование строк SQL для построения запросов

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

```ts
SQL.raw('')

Другие запросы

Создание таблицы

const sql = SQL.createTable('Person').as(({ column }) => [
        // Определяющая колонка
        column('id', DbType.int32).notNull().primaryKey().identity(),
        column('name', DbType.string(100)).notNull(),
        column('age', DbType.int32).null(),
        column('sex', DbType.string(2)).null(),
        column('description', DbType.string(100)).null(),
		// По умолчанию значение getDate()
		column('createDate', DbType.datetime).default(SQL.now())
    ])
// => CREATE TABLE Person(
//   id int not null primary key identity(1, 1),
//   name nvarchar(100) not null,
//   age int null,
//   sex nvarchar(2) null,
//   description nvarchar(100) null,
//   createDate datetime default (sysdate())
// )

Изменение таблицы

  • Добавление столбца
const sql = SQL.alterTable('Person').addColumn(column => column('rowflag', DbType.rowflag).notNull())
// => ALTER TABLE Person add column rowflag TIMESTAMP NOT NULL
  • Удаление столбца
const sql = SQL.alterTable('Person').dropColumn('rowflag')
// => ALTER TABLE Person drop column rowflag
  • Создание внешнего ключа
const sql = SQL.alterTable('House').addForeignKey(
    fk => fk('FK_HOUSE_PERSON').on('personId').reference('Person', ['id'])
)
// => ALTER TABLE House ADD FOREIGN KEY FK_HOUSE_PERSON ON personId REFERENCES Person(id)
  • Удаление внешнего ключа
const sql = SQL.alterTable('House').dropForeignKey('FK_HOUSE_PERSON')
```#### Создание индекса

```ts
const sql = SQL.createIndex('IX_Person_name').on('Person', ['name']);
// => CREATE INDEX IX_Person_name ON Person(name)

Удаление индекса

const sql = SQL.dropIndex('Person', 'IX_Person_name');
// => DROP INDEX IX_Person_name

Примечание: из-за различий в поведении различных диалектов баз данных, здесь требуется передача имени таблицы.

Из-за ограничений по объему, в этом разделе больше нет информации о других методах использования запросов. Для получения более подробной информации обратитесь к "Руководству по API".

Поддерживаемые Lubejs запросы#### Запросы на операции с данными| Инструкция | Использование | Описание |

| ---------------------------------- | ------------------------------------------------------------ | --------------------------- | | insert | SQL.insert(...).values(...) | | | update | SQL.update(...).set(...).from(...).where(...) | | | select | SQL.select(...).from(...).where(...)
Дополнительные продвинутые методы см. в разделе Создание инструкций SELECT | | | delete | SQL.delete(...).from(...).where(...) | | | case when ... then ... else ... end | SQL.case(...).when(...).else(...) | Инструкция CASE | | execute | SQL.execute(...)SQL.proc(...).execute(...) | Вызов хранимых процедур | | | SQL.makeExec(...) | Объявление SQL-процедуры как JS-функции | | invoke | SQL.invokeAsScalar(...)SQL.invokeAsTable(...) | Вызов функций | | | SQL.makeInvoke(...) | Объявление SQL-функций как JS-функций |Примечание: В таблице используются символы кавычек “”, которые могут быть заменены на обычные ".#### Операции с данными| Инструкция | Использование | Описание | | ---------------------- | --------------------------------------------------------------------- | -------- | | create table | SQL.createTable(...).as(...). Дополнительные продвинутые методы см. в «Создание таблиц» | | | alter table | SQL.alterTable(...).as(...). Дополнительные продвинутые методы см. в «Изменение таблиц» | | | drop table | SQL.dropTable(...) | | | create view | SQL.createView(...).as(...) | | | alter view | SQL.alterView(...).as(...) | | | drop view | SQL.dropView(...) | | | create procedure | SQL.createProcedure(...).as(...) | | | alter procedure | SQL.alterProcedure(...).as(...) | | | drop procedure | SQL.dropProcedure(...) | | | create function | SQL.createFunction(...).as(...) | | | alter function | SQL.alterFunction(...).as(...) | | | drop function | SQL.dropFunction(...) | | | create sequence | SQL.createSequence(...).as(...).startWith(...).incrementBy(...) | | | drop sequence | SQL.dropSequence(...) | | | create database | SQL.createDatabase(...).collate(...) | | | alter database | SQL.alterDatabase(...).collate(...) | | | drop database | SQL.dropDatabase(...) | | | create index | SQL.createIndex(...).on(...). ) | | | drop index |SQL.drop_index(...) | |#### Управляющие инструкции программы| Инstrukciya | Ispol'zovanie | Opisanie | | ------------- | ----------------------------------- | -------- | | if..then..else |SQL.if(...).then(...).else(...)| | | while |SQL.while(...).do(...) | | | begin ... end |SQL.block(...) | | | break |SQL.break() | | | return |SQL.return(...) | | | continue |SQL.continue` | |

Выполнение SQL

Создание таблицы

import { SQL, DbType, connect } from 'lubejs';

(async () => {
	const db = await connect('mssql://user:password@localhost:1433');
	await db.query(SQL.createDatabase('test-database'));
	await db.changeDatabase('test-database');
	await db.query(SQL.createTable('Person').as(({ column }) => [
		column('id', DbType.int32).notNull().primaryKey().identity(),
		column('name', DbType.string(100)).notNull(),
		column('age', DbType.int32).null(),
		column('sex', DbType.string(2)).null(),
		column('description', DbType.string(100)).null()
	]));
	await db.close();
})();

Использование соединения (класс Connection)

Класс Connection является основой всего уровня баз данных и содержит множество методов, более удобных для использования, чем создание SQL (например, insert, update, select, delete и т.д.)

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

const db = await connect('mssql://user:password@localhost:1433/database');

Выполнение запроса (.query)

Использование конструкций для создания SQL-запросов

const sql = SQL.select(1);
const result = await db.query(sql);
console.log(result); // => { rows: [{ '#column_1': 1 }] }

Передача параметров вместе с запросом```ts const sql = SQL.select(SQL.input('@p', 1)); const result = await db.query(sql); console.log(result); // => { rows: [{ '#column_1': 1 }] }


Получение выходных параметров

```ts
const p1 = SQL.output('p', DbType.int32);
const sql = SQL.select(SQL.assign(p1, 1));
const result = await db.query(sql); // => SELECT @p = 1;
// Получаем значение из списка выходных значений
console.log(result.output['p']); // => 1
// Также можно получить значение, используя оригинальный параметр
console.log(p1.value); // => 1

Использование сырой SQL-строки запроса

Использование SQL-строки

const sql = 'SELECT 1 AS [#column_1]';
const result = await db.query(sql);
console.log(result); // => { rows: [{ '#column_1': 1 }]} }

Примечание: при использовании сырой SQL-строки запроса, lubejs не будет указывать имя столбца.

Передача параметров вместе с запросом

const sql = 'SELECT @p1 AS [#column_1]';
const result = await db.query(sql, [ 1 ]);
console.log(result); // => { rows: [{ '#column_1': 1 }]} }

Возвращаемое значение:- Тип: QueryResult<T, R, O>

  • Структура```ts { rows: T[]; // Первый возвращаемый набор данных запроса returnValue: R; // Возвращаемое значение, представляющее выходное значение хранимой процедуры rowsets: O; // Если SQL возвращает несколько наборов данных, они хранятся в этом свойстве как массив объектов, где первый элемент является ссылкой на .rows rowsAffected: number; // Количество затронутых строк output: Record<string, Scalar>; // Возвращенные значения выходных параметров }

**Запрос одного значения**

```ts
const sql = SQL.select(1);
const result = await db.queryScalar(sql);
console.log(result); // => 1

Использование параметров аналогично передаче параметров методом .query.

Запрос значения с использованием выражения

Можно также запросить значение, используя выражение; lubejs автоматически преобразует это в SELECT-запрос и возвращает первое значение.

const result = await db.queryScalar(SQL.literal(1));
console.log(result); // => 1

Вставка данных (.insert)

Использование объекта таблицы для запроса

await db.insert(personTable, {
    name: '张三',
    age: 42,
    sex: 'мужской',
    description: 'это можно игнорировать'
});

Прямое использование имени таблицы для вставки

await db.insert<Person>('Person', {
    name: '张三',
    age: 42,
    sex: 'мужской',
    description: 'это можно игнорировать'
});

Внимание: при прямой вставке с указанием имени таблицы и отсутствием спецификации типа generics, потеряется типизация.

Поиск одной строки (.find)

Поиск одной записи:

const row = await db.find(personTable, { name: '张三' });
console.log(row);
// => {
//    id: 1,
//    name: '张三',
//    age: 42,
//    sex: 'мужской',
//    description: 'это можно игнорировать'
// }

Выбор нескольких строк (.select)

const rows = await db.select(personTable, {
    where: {
        name: '张三'
    }
});

console.log(rows);
// => [{
//    id: 1,
//    name: '张三',
//    age: 42,
//    sex: 'мужской',
//    description: 'это можно игнорировать'
// }]

Указание полей для возврата

const rows = await db.select(personTable, {
    fields: ['name'],
    where: {
        name: '张三'
    }
});
``````md
## Вывод результатов запроса

```javascript
console.log(rows);
// => [{
//     name: 'Зhang San'
// }]

Удаление данных (.delete)

await db.delete<Person>('Person', {
    id: 1
});

Обновление данных (.update)

await db.update<Person>('Person', {
    age: 43 // стал старше на год
}, { id: 1 });

Вывод лога SQL-запросов (.on('command', handler))

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

db.on('command', cmd => {
    //
    console.log('SQL: ' + cmd.sql);
    // параметры, переданные при выполнении запроса
    console.log('ПАРАМЕТРЫ: ' + JSON.stringify(cmd.params));
})

Конфигурационный файл

Многие ситуации требуют того, чтобы строки подключения или конфигурации не были записаны в коде, либо требуется использование инструмента миграции баз данных. В таких случаях можно использовать конфигурационный файл. Для lubejs имя конфигурационного файла должно быть .lubejs.ts / .lubejs.js. Из-за необходимости импорта драйверов и других причин, использование JSON-формата для конфигураций не поддерживается.

Структура конфигурационного файла:

// Импорт типа конфигурации, в JavaScript этот шаг можно пропустить
import { LubeConfig } from 'lubejs';
// Импорт драйвера
import 'lubejs-mssql';
import './orm-configure'
// import 'orm';
``````javascript
export const config: LubeConfig = {
  // Название по умолчанию для конфигурации, используется при вызове функции connect для создания соединения, если параметры не передаются. Название должно совпадать с одним из существующих узлов в `configures`.
  default: 'lubejs-test',
  // Директория для хранения файлов миграции, используемых при миграциях данных. По умолчанию это `migrates`.
  migrateDir: 'migrates',
  // Конфигурации
  configures: {
    // Название конфигурации, когда название совпадает с названием DbContext, эта конфигурация становится основной конфигурацией подключения для этого DbContext.
    'lubejs-test': {
      // Имя драйвера, которое должно быть заранее импортировано
      dialect: 'mssql',
      // Название сервера базы данных, также может быть IP-адресом
      host: 'your-server',
      // Имя пользователя
      user: 'sa',
      // Пароль
      password: 'your!password',
      // Название базы данных
      database: 'lubejs-orm-test',
      port: 1433,
    }
  }
};
``````typescript
export default config;

Когда название конфигурации совпадает с названием DbContext, эта конфигурация становится основной конфигурацией подключения для данного DbContext.

ORM

Ключевые понятия

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

Глобальный тип ключа (EntityKey)

Для усиления управления типами lubejs определяет правила для типов ключей:

Один объект имеет и только один атрибут, который является ключом

lubejs получает тип этого атрибута через интерфейс EntityKey, используя его как тип ключа для проверки типов методов, таких как Repository.prototype.get. Поэтому объекты должны реализовать интерфейс EntityKey.

По умолчанию, EntityKey представляет собой пустой интерфейс, поэтому тип ключа будет Scalar, если интерфейс EntityKey не определён. При этом вызов Repository.prototype.get будет выглядеть так:

await repo.get(1);
await repo.get('1');
await repo.get(new Date());
// Все эти вызовы проходят проверку типов

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

Поэтому перед объявлением сущностей можно использовать возможность объединения типов в TypeScript для определения интерфейса EntityKey.

// Объявление глобального ключа сущности
declare module 'lubejs' {
    export interface EntityKey {
        // Если это автоматически генерируемый ID, его следует объявить как nullable, чтобы избежать ошибок синтаксического анализа при использовании .insert.
        id?: number;
    }
}
```Если нам не требуется повторное определение ключа сущности в каждом объекте, мы можем также использовать следующий подход для неявного объявления ключа сущности для всех сущностей:

```ts
contextBuilder.hasGlobalKey('id', Number);

Сущностные классы (Entity)

Сущность является базовым элементом операций ORM lubejs с базой данных. Если нам требуется выполнять операции с базой данных через Repository, то сначала необходимо определить сущностные классы.

Существуют различные виды сущностных классов:

  • Таблица — отображается в базе данных как таблица,
  • Вид — отображается в базе данных как представление,
  • Запрос — представляет собой только читающий SQL запрос и не может быть отображен в виде объекта в базе данных.

Все пользовательские сущностные классы могут наследовать класс Entity, либо не наследовать его. Однако сущность должна быть классом, так как интерфейсы и типы в TypeScript будут удалены после компиляции, а декораторы не могут применяться к интерфейсам. Кроме того: сущности, не наследующие класс Entity, не имеют статического метода .create.

В последующих разделах будет рассказано, как создавать сущности.

Навигационные свойства (свойства связи)

Сущностные классы могут объявлять навигационные свойства, которые предоставляют удобные возможности для выполнения связанных запросов между таблицами.Например, если мы объявляем одно-к-одному (основное) навигационное свойство для сущностного класса User, то используем следующий код для получения данных User вместе с Employee:

const user = await userRepo.get(1, { includes: { employee: true } });
// => { id: 1, ..., employee: { userId: 1, ... }}

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

  • Навигационные свойства являются двунаправленными; даже если они не объявлены в другом сущностном классе, конструктор всё равно объявит им скрытое навигационное свойство, название которого следует соглашению связь.
  • Сущности, являющиеся частью навигационных свойств, должны иметь внешний ключ, даже если он не объявлен явно, конструктор всё равно объявит ему скрытый внешний ключ, название которого следует соглашению связь. Кроме того, навигационные свойства также могут иметь ассоциацию с сохранением свойств, подробнее см.: Репозиторий объектов (Repository).

Идентификатор внешнего ключа

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

Например, свойство userId в сущности Employee, которое ссылается на первичный ключ таблицы User, то есть на свойство id. Таким образом, userId является идентификатором внешнего ключа.#### Неявные свойства

Навигационные свойства и идентификаторы внешних ключей могут быть объявлены как неявные, то есть они не объявляются явно в классе сущности, но автоматически объявляются моделями. При запросах неявные идентификаторы внешних ключей будут возвращаться вместе со сущностью, тогда как неявные навигационные свойства требуют специальных методов для обхода проверок синтаксиса TypeScript. В возвращаемых данных неявные навигационные свойства и неявные идентификаторы внешних ключей являются недоступными для перечисления (через Object.keys(obj) эти свойства не вернутся), при сериализации через JSON.stringify(obj) данные этих свойств также не будут сериализованы.

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

  • Когда навигационное свойство представляет собой одиночную ссылку на объект, используется имя таблицы (например, Employee), начальное маленькое буквы. Если такое же название уже существует в сущности (например, employee), то используется это существующее свойство.

  • Когда навигационное свойство представляет собой список ссылок (например, один ко многим, многие ко многим), используется имя таблицы (например, Employee), начальное маленькое буквы, затем преобразуется во множественное число (например, employees). Если такое же название уже существует в сущности, то используется это существующее свойство.Неявные идентификаторы внешних ключей имеют следующие правила названий:

  • Используется имя таблицы (в данном случае User), начальная маленькая буква, затем добавляется суффикс Id для создания имени идентификатора внешнего ключа. Если такое же название уже существует в сущности, то используется это существующее свойство.

Примечание: Независимо от того, что навигационные свойства или идентификаторы внешних ключей объявлены автоматически, если такие же свойства уже существуют в сущности, модель будет использовать эти существующие свойства, и они больше не будут считаться неявными. Если типы не совпадают, LubeJS не выдаст ошибку (из-за отсутствия функции отражения типа в JS/TS), поэтому следует особенно внимательно относиться к таким объявляемым свойствам и их типам, чтобы избежать проблем. Сильнее всего рекомендуется использовать явные объявления или полностью неявные объявления.

Примеры сущностейПоскольку TypeScript использует механизм совместимости типов вместо строгих интерфейсов, как в Java или C#, даже если объект не является экземпляром сущности, он может использоваться как сущность, если его тип совместим. Основные характеристики таких объектов представлены ниже:

  • Результаты запросов из объектов Context, Repository или Queryable возвращаются как экземпляры сущностей (за исключением случаев динамического типа).
  • Мы можем использовать статический метод класса сущностей create для создания экземпляра сущности, например: User.create({ name: 'abc', password: 'abc123' }).
  • В процессе работы с базой данных, например при создании новой записи, мы также можем использовать объекты, соответствующие типу сущности (например, {} — конструктор объекта), чтобы работать с базой данных, например: { name: 'abc', password: 'abc123' }. Однако при использовании Context для выполнения операций необходимо указывать тип сущности, например: context.insert(User, { name: 'abc', password: 'abc123' }).#### Контекст базы данных (DbContext)

Обычно класс DbContext соответствует одной базе данных. Объявление Repository не является обязательным; если его нет, можно прямым образом использовать класс DbContext для доступа к базе данных.

  • Для манипулирования данными с помощью DbContext используется методика, аналогичная той, которая используется для Repository. Однако требуется наличие экземпляра сущности или указание типа сущности. Подробнее см. объект репозитория (Repository).

  • Непосредственный поиск данных через DbContext невозможен; необходимо получить объект для поиска с помощью context.getQueryable(), затем выполнять поиск.

Создание экземпляра класса контекста

Можно использовать метод createContext для создания экземпляра класса DbContext;

import { createContext } from 'lubejs';
import { DB } from './db';

// Первый аргумент — это конструктор класса DbContext, второй — конфигурация соединения
const ctx = await createContext(DB, 'mssql://user:password@localhost:1433/database');

Создание с использованием файла конфигурации

Когда конфигурация не передается явно, lubejs автоматически использует конфигурацию из файла конфигурации, соответствующую имени класса DbContext. Поэтому следует обратить особое внимание на то, чтобы имя класса DbContext не совпадало с другими.

import { createContext } from 'lubejs';
const ctx = await createContext(DB);
```**Создание экземпляра по умолчанию класса DbContext**

Здесь важно отметить, что по умолчанию класс DbContext может не совпадать с встроенным классом DbContext lubejs. `moduleBuilder` регистрирует первый встреченной класс `DbContext`, который заменяет встроенный класс `DbContext` lubejs. При вызове `createContext` без передачи параметров, lubejs читает конфигурацию соединения из файла конфигурации и создаёт по умолчанию экземпляр класса `DbContext`.

```ts
import { createContext } from 'lubejs';
const ctx = await createContext();

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

Объект с возможностью запроса (Queryable)

Объект Queryable реализует все возможности для выполнения запросов и при этом сам является асинхронным итератором, что позволяет использовать его аналогично спискам. API объекта спроектирован так, чтобы отражать методы встроенных массивов JavaScript, поэтому многие операции очень похожи.

Для повышения производительности Queryable имеет возможность отложенного выполнения запросов, то есть данные будут запрошены из базы данных только при вызовах .fetchAll(), .fetchFirst() или при использовании асинхронного итератора for await (const item of userQuerable) для перебора объекта Queryable.

Получение экземпляра объекта Queryable```ts

const userQueryable = ctx.getQueryable(User);


#### Получение всех данных (fetchAll)

```ts
const allUsers = await userQueryable.fetchAll();

Получение первой строки (fetchFirst)

const user = await userQueryable.fetchFirst();

Фильтрация данных (filter)

Метод Queryable.prototype.filter принимает объект Rowset для построения условий фильтрации. Для фильтрации данных достаточно вернуть объект условия Condition.

const adminUser = await userQueryable.filter(p => p.name.eq('admin')).fetchFirst();

Получение связанных данных (include)

Метод Queryable.prototype.include позволяет указывать подэлементы для запроса.

const user = userQueryable.include({
    employee: true
}).fetchFirst()
// user => {
//   name: '...',
//   // ...
//   employee: {
//     //...
//   }
// }

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

const user = userQueryable.include({
    employee: {
        positions: true
    }
}).fetchFirst();
// user => {
//   name: '...',
//   // ...
//   employee: {
//     //...
//     positions: [...]
//   }
// }

Использование асинхронного итератора для перебора объекта Queryable

for await (const item of userQueryable) {
    console.log(item);
}

Объект хранилища (Repository)

Объект Repository предназначен для предоставления функциональностей получения, вставки, обновления и сохранения данных.

Получение объекта Repository для одного User

const userRepo = ctx.getRepository(User);

Использование асинхронного итератора для перебора объекта Repository

for await (const item of userRepo) {
    console.log(item);
}
```#### Получение данных одного объекта (get)

Передача ключей для получения данных объекта. При попытке получить несуществующие данные будет выброшено исключение. Если вы хотите избежать выбрасывания исключения, используйте метод `Queryable.prototype.filter` для выполнения запроса. **Получение данных с помощью DbContext**

```ts
const user = await ctx.get(User, 1);
// user — это экземпляр класса User

Получение данных с помощью Repository

const user = userRepo.get(1);
// user — это экземпляр класса User, user instanceof User === true

Объект, полученный через .get, является экземпляром класса-сущности.

Вставка данных (insert)

При вставке экземпляра сущности, если навигационные свойства имеют значения, будут выполнены операции сохранения объектов этих свойств.

Использование DbContext для вставки

Указание конструктора сущности для вставки:

await ctx.insert(User, {
    name: 'admin',
    password: '123456'
});

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

await ctx.insert(User.create({
    name: 'admin',
    password: '123456'
}));

Использование Repository для вставки

Использование JSON-объекта для вставки:

await userRepo.insert({
    name: 'admin',
    password: '123456'
});

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

await userRepo.insert(User.create({
    name: 'admin',
    password: '123456'
}));
Обновление данных (update)Во всех операциях обновления данных LubeJS используется наличие первичного ключа для проверки наличия данных в базе данных. При обновлении данных сущности, если навигационные свойства имеют значения, будут выполнены операции сохранения объектов этих свойств.Использование DbContext для обновления

Указание конструктора сущности для обновления:

const user = await ctx.get(User, 1);
user.password = 'changed!password';
await ctx.update(User, user);

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

// .get возвращает экземпляр класса сущности
const user = await userRepo.get(User, 1);
user.password = 'changed!password';
await userRepo.update(user);

Метод update обновляет данные, но если таких данных нет в базе данных, будет выброшено исключение.

Использование Repository для обновления

const user = await userRepo.get(1);
user.password = 'changed!password';
await userRepo.update(user);

Метод update обновляет данные, но если таких данных нет в базе данных, будет выброшено исключение.

Обработка проблем конкурентной работы

Как было упомянуто ранее, при использовании Repository для сохранения данных существует риск перезаписи данных. Поэтому требуется решение проблемы конкурентной работы. Мы рекомендуем добавить атрибут rowflag к сущностям, где возможна конкурентная работа. Когда сущность имеет атрибут rowflag, он автоматически используется как условие для обновления при вызове метода update. Если данные не могут быть обновлены, будет выброшено исключение.

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

Для предотвращения перезаписи рекомендуется добавить атрибут rowflag к классам сущностей, где возможна конкуренция.

Использование DbContext для удаления

Задайте конструктор сущности для удаления:

const user = await ctx.get(User, 1);
await ctx.delete(User, user);

Вы также можете использовать экземпляр сущности для удаления:

const user = await ctx.get(User, 1);
await ctx.delete(user);

Использование Repository для удаления

Задайте конструктор сущности для удаления:

const user = await userRepo.get(1);
await userRepo.delete(user);

Вы также можете использовать экземпляр сущности для удаления:

const user = await userRepo.get(1);
await userRepo.delete(user);
Сохранение данных сущностей (сохранение)

Вы можете удивиться наличию двух методов — .update и .save, для сохранения данных. На самом деле, метод .save предоставляет более продвинутую функциональность. Если существуют связанные свойства и значение этих свойств не равно undefined, то метод .save анализирует эти связи и отправляет их вместе с основной записью. В отличие от этого, метод update обновляет только данные, но не автоматически вставляет или обновляет.

Использование DbContext для сохранения

const user = await ctx.get(User, 1, { includes: { employee: true } });
user.password = 'changed!password';
user.employee.description = 'Сотрудник изменил пароль';

await ctx.save(user);
// Сохраняет как сам объект user, так и его связанный объект employee
```**Использование Repository для сохранения**

```ts
const user = await userRepo.get(1, { includes: { employee: true } });
user.password = 'changed!password';
user.employee.description = 'Сотрудник изменил пароль';

await userRepo.save(user);
// Сохраняет как сам объект user, так и его связанный объект employee

Правила сохранения текущего объекта следующие:

  • Если данные уже присутствуют в базе данных, выполняется обновление

  • Если данные отсутствуют в базе данных, выполняется вставкаПравила сохранения связанных свойств следуют:

  • Когда связанное свойство имеет значение undefined, никакие действия над этим свойством не выполняются.

  • Когда связанное свойство представляет собой один-к-одному (основной) отношение:

    • Когда это свойство имеет значение, выполняется операция (вставка/обновление) этого значения.
    • Когда это свойство равно null, выполняется операция удаления связанного с ним объекта данных.
  • Когда связанное свойство представляет собой один-к-одному (зависимый) отношение:

    • Когда это свойство равно null, выполняется удаление связи (устанавливается внешний ключ как DBNULL).
    • Когда это свойство имеет значение, выполняется операция вставки/обновления связанного с ним объекта данных.
  • Когда связанное свойство представляет собой один-ко-многим отношение:

    • Когда это свойство имеет значение, анализируется существующий в базе данных набор данных, и выполняются следующие действия: данные, совпадающие со значением данного свойства и имеющиеся в базе данных, обновляются; данные, отсутствующие в базе данных, но присутствующие в значении данного свойства, вставляются; данные, присутствующие в базе данных, но отсутствующие в значении данного свойства, удаляются.
    • Когда это свойство равно null или [], выполняется очистка связанных данных в базе данных.- Когда связанное свойство представляет собой многие ко многим отношение
    • Когда это свойство имеет значение, анализируется текущее состояние таблицы промежуточных связей в базе данных, и выполняются следующие действия: данные, совпадающие со значением данного свойства и имеющиеся в базе данных, обновляются; данные, отсутствующие в базе данных, но присутствующие в значении данного свойства, вставляются; данные, присутствующие в базе данных, но отсутствующие в значении данного свойства, удаляются. В то же время выполняется обновление данных в таблице связей. (Не рекомендуется выполнять обновление данных в таблице связей при данном условии, так как если одновременно выполняются операции удаления и обновления данных, будет удален только объект данных из таблицы промежуточных связей, а не целевой таблицы.)
    • Когда это свойство равно null или [], выполняется очистка данных в таблице промежуточных связей. Когда связанные свойства имеют многоуровневую структуру, данное правило также применяется.Внимание: при использовании сохранения данных следует учитывать возможность конкурентного доступа; в противном случае существует риск перезаписи данных.

Дополнительные примеры см. в разделе Связывание отношений

Моделирование

Моделирование ORM осуществляется следующими двумя способами:

  • Использование декораторов для объявления, при этом сущности, объявленные с помощью декораторов, в настоящее время не могут наследовать конфигурацию декораторов через наследование класса. Однако это временное ограничение, которое будет устранено в следующей версии LUBEJS.
  • Использование API для объявления

Оба этих метода объявления можно использовать вместе, причём объявление с помощью декораторов выполняется первым, а объявление с помощью API — вторым, если позволяет конфигурация декораторов.

Внимание: режим декораторов поддерживает только TypeScript и требует открытия опций experimentalDecorators и emitDecoratorMetadata в файле tsconfig.json. В противном случае возникнут ошибки из-за невозможности получения типа свойства.

Создание файла сущностей

Таблица сущностей (объявление с декораторами)

Объявление сущности User

import { DB } from '../index';
import {
  column,
  comment,
  context,
  Entity,
  EntityKey,
  identity,
  key,
  nullable,
  oneToOne,
  principal,
  table,
} from 'lubejs';
``````typescript
@comment('Таблица пользователей') // Объявление примечаний, которые будут помещены в расширенные атрибуты базы данных / примечания.
@Table()                         // Объявление таблицы
@context(() => DB)              // Привязка сущности к DbContext
@Data([                          // Объявление семенного набора данных, который будет автоматически инициализирован в базе данных при выполнении миграции данных.
    { id: 1, name: 'admin', password: '123456' }
])
export class User extends Entity implements EntityKey {
    // Объявление столбца
	@Column()
    // Объявление ключа
  	@Key()
    // Объявление идентификатора
    @Identity()
    @Comment('ID')
    id?: bigint;

    @Comment('Имя пользователя')
    @Column()
    name!: string;

    @Comment('Пароль')
    // Объявление пустого значения, если не указано, то значение по умолчанию - null
    @Nullable()
    @Column()
    password?: string;

    @Comment('Описание')
    @Nullable()
    @Column()
    description?: string;
}

Вы можете удивиться необходимости использования стрелочной функции при ссылке на класс DB. На самом деле здесь может возникнуть проблема циклического обращения в Node.js. Когда классы хранятся в отдельных файлах, файл, объявляющий класс DB, ссылается на файл User, а файл User ссылается обратно на класс DB. Использование функции позволяет отложить выполнение до тех пор, пока все классы не будут созданы, что предотвращает получение undefined значений при обращении к классам. Аналогичная проблема существует и при взаимной ссылке между сущностями.##### Таблица сущностей (объявление API)

import {
  modelBuilder,
  DbContext,
  Repository,
  DbType,
  Entity,
  SQL,
  EntityKey,
  Binary,
  Decimal,
} from 'lubejs';

/**
 * Класс сущности пользователя
 */
export class User extends Entity implements EntityKey {
  id?: bigint;
  name!: string;
  password!: string;
  description?: string;
  employee?: Employee;
}

modelBuilder.context(DB, context => {
  context
    // Объявление сущности
    .entity(User)
    // Преобразование сущности в таблицу
    .asTable(table => {
      // Добавление комментария к таблице
      table.hasComment('Сотрудник');
      table
        // Объявление столбца
        .property(p => p.id, BigInt)
        // Объявление столбца как первичного ключа
        .isIdentity()
        // Добавление комментария к столбцу
        .hasComment('ID');
      table.property(p => p.name, String).hasComment('Имя сотрудника');
      table
        .property(p => p.password, String)
        // Объявление столбца как nullable
        .isNullable()
        .hasComment('Пароль');
      table
        .property(p => p.description, String)
        .isNullable()
        .hasComment('Описание');
      // Объявление первичного ключа
      table.hasKey(p => p.id).hasComment('Первичный ключ');
      // Объявление начальных данных
      table.hasData([{ id: 0, name: 'администратор' }]);
    });
});
```


#### Ассоциации

В этом разделе будут рассмотрены примеры сохранения ассоциаций. Для лучшего понимания рекомендуется прочитать [сохранение данных сущностей](#сохранение-сущностей-данных), прежде чем приступить к чтению данного раздела.


##### Один ко одному (основной)

Допустим, что у нас есть две сущности: `User`, `Employee`. Внешний ключ `Employee.userId` ссылается на `User.id`. Объявление сущности `User` выглядит следующим образом:
**Явное объявление навигационного свойства**

Если сущность `Employee` уже имеет объявленное свойство `user`, то можно объявить его следующим образом.

```ts
// # entities/user.ts
// ... остальные импорты
import { Employee } './employee'

@comment('Пользователь')   // Объявление комментария, который будет записан в расширенные атрибуты/комментарии базы данных.
@Table()                   // Объявление сущности как таблицы
@context(() => DB)         // Привязка сущности к контексту DbContext
@Data([                    // Объявление начальных данных, которые будут автоматически инициализированы при выполнении миграций данных.
    { id: 1, name: 'администратор', password: '123456' }
])
export class User extends Entity implements EntityKey {
    // Объявление как столбец
	@Column()
    // Объявление как первичный ключ
  	@Key()
    // Объявление как автоинкрементируемый столбец
    @Identity()
    @Comment('ID')
    id?: bigint;
```

## Явное объявление внешнего ключа и навигационного свойства

```typescript
// # entities/employee.ts
// Другие импорты...
import { User } from './user';

@table()
@comment('Сотрудник')
@context(() => DB)
export class Employee extends Entity implements EntityKey {
  @column()
  @key()
  @comment('EmployeeID')
  @identity()
  id?: bigint;

  // ... другие свойства

  @foreign(() => User, u => u.employee)
  @navigation(User)
  user?: User;
}
```

### Неявное объявление навигационного свойства

Предположим, что сущность `Employee` не объявила свойство `user`, следующий пример автоматически объявляет неявное **один-к-одному (от)** навигационное свойство `user`.```typescript
// # entities/user.ts
// ... другие импорты
import { Employee } from './employee';

@comment('Пользователь')   // Объявление аннотации, которая будет включена в расширенные атрибуты/аннотации базы данных.
@Table()                   // Объявление таблицы.
@context(() => DB)         // Привязка сущности к контексту DbContext.
@Data([                    // Объявление семенного набора данных, который будет автоматически инициализирован при выполнении миграций данных.
    { id: 1, name: 'администратор', пароль: '123456' }
])
export class User extends Entity implements EntityKey {
    // Объявление колонки
    @column()
    // Объявление первичного ключа
    @key()
    // Объявление автоинкрементирующегося поля
    @identity()
    @comment('ID')
    id?: bigint;

    // ... другие свойства

    @principal()  // Объявление этого свойства как основного одно-к-одному отношения.
    @oneToOne(() => Employee) // Объявление одно-к-одному отношения.
    employee?: Employee;
}
```

### Одно-к-одному отношение

Одно-к-одному отношение также может объявлять навигационное свойство как детальное свойство. После объявления как детального свойства...

#### Получение связанных свойств

```typescript
const user = await userRepo.get(1, { includes: { employee: true } });
// => { id: 1, ..., employee: { userId: 1, ... }}
```

#### Создание и сохранение связанных свойств одновременно

```typescript
const user = User.create({
    name: 'администратор',
    // ...
    employee: {
        name: 'Администратор',
        description: 'Связь создана'
    }
});
await userRepo.insert(user);
// Сохраняет пользователя и сотрудника одновременно
```

### Исправленные строки:

- `пароль` -> пароль
- `администратор` -> администратор
- `Связь создана` -> Связь создана#### Удаление связи

Для удаления связи необходимо выполнить операцию над одно-к-одным (от) сущностью. Конкретные правила сохранения см. в разделе [сохранение данных сущностей (save)](#сохранение-данных-сущностей-save).

##### Одно-к-одному отношение (от)

Продолжим наш предыдущий пример, создаем файл сущности `entities/employee.ts`, **одно-к-одному отношению (от)**.

**Явное объявление внешнего ключа и навигационного свойства**

```typescript
// # entities/employee.ts
// Другие импорты...
import { User } from './user';

@table()
@comment('Сотрудник')
@context(() => DB)
export class Employee extends Entity implements EntityKey {
  @column()
  @key()
  @comment('EmployeeID')
  @identity()
  id?: bigint;

  // ... другие свойства

  @foreign(() => User, u => u.employee)
  @navigation(User)
  user?: User;
}
```

## Неявное объявление внешнего ключа и навигационного свойства

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

```typescript
// # entities/user.ts
// Другие импорты...
import { Employee } from './employee';

@Table()
@Comment('Пользователь')
@Context(() => DB)
export class User extends Entity implements EntityKey {
  @Column()
  @Key()
  @Identity()
  @Comment('UserID')
  id?: bigint;

  // Другие свойства...

  // Объявление одно-к-одного отношения (отношение "одно к одному")
  @OneToOne(() => Employee, p => p.user)
  @ForeignKey(() => Employee, 'userId')  // Объявление внешнего ключа
  employee?: Employee | null;  // Если userId является nullable, следует указать возможность null, чтобы отключить связь
}
```

### Неявное объявление внешнего ключа и навигационного свойстваВнешний ключ может быть создан автоматически моделью без явной декларации, например:

```typescript
// # entities/сотрудник.ts
// Другие импорты...
import { Пользователь } from './пользователь';

@Table()
@Comment('Сотрудник')
@Контекст(() => БД)
экспортировать класс Сотрудник расширяет Энтити реализует ЭнтитиКлюч {
  @Столбец()
  @Ключ()
  @Comment('ID сотрудника')
  @Идентификатор()
  id?: bigint;

  // Другие свойства...

  // Объявление однозначного отношения (отношение "один ко одному" - отношение "деталь")
  @ForeignКлюч()  // Объявление внешнего ключа
  @OneToOne(() => Пользователь)  // Объявление однозначного отношения
  пользователь?: Пользователь | null;  // Если orderId является nullable, следует указать возможность null, чтобы отключить связь
}
```
Предположим, что в сущности `Пользователь` нет объявленного навигационного свойства `сотрудник`, то модель будет автоматически создана это скрытое навигационное свойство (один ко одному - основное) `сотрудник`. В то же время модель также создаст внешний ключ `userId` в сущности `Сотрудник`.

В отношении "один ко одному", правила создания скрытого навигационного свойства следуют ниже:

#### Одновременная вставка данных в однозначное отношение (основное)

```typescript
const сотрудник = Сотрудник.create({
    имя: 'Администратор',
    // ...
    пользователь: {
        имя: 'админ',
        пароль: '123456'
    }
});
await сотрудникRepo.save(сотрудник);
// Одновременно вставлены данные для Сотрудник и Пользователь
```

#### Удаление связанного отношения

```typescript
const сотрудник = await сотрудникRepo.get(1);
сотрудник.пользователь = null;
await сотрудникRepo.save(сотрудник);
```

### Один ко многим отношениеОтношение один ко многим связано с отношением много ко одному.

#### Явное объявление навигационного свойства

Предположим, что сущность `OrderDetail` уже имеет объявленное отношение много ко одному (`order`). Мы можем использовать следующий способ для его связи.

```typescript
// Другие импорты...
import { OrderDetail } from './order-detail';

/**
 * Заказ
 */
@Table()
@Context(() => DB)
@Comment('Заказ')
export class Order extends Entity implements EntityKey {
   @Column()
   @Comment('ID')
   @Key()
   @Identity()
   id?: bigint;

   // ...

   @OneToMany(() => OrderDetail, p => p.order)
   details?: OrderDetail[];
}
```

```## Неявное объявление навигационного свойства

Предположим, что сущность `OrderDetail` не объявила много ко одному навигационное свойство `order`. Моделировщик автоматически создаст неявное много ко одному навигационное свойство `order`.

```ts
// Другие импорты...
import { OrderDetail } from './order-detail'

/**
 * Заказ
 */
@Table()
@Context(() => DB)
@Comment('Заказ')
export class Order extends Entity implements EntityKey {
   @Column()
   @Comment('ID')
   @Key()
   @Identity()
   id?: bigint;

   // ...

   @OneToMany(() => OrderDetail)
   details?: OrderDetail[];
}
```

### Вставка данных в одно ко многим отношение

```ts
const order = Order.create({
    orderNo: '202101010001',
    // ...
    details: [
        {
            product: 'Карандаш',
            count: 1,
            price: new Decimal(0.56),
            // ...
        },
        {
            product: 'Ручка',
            count: 1,
            price: new Decimal(10.65),
            // ...
        },
        {
            product: 'Блокнот',
            count: 1,
            price: new Decimal(3.5)
        }
    ]
});

await orderRepo.insert(order);
```### Добавление и удаление деталей заказа

Следующий код удалит запись `OrderDetail`, где продукт  карандаш, и добавит новую запись, где продукт  ручка.

```ts
const order = await orderRepo.get(1, { includes: { detail: true } });
order.details.splice(0, 1); // Удаление первого элемента, то есть карандаша
order.details.push(OrderDetail.create({
    product: 'Ручка',
    count: 1,
    price: new Decimal(1.2),
    // ...
}));

await orderRepo.save(order);
```


#### Одно ко многим отношение

##### Явное объявление навигационного свойства

Предположим, что сущность `Order` уже определяет одно ко многим навигационное свойство `details`. Мы можем использовать следующий способ для его ассоциации.

```ts
// ...
import { Order } from './order'

/**
 * OrderDetail
 */
@table()
@context(() => DB)
@comment('OrderDetail')
export class OrderDetail extends Entity implements EntityKey {
    @column()
    @comment('ИД')
    @identity()
    @key()
    id?: bigint;

    // ...

    @comment('ИД заказа')
    @column()
    orderId?: bigint;

    @foreignKey('orderId') // Указание внешнего ключа
    @ManyToOne(() => Order, p => p.details)
    order?: Order | null; // Если поле orderId может быть пустым, следует указать возможность значения null для отключения связи
}
```

##### Отключение связи

Следующий код может отключить связь:

```ts
const orderDetail = await orderDetailRepo.get(1);
orderDetail.order = null;
await orderDetailRepo.save(orderDetail);

// => Обновляет orderDetail.orderId значением DBNULL
```


**Неявное объявление навигационных свойств и внешних ключей**Предположим, что сущность `Order` не имеет определённого однозначного отношения один ко многим. Следующий код автоматически создаст неявное навигационное свойство **`orderDetail`**, а также автоматически создаст неявный внешний ключ `orderId` для сущности `Order`.```ts
// ...
import { Order } from './order'

/**
 * OrderDetail
 */
 @table()
 @context(() => DB)
 @comment('OrderDetail')
 export class OrderDetail extends Entity implements EntityKey {
   @column()
   @comment('ID')
   @identity()
   @key()
   id?: bigint;

   // ...

   @comment('OrderId')
   @column()
   orderId?: bigint;

   @manyToOne(() => Order)
   order?: Order;
 }
```



##### Один ко многим отношение

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

**Явное объявление навигационных свойств и промежуточной сущности связи**

Сотрудник: entities/employee.ts

```ts
// ...
import { Position } from './position'

@table()
@comment('Employee')
@context(() => DB)
export class Employee extends Entity implements EntityKey {
  @column()
  @key()
  @comment('EmployeeID')
  @identity()
  id?: bigint;

  // ...

  @manyToMany(() => Position, p => p.employees) // Навигационное свойство в обратном направлении
  positions?: Position[];

  @oneToMany(() => EmployeePosition, p => p.employee)
  employeePositions?: EmployeePosition[];
}
```

Должность: entities/position.ts

```ts
import { Employee } from './employee'

@table()
@comment('Position')
@context(() => DB)
export class Position extends Entity implements EntityKey {
  @column()
  @comment('PositionID')
  @identity()
  @key()
  id?: bigint;

  // ...

  @manyToMany(() => Employee, p => p.positions)   // Навигационное свойство в обратном направлении
  employees?: Employee[];

  @oneToMany(() => EmployeePosition, p => p.position)
  employeePositions?: EmployeePosition[];
}
```

Промежуточная сущность связи: entities/employee-position.ts```ts
// ...
import { Position } from './position'
import { Employee } from './employee'
``````markdown
@table()
@context(() => БД)
// @among(() => Позиция, () => Сотрудник, 'position', 'employee')
@among<EmployeePosition, Позиция, Сотрудник>(() => Позиция, () => Сотрудник, п => п.position, п => п.employee)
экспортировать класс EmployeePosition расширяет EntityEngineKey реализует EntityKey {
  @column()
  @comment('ID')
  @key()
  @identity()
  id?: bigint;
}
```  
@comment('PositionID')
@column()
positionId!: bigint;
```  @foreignKey('positionId')
  @manyToOne(() => Position, p => p.employeePositions) # Определение навигационной свойства, связанной с одной стороной таблицы
  position?: Position;

  @column()
  @comment('EmployeeID')
  employeeId!: bigint;

  # Определение навигационной свойства, связанной с внешним ключом таблицы
  @foreignKey('employeeId')
  @manyToOne(() => Employee, p => p.employeePositions) # Определение навигационной свойства, связанной с другой стороной таблицы
  employee?: Employee;
}
```

**Неявное объявление навигационных свойств и промежуточного отношения**

В следующем примере модель автоматически создает следующие элементы:- Навигационное свойство `employees` типа многие ко многим для класса сущностей `Position`.
- Навигационное свойство `employeePositions` типа один ко многим для класса сущностей `Position`, связанное с неявным классом сущностей `EmployeePosition`.
- Навигационное свойство `employeePositions` типа один ко многим для класса сущностей `Employee`, связанное с неявным классом сущностей `EmployeePosition`.
- Автоматическое создание неявного промежуточного класса сущностей `EmployeePosition`. Его имя составлено путём соединения двух имен связанных сущностей в алфавитном порядке. Если уже существует сущность с таким же именем, она будет использована как промежуточная сущность. Структура этого промежуточного класса сущностей аналогична классу `entities/employee-position.ts` и имеет следующие свойства:
  - Первичный ключ, созданный согласно глобальной конфигурации первичного ключа.
 - Навигационное свойство `position` типа много ко одному, связанное с сущностью `Position`.
 - Внешний ключ `positionId`, указывающий на `Position.id`.
 - Навигационное свойство `employee` типа много ко одному, связанное с сущностью `Employee`.
 - Внешний ключ `employeeId`, указывающий на `Employee.id`.Сотрудник: entities/employee.ts

```ts
// ...
import { Position } from './position'

@table()
@comment('Сотрудник')
@context(() => DB)
export class Employee extends Entity implements EntityKey {
  @column()
  @key()
  @comment('ID сотрудника')
  @identity()
  id?: bigint;

  // ...

  @manyToMany(() => Position) # Определение навигационного свойства, связанного с противоположной стороной
  positions?: Position[];
}
```

Позиция: entities/position.ts

```ts
import { Employee } from './employee'

@table()
@comment('Должность')
@context(() => DB)
export class Position extends Entity implements EntityKey {
  @column()
  @comment('ID должности')
  @identity()
  @key()
  id?: bigint;
}
```  // ...
}
```



**Добавление данных в много ко многим отношение**

```ts
const сотрудник = Сотрудник.create({
    имя: 'Пётр',
    // ...
    должности: [{
        имя: 'Менеджер отдела продаж (в совмещении)',
        // ...
    }, {
        имя: 'Заместитель директора',
        // ...
    }]
})

await сотрудникРепозиторий.save(сотрудник);
```
Вышеуказанный код последовательно вставляет следующие данные:

- Сотрудник, Пётр, предположительно id=1
- Таблица Должность, менеджер отдела продаж (в совмещении) [предположительно id=1] и заместитель директора [предположительно id=2]
- Таблица СотрудникДолжность [{ сотрудникаId: 1, должностиId: 1 }, { сотрудникаId: 1, должностиId: 2 }]

**Удаление связей**

```ts
const сотрудник = сотрудникРепозиторий.get(1);
сотрудник.должности = []; // Также можно установить равным null
await сотрудникРепозиторий.save(сотрудник);
```
Приведённый выше код удалит все записи с employeeId=1 из таблицы СотрудникДолжность.#### Создание файла контекста базы данных

Файл контекста базы данных `db.ts`

```ts
import { КонтекстБазыДанных, Репозиторий, репозиторий } from 'lubejs';
import { Пользователь } from './entity/пользователь';
import { Сотрудник } from './entity/сотрудник';

export class БД extends КонтекстБазыДанных {
  	@Repository(() => Пользователь) // Объявление атрибута репозитория, который позволяет получить доступ к репозиторию непосредственно через этот атрибут, при этом он создаётся лениво, то есть только при обращении к нему.
  	пользователи: Репозиторий<Пользователь>;

    @Repository(() => Сотрудник)
    сотрудники: Репозиторий<Сотрудник>;
}
```



### Операция над данными сущностей

Операции над данными подробно описаны в объектах репозитория и объектах запроса, поэтому здесь повторяться не будем.

- [Контекст базы данных](#контекст-базы-данных(dbcontext))
- [Объект запроса (queriable)](#объект-запроса(queriable))
- [Объект репозитория (repository)](#объект-репозитория(repository))
- [Отношения](#отношения)

### Использование конфигураций

Пожалуйста, обратитесь к [конфигурационному файлу](#конфигурационный-файл)

### Полный пример

- Объявление декоратора: [ORM](https://github.com/jovercao/lubejs-tester/blob/master/orm-decorator/index.ts)

- Объявление кода конфигурации: [ORM](https://github.com/jovercao/lubejs-tester/blob/master/orm-configure.ts)

### Использование репозитория- [Вставка](https://github.com/jovercao/lubejs-tester/blob/master/tests/repository/insert.test.ts)
- [Обновление](https://github.com/jovercao/lubejs-tester/blob/master/tests/repository/update.test.ts)
- [Удаление](https://github.com/jovercao/lubejs-tester/blob/master/tests/repository/delete.test.ts)

## Данная миграцияДля использования функции данных миграций требуется использование командной строки (CLI), которая входит в состав пакета lubejs. После установки lubejs мы получаем доступ к команде `lube`, с помощью которой можно выполнять операции по миграции данных.

### Создание конфигурационного файла

Перед использованием данных миграций необходимо создать конфигурационный файл `.lubejs.ts` или `.lubejs.js`. Без этого файла невозможно запустить инструмент миграции. Подробнее о создании конфигурационного файла см. раздел [Конфигурационный файл](#конфигурационный-файл).

### Создание файлов миграции

```shell
lube migrate add [name]
```

Эта команда создаёт файл с описанием структуры таблицы в виде `./migrates/<yyyyMMddHHmmss>_Init.ts`. Вместе с ним также создаётся файл с суффиксом `.snapshot.ts`.

Пример:

```sh
# Создание файла миграции с именем Init.
lube migrate add Init
```

Если после этого структура таблиц не была изменена, повторное выполнение следующей команды:

```ts
lube migrate add AddOrderModule
```

Результатом будет пустой файл `./migrates/<yyyyMMddHHmmss>_AddOrderModule.ts`.

### Ручное создание файлов миграции

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

```ts
import { Migrate, SQL, DbType, MigrateBuilder } from 'lubejs';
``````typescript
export class Init implements Migrate {
  async up(
    builder: MigrateBuilder, // Object for building migration code
    dialect: string // Database dialect used during execution
  ): Promise<void> {
    // Here you should add the code to apply the migration
  }

  async down(
    builder: MigrateBuilder,
    dialect: string
  ): Promise<void> {
    // Here you should add the code to rollback the migration
  }
}

export default Init;
```

All migration files must be created using the `MigrateBuilder` class as shown above. This object is used similarly to the `SQL` object but provides more capabilities for working with migrations rather than other types of data operations.

It's important to note that using the method `builder.sql(...)` to create migration code can lead to issues because changes in structure created this way may not be accounted for by the system when creating snapshots. This could cause errors on subsequent attempts to create migration files.

### Updating the database to a migration version

```shell
lube migrate update [name]
```

This command updates the database to the migration version named `[name]`. If the current database version is newer than the specified one, it will be rolled back to that version.

If the parameter `[name]` is not provided, it means using the latest migration version.


### Synchronizing the database

```ts
lube migrate sync
```Эта команда отличается от `update`: она анализирует текущую структуру данных объектов и обновляет структуру базы данных до соответствия этим объектам, но не выполняет код внутри файлов миграций. Обычно используется для быстрого создания тестовой среды базы данных; не рекомендуется использовать эту команду в продакшне.


### Экспорт скриптов обновления/понижения

```ts
lube migrate script --source <source_name> --target <target_name> --output <output_file>
```

Эта команда генерирует SQL-код для файла миграции, где `<source_name>` — имя файла источника миграции, `<target_name>` — имя файла целевой миграции, а результат экспортируется в файл `<output_file>`.


Дополнительная информация доступна через `lube --help`.


## Другие вопросы


### Проблемы сериализации JSON

Обычно при использовании JavaScript мы используем объект `JSON` для сериализации (например, когда отправляем данные клиенту), однако некоторые типы, такие как `BigInt`, не поддерживаются сериализацией, что приводит к ошибкам. В Lubejs специальные скалярные типы `Scalar` сериализуются следующим образом:| Тип      | Описание                                                   | Конкретная ситуация                                                                                           |
| -------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Binary   | псевдоним типа TypeScript, фактически является `Buffer`, `ArrayBuffer` или `TypedArrayBuffer`          | Чтобы не загрязнять native объекты, lubejs не меняет поведение сериализации:<br>- Если значение имеет тип Buffer, вызывается `Buffer.prototype.toString()`, что может привести к появлению мусора.<br>- Если значение имеет тип ArrayBuffer, возвращается `{}`. |
| Uuid     | тип UUID                                                   | Реализует `.toJSON`, возвращает строку в формате `"00000000-0000-0000-0000-000000000000"`.                    |
| BigInt   | встроенный тип V8                                          | При попытке сериализации возникнет ошибка.                                                                    |
| Decimal  | основан на библиотеке `decimal.js-light`                   | Реализует `.toJSON`, сериализуется в строку, пример: `"100"`.                                               | Рекомендованное решение следующее: |1. Пользовательская сериализация

   ```ts
   JSON.stringify({ bigint: 1n }, (key, value) => {
       if (typeof value === 'bigint') {
           return value.toString();
       } else {
           return value;
       }
   });
   ```

2. Реализация метода `.toJSON`

   ```ts
   BigInt.prototype.toJSON = function() { return this.toString() }

   JSON.stringify(1n); // => '"1"'
   ```

При десериализации также следует учитывать тип.### Различия между базами данных| Функционал                  | MSSQL                                                       | MySQL                                                       | PostgreSQL                                                   |
 | ---------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | Поддержка функций           | Да                                                         | Да                                                          | Да                                                           |
 | Поддержка хранимых процедур  | Да                                                         | Да                                                          | Нет поддержки объявления, можно использовать только функции |
 | Вызов хранимой процедуры    | EXECUTE <sp_name>                                          | CALL <sp_name>                                              | SELECT <fn_name>(...)                                          |
 | Возврат множественного набора функцией | Нет поддержки, возвращаются только одиночные наборы       | Нет поддержки, возвращаются только одиночные наборы       | Да                                                           |
 | Возврат множественного набора хранимыми процедурами | Да                                                         | Да                                                          | Через выходные параметры                                      |
 | Транзакционность хранимых процедур | Явная открытие                                            | Автоматическая открытие                                     | Автоматическая открытие                                        || Предварительно скомпилированные запросы | Ключевое слово PREPARE                                      | Через хранимую процедуру: sp_prepare, sp_execute, sp_unprepare, ключевое слово PREPARE | Ключевое слово PREPARE                                      |
 | Поддержка выполнения нескольких команд | Поддерживается, **поддерживаются объявление переменных, присваивание значений и операции**, а также возврат множественного набора    | Поддерживается, но требуется открытие опции multiStatements и использование разделителя (обычно ";", может быть настроен); **поддерживаются только операции присваивания глобальных переменных**. Поддерживается возврат множественного набора | Поддерживается, но требуется использование разделителя ";" между командами | **Поддержка объявления и присваивания значений переменных недоступна**, поддерживается возврат множества строк с использованием блока BEGIN...END | Поддерживается использование блока BEGIN...ENDEND, который **может использоваться только внутри хранимых процедур и не связан с транзакциями**, может вернуть множество строк; эту функциональность можно достигнуть путём создания временной хранимой процедуры | В блоке DO можно выполнить операцию в любом месте кода, но она будет автоматически рассматриваться как атомарная транзакция, и не поддерживает передачу параметров и возврат множества строк; путь через переменные заблокирован, поэтому **можно использовать временные таблицы уровня транзакций для реализации этой функции**
 | Объявление локальных переменных | Может выполняться в любом месте кода, локальные переменные | Доступно только внутри хранимых процедур/функций | Доступно только внутри блока DO, хранимых процедур и функций |
 | Глобальные переменные | Только системные глобальные переменные, доступны в любом месте кода | Поддерживаются глобальные переменные (уровня сессии — соединение, глобального — всей базы данных), доступны в любом месте кода | Только системные глобальные переменные, создание пользовательских глобальных переменных недоступно |
 | Передача параметров prepare | Через имя переменной (EXEC SP_EXECUTE), поддерживаются выходные параметры | ?Заполнители, выходные параметры не поддерживаются, могут быть достигнуты через глобальные переменные или `SELECT` | `$n` заполнители, выходные параметры не поддерживаются, могут быть достигнуты только через `SELECT` |
| Передача параметров функций/хранимых процедур | Выходные параметры могут быть переданы через ссылку, выводятся в переменные | Выходные параметры могут быть переданы через ссылку, выводятся в переменные<br>Глобальные переменные могут быть возвращены (уровня сессии, глобального) | Поддерживается только передача значений, через `SELECT` возвращаются |
| Смена базы данных | `USE <db_name>` | `USE <db_name>` | Недоступно, но можно использовать схемы вместо этого |
| Открытие транзакции | `BEGIN TRANSACTION...COMMIT...ROLLBACK` | `START TRANSACTION...COMMIT...ROLLBACK` | `BEGIN...END;`<br>`START BEGIN...COMMIT...ROLLBACK` | **реализация вывода параметров в PostgreSQL:** |```plsql
begin;
do $$ declare a int = 1;
begin
  a := 200;
  create local temporary table xyz on commit drop as select a;
end$$;
select * from xyz;
end;
```

## API

[Документация API](./doc/globals.md)

## Задачи

- [ ] Поддержка драйвера MySQL
- [ ] Поддержка драйвера PostgreSQL
- [ ] Поддержка драйвера SQLite
- [x] Улучшение охвата тестов до 85%
- [ ] Оптимизация производительности
  - [x] Преобразование множественных запросов между главной базой данных и репликами в одиночный запрос
  - [ ] Оптимизация производительности операций вставки, удаления, выборки и изменения, сокращение компиляции SQL

## Обновленные логи

### 3.0.0-preview06

- Исправление ошибок

### 3.0.0-preview05

- Преобразование множественных запросов между главной базой данных и репликами в одиночный запрос
- Добавление сериализации UUID
- Отключение кэширования запросов
- Исправление ошибок

### 3.0.0-preview04

- Исправление некоторых ошибок
- Изменение `Repository.prototype.get` так, чтобы выбрасывалось исключение при отсутствии ключа
- Создание первоначальной версии документации
- Увеличение охвата тестов

### 3.0.0-preview01

Первоначальная версия для просмотра

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Lubejs — это инструмент для удобного использования SQL-соединений с базой данных в node.js. Развернуть Свернуть
Apache-2.0
Отмена

Обновления (3)

все

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/jovercao-lubejs.git
git@api.gitlife.ru:oschina-mirror/jovercao-lubejs.git
oschina-mirror
jovercao-lubejs
jovercao-lubejs
master