ECOS Data
ecos-data - это библиотека, которая позволяет добавлять новые Records DAO, записи которых будут храниться в БД. Библиотека на основе атрибутов в ECOS типе автоматически создает таблицы и предоставляет базовые операции над записями:
Создание новых записей;
Получение атрибутов у записей по их ID;
Поиск записей с использованием языка предикатов;
Поддерживаемые БД:
Тип |
Версия |
PostgreSQL |
9.4+ |
Хранение данных в ecos-model
В ECOS по умолчанию для типов предусмотрено хранение в микросервисе ecos-model. Для этого достаточно на форме типа выбрать тип источника данных «ECOS Model». Микросервис ecos-model для всех подобных типов автоматически создаст RecordsDAO с идентификатором (sourceId), который формируется по следующим правилам:
Берем id типа и меняем camelCase на kebab-case;
Переводим id в нижний регистр;
Заменяем все специальные символы на
-
;Убираем все задублировавшиеся символы
-
заменяя их на одинарные (т.е.--
заменяем на-
);Если длина полученного id получилась больше 42х символов, то берем первые 34 символа из id, добавляем
-
плюс контрольная сумма оставшихся символов в идентификаторе;Убираем символ
-
в начале идентификатора и в конце если он там есть
Примеры:
camelCaseTest -> camel-case-test
kebab-case-id -> kebab-case-id
a----b- -> a-b
$$$-abc-$$$ -> abc
При создании типа с sourceId, который конфликтует с существующими будет показана ошибка. Чтобы проверить список зарегистрированных sourceId можно выполнить следующий скрипт в консоли браузера:
await Citeck.Records.query({sourceId: 'emodel/src', query: {}, language:'predicate'}, '?localId')
Создание нового RecordsDAO в своем микросервисе
Для создания нового RecordsDAO в своем микросервисе нужно добавить следующую Spring конфигурацию
Java:
import kotlin.Unit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.citeck.ecos.data.sql.domain.DbDomainConfig;
import ru.citeck.ecos.data.sql.domain.DbDomainFactory;
import ru.citeck.ecos.data.sql.dto.DbTableRef;
import ru.citeck.ecos.data.sql.records.DbRecordsDaoConfig;
import ru.citeck.ecos.data.sql.service.DbDataServiceConfig;
import ru.citeck.ecos.model.lib.utils.ModelUtils;
import ru.citeck.ecos.webapp.api.entity.EntityRef;
import ru.citeck.ecos.records3.record.dao.RecordsDao;
@Configuration
public class CustomRecordsDaoConfig {
private DbDomainFactory dbDomainFactory;
@Bean
public RecordsDao customRecordsDao() {
EntityRef typeRef = ModelUtils.getTypeRef("ecos-type-id");
return dbDomainFactory.create(
DbDomainConfig.create()
.withRecordsDao(
DbRecordsDaoConfig.create(b -> {
b.withId("records-dao-id");
b.withTypeRef(typeRef);
return Unit.INSTANCE;
})
)
.withDataService(
DbDataServiceConfig.create(b -> {
b.withTable("table_name");
b.withStoreTableMeta(true);
return Unit.INSTANCE;
})
)
.build()
).withSchema("schema_name").build();
}
@Autowired
public void setDbDomainFactory(DbDomainFactory dbDomainFactory) {
this.dbDomainFactory = dbDomainFactory;
}
}
Kotlin:
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import ru.citeck.ecos.data.sql.domain.DbDomainConfig
import ru.citeck.ecos.data.sql.domain.DbDomainFactory
import ru.citeck.ecos.data.sql.dto.DbTableRef
import ru.citeck.ecos.data.sql.records.DbRecordsDaoConfig
import ru.citeck.ecos.data.sql.service.DbDataServiceConfig
import ru.citeck.ecos.model.lib.utils.ModelUtils
import ru.citeck.ecos.records3.record.dao.RecordsDao
@Configuration
class CustomRecordsDaoConfig(
private val dbDomainFactory: DbDomainFactory
) {
@Bean
fun customRecordsDao(): RecordsDao {
val typeRef = ModelUtils.getTypeRef("ecos-type-id")
val recordsDao = dbDomainFactory.create(
DbDomainConfig.create()
.withRecordsDao(
DbRecordsDaoConfig.create {
withId("records-dao-id")
withTypeRef(typeRef)
}
)
.withDataService(
DbDataServiceConfig.create {
withTable("table_name")
withStoreTableMeta(true)
}
)
.build()
).withSchema("schema_name").build()
return recordsDao
}
}
Поиск по полям ассоциаций
Если источники данных на базе ecos-data находятся в одном микросервисе, в одной БД и одной схеме, то можно выполнять поиск с использованием полей из связанных сущностей. Для этого у атрибута-ассоциации должен быть указан тип, в котором sourceId ссылается на ecos-data источник в той же схеме.
Для поиска по полям из связанных сущностей в предикате нужно указать атрибут в формате {имя ассоциации}.{имя поля из связанной сущности}
Например, если мы хотим найти договоры, у которых имя контрагента содержит строку «ООО», то это можно сделать используя следующий скрипт:
await Records.query({
ecosType: 'ecos-contract',
language: 'predicate',
query: {
t: 'contains',
a: 'counterparty.fullOrganizationName',
v: 'ООО'
}
});
здесь counterparty - это ассоциация на контрагента, а fullOrganizationName - его имя.
Запросы с использованием функций
Если атрибут заканчивается на круглые скобки с любым содержимым (например max(attribute)
или count(*)
, то этот атрибут воспринимается
как функция и транслируется в SQL запрос (если речь о SQL бэкенде).
Функции можно указывать в условиях поиска, для группировки, для сортировки и для получения значения.
Пример запроса:
await Records.query(
{
ecosType: 'ecos-contract',
language: 'predicate',
query: {
t: 'contains',
a: 'counterparty',
v: 'emodel/counterparty@some-counterparty-id'
}
},
{
amount: 'amount?num',
poweredAmount: 'power(amount,2)?num' // функция power
}
);
Список поддерживаемых функций:
Числовые функции
Функция |
Описание |
---|---|
abs ( number ) → number |
Абсолютное значение
abs(-17.4) → 17.4 |
ceil ( number ) → number |
Ближайшее целое, большее или равное аргументу
ceil(42.2) → 43 ceil(-42.8) → -42 |
ceiling ( number ) → number |
Ближайшее целое, большее или равное аргументу (равнозначно ceil)
ceiling(95.3) → 96 |
div ( y number, x number ) → number |
Целочисленный результат y/x (округлённый в направлении нуля)
div(9, 4) → 2 |
exp ( number ) → number |
Экспонента (e возводится в заданную степень)
exp(1.0) → 2.7182818284590452 |
floor ( number ) → number |
Ближайшее целое, меньшее или равное аргументу
floor(42.8) → 42 floor(-42.8) → -43 |
mod ( y number, x number ) → number |
Остаток от деления y/x
mod(9, 4) → 1 |
power ( a number, b number ) → number |
a возводится в степень b
power(9, 3) → 729 |
round ( number ) → numeric |
Округляет до ближайшего целого числа. Для numeric половина (.5) округляется до одного по модулю.
round(42.4) → 42 |
round ( v number, s number ) → numeric |
Округление v до s десятичных знаков. Половина (.5) округляется до 1 по модулю.
round(42.4382, 2) → 42.44 round(1234.56, -1) → 1230 |
sign ( number ) → number |
Знак аргумента (-1, 0 или +1)
sign(-8.4) → -1 |
sqrt ( number ) → number |
Квадратный корень
sqrt(2) → 1.4142135623730951 |
trunc ( number ) → number |
Округление до целого (в направлении нуля)
trunc(42.8) → 42 trunc(-42.8) → -42 |
Строковые функции
Функция |
Описание |
---|---|
btrim ( string text [, characters text] ) → text |
Удаляет наибольшую подстроку, содержащую только символы characters (по умолчанию пробел),
с начала и с конца строки string.
btrim('xyxtrimyyx', 'xyz') → trim |
length ( text ) → integer |
Возвращает число символов в строке.
char_length('josé') → 4 |
initcap ( text ) → text |
Переводит первую букву каждого слова в строке в верхний регистр, а остальные — в нижний.
Словами считаются последовательности алфавитно-цифровых символов, разделённые любыми другими символами.
initcap('hi THOMAS') → Hi Thomas |
lpad ( string text, length integer [, fill text] ) → text |
Дополняет строку string слева до длины length символами fill (по умолчанию пробелами).
Если длина строки уже больше заданной, она обрезается справа.
lpad('hi', 5, 'xy') → xyxhi |
ltrim ( string text [, characters text] ) → text |
Удаляет наибольшую подстроку, содержащую только символы characters (по умолчанию пробелы),
с начала строки string.
ltrim('zzzytest', 'xyz') → test |
repeat ( string text, number integer ) → text |
Повторяет содержимое string указанное число (number) раз.
repeat('Pg', 4) → PgPgPgPg |
replace ( string text, from text, to text ) → text |
Заменяет все вхождения в string подстроки from подстрокой to.
replace('abcdefabcdef', 'cd', 'XX') → abXXefabXXef |
rpad ( string text, length integer [, fill text] ) → text |
Дополняет строку string справа до длины length символами fill (по умолчанию пробелами). Если длина строки уже больше заданной, она обрезается.
rpad('hi', 5, 'xy') → hixyx |
rtrim ( string text [, characters text] ) → text |
Удаляет наибольшую подстроку, содержащую только символы characters (по умолчанию пробелы), с конца строки string.
rtrim('testxxzx', 'xyz') → test |
strpos ( string text, substring text ) → integer |
Возвращает начальную позицию первого вхождения substring в строке string либо 0, если такого вхождения нет.
|
upper ( text ) → text |
Переводит символы строки в верхний регистр, в соответствии с правилами локали базы данных.
upper('tom') → TOM |
lower ( text ) → text |
Переводит символы строки в нижний регистр в соответствии с правилами локали базы данных.
lower('TOM') → tom |
Функции форматирования данных
Функция |
Описание |
---|---|
to_char ( timestamp, text ) → text to_char ( timestamp with time zone, text ) → text |
Преобразует время в строку согласно заданному формату.
to_char(timestamp '2002-04-20 17:31:12.66', 'HH12:MI:SS') → 05:31:12 |
to_char ( interval, text ) → text |
Преобразует интервал в строку согласно заданному формату.
to_char(interval '15h 2m 12s', 'HH24:MI:SS') → 15:02:12 |
to_char ( numeric_type, text ) → text |
Преобразует число в строку согласно заданному формату; поддерживаются типы integer, bigint, numeric, real, double precision.
to_char(125, '999') → 125 to_char(125.8::real, '999D9') → 125.8 to_char(-125.8, '999D99S') → 125.80- |
to_date ( text, text ) → date |
Преобразует строку в дату согласно заданному формату.
to_date('05 Dec 2000', 'DD Mon YYYY') → 2000-12-05 |
to_number ( text, text ) → numeric |
Преобразует строку в число согласно заданному формату.
to_number('12,454.8-', '99G999D9S') → -12454.8 |
to_timestamp ( text, text ) → timestamp with time zone |
Преобразует строку в значение времени согласно заданному формату.
to_timestamp('05 Dec 2000', 'DD Mon YYYY') → 2000-12-05 00:00:00-05 |
Функции даты/времени
Функция |
Описание |
---|---|
age ( timestamp, timestamp ) → interval |
Вычитает аргументы и выдаёт «символический» результат с годами и месяцами, а не просто днями
age(timestamp '2001-04-10', timestamp '1957-06-13') → 43 years 9 mons 27 days (43 года 9 месяцев 27 дней) |
age ( timestamp ) → interval |
Вычитает аргумент из current_date (полночь текущего дня)
age(timestamp '1957-06-13') → 62 years 6 mons 10 days (62 года 6 месяцев 10 дней) |
current_date → date |
Текущая дата
current_date → 2023-12-23 |
current_time → time with time zone |
Текущее время суток
current_time → 14:39:53.662522-05 |
current_time ( integer ) → time with time zone |
Текущее время суток (с ограниченной точностью)
current_time(2) → 14:39:53.66-05 |
current_timestamp → timestamp with time zone |
Текущая дата и время (на момент начала транзакции)
current_timestamp → 2019-12-23 14:39:53.662522-05 |
current_timestamp ( integer ) → timestamp with time zone |
Текущие дата и время (на момент начала транзакции; с ограниченной точностью)
current_timestamp(0) → 2019-12-23 14:39:53-05 |
clock_timestamp ( ) → timestamp with time zone |
Текущая дата и время (меняется в процессе выполнения операторов)
clock_timestamp() → 2019-12-23 14:39:53.662522-05 |
date_bin ( interval, timestamp, timestamp ) → timestamp |
Подгоняет заданное значение под интервал, отсчитывая от указанного начального момента
date_bin('15 minutes', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00') → 2001-02-16 20:35:00 |
date_part ( text, timestamp ) → double precision |
Возвращает поле даты/времени (равнозначно extract)
date_part('hour', timestamp '2001-02-16 20:38:40') → 20 |
date_trunc ( text, timestamp ) → timestamp |
Отсекает компоненты даты до заданной точности
date_trunc('hour', timestamp '2001-02-16 20:38:40') → 2001-02-16 20:00:00 |
date_trunc ( text, timestamp with time zone, text ) → timestamp with time zone |
Отсекает компоненты даты до заданной точности в указанном часовом поясе
date_trunc('day', timestamptz '2001-02-16 20:38:40+00', 'Australia/Sydney') → 2001-02-16 13:00:00+00 |
date_trunc ( text, interval ) → interval |
Отсекает компоненты даты до заданной точности
date_trunc('hour', interval '2 days 3 hours 40 minutes') → 2 days 03:00:00 |
isfinite ( date ) → boolean |
Проверяет конечность даты (её отличие от +/-бесконечности)
isfinite(date '2001-02-16') → true |
isfinite ( timestamp ) → boolean |
Проверяет конечность времени (его отличие от +/-бесконечности)
isfinite(timestamp 'infinity') → false |
isfinite ( interval ) → boolean |
Проверяет конечность интервала (в настоящее время все интервалы конечны)
isfinite(interval '4 hours') → true |
justify_days ( interval ) → interval |
Преобразует интервал так, что каждый 30-дневный период считается одним месяцем
justify_days(interval '35 days') → 1 mon 5 days (1 месяц 5 дней) |
justify_hours ( interval ) → interval |
Преобразует интервал так, что каждый 24-часовой период считается одним днём
justify_hours(interval '27 hours') → 1 day 03:00:00 (1 день 03:00:00) |
justify_interval ( interval ) → interval |
Преобразует интервал с применением justify_days и justify_hours и дополнительно корректирует знаки
justify_interval(interval '1 mon -1 hour') → 29 days 23:00:00 (29 дней 23:00:00) |
make_date ( year int, month int, day int ) → date |
Образует дату из полей: year (год), month (месяц) и day (день)
(отрицательное значение поля year означает год до н. э.)
make_date(2013, 7, 15) → 2013-07-15 |
make_interval ( [years int [, months int [, weeks int [, days int [, hours int [, mins int [, secs double precision]]]]]]] ) → interval |
Образует интервал из полей: years (годы), months (месяцы), weeks (недели), days (дни), hours (часы),
minutes (минуты) и secs (секунды), каждое из которых по умолчанию считается равным нулю.
make_interval(days => 10) → 10 days |
make_time ( hour int, min int, sec double precision ) → time |
Образует время из полей: hour (час), minute (минута) и sec (секунда)
make_time(8, 15, 23.5) → 08:15:23.5 |
make_timestamp ( year int, month int, day int, hour int, min int, sec double precision ) → timestamp |
Образует момент времени из полей: year (год), month (месяц), day (день), hour (час),
minute (минута) и sec (секунда) (отрицательное значение поля year означает год до н. э.)
make_timestamp(2013, 7, 15, 8, 15, 23.5) → 2013-07-15 08:15:23.5 |
make_timestamptz ( year int, month int, day int, hour int, min int, sec double precision [, timezone text] ) → timestamp with time zone |
Образует дату и время с часовым поясом из полей: year (год), month (месяц), day (день), hour (час), minute (минута) и sec (секунда) (отрицательное значение поля year означает год до н. э.). Если параметр timezone (часовой пояс) не указан, используется текущий часовой пояс; в примерах предполагается часовой пояс Europe/London (Европа/Лондон).
make_timestamptz(2013, 7, 15, 8, 15, 23.5) → 2013-07-15 08:15:23.5+01 make_timestamptz(2013, 7, 15, 8, 15, 23.5, 'America/New_York') → 2013-07-15 13:15:23.5+01 |
statement_timestamp ( ) → timestamp with time zone |
Текущая дата и время (на момент начала текущего оператора)
statement_timestamp() → 2019-12-23 14:39:53.662522-05 |
timeofday ( ) → text |
Текущая дата и время (как clock_timestamp, но в виде строки типа text)
timeofday() → Mon Dec 23 14:39:53.662522 2019 EST |
transaction_timestamp ( ) → timestamp with time zone |
Текущая дата и время (на момент начала транзакции)
transaction_timestamp() → 2019-12-23 14:39:53.662522-05 |
extract ( field from timestamp ) → numeric |
Возвращает поле даты/времени
extract(hour from timestamp '2001-02-16 20:38:40') → 20 |
extract ( field from interval ) → numeric |
Возвращает поле интервала
extract(month from interval '2 years 3 months') → 3 |
localtime → time |
Текущее время суток
localtime → 14:39:53.662522 |
localtime ( integer ) → time |
Текущее время суток (с ограниченной точностью)
localtime(0) → 14:39:53 |
localtimestamp → timestamp |
Текущая дата и время (на момент начала транзакции)
localtimestamp → 2019-12-23 14:39:53.662522 |
localtimestamp ( integer ) → timestamp |
Текущие дата и время (на момент начала транзакции; с ограниченной точностью)
localtimestamp(2) → 2019-12-23 14:39:53.66 |
now ( ) → timestamp with time zone |
Текущая дата и время (на момент начала транзакции)
now() → 2019-12-23 14:39:53.662522-05 |
startOfMonth ( integer ) → date |
Первое число месяца. Аргумент определяет относительный сдвиг в месяцах:
0 - текущий месяц
1 - следующий месяц
-1 - предыдущий месяц
|
endOfMonth ( integer ) → date |
Последнее число месяца. Аргумент определяет относительный сдвиг в месяцах:
0 - текущий месяц
1 - следующий месяц
-1 - предыдущий месяц
|
Случайные функции
Функция |
Описание |
---|---|
random ( ) → number |
Возвращает случайное число в диапазоне 0.0 <= x < 1.0
random() → 0.897124072839091 |
Условные функции
Функция |
Описание |
---|---|
COALESCE(значение [, ...]) |
Функция COALESCE возвращает первый попавшийся аргумент, отличный от NULL.
Если же все аргументы равны NULL, результатом тоже будет NULL.
Это часто используется при отображении данных для подстановки некоторого
значения по умолчанию вместо значений NULL.
|
NULLIF(значение1, значение2) |
Функция NULLIF выдаёт значение NULL, если значение1 равно значение2;
в противном случае она возвращает значение1. Это может быть полезно
для реализации обратной операции к COALESCE.
|
GREATEST(значение [, ...]) |
Выбирает наибольшее значение из списка выражений.
|
LEAST(значение [, ...]) |
Выбирает наименьшее значение из списка выражений.
|
Агрегатные функции
Функция |
Описание |
---|---|
avg ( number ) → numeric |
Вычисляет арифметическое среднее для всех входных значений, отличных от NULL.
|
count ( * ) → bigint |
Выдаёт количество входных строк.
|
count ( "any" ) → bigint |
Выдаёт количество входных строк, в которых входное значение отлично от NULL.
|
max ( см. описание ) → тот же тип, что на входе |
Вычисляет максимальное из всех значений, отличных от NULL.
Имеется для всех числовых и строковых типов, типов-перечислений и даты/времени.
|
min ( см. описание ) → тот же тип, что на входе |
Вычисляет минимальное из всех значений, отличных от NULL.
Имеется для всех числовых и строковых типов, типов-перечислений и даты/времени.
|
sum ( number ) → bigint |
Вычисляет сумму всех входных значений, отличных от NULL.
|
Использование выражений при поиске
Если атрибут начинается на (
и заканчивается на )
, то между скобками может быть выражение любого уровня сложности
с использованием функций и операторов +
, -
, *
, /
Пример запроса:
await Records.query(
{
ecosType: 'ecos-contract',
language: 'predicate',
query: {
t: 'contains',
a: 'counterparty',
v: 'emodel/counterparty@some-counterparty-id'
}
},
{
amount: 'amount?num',
poweredAmount: '(2 * power(amount,2))?num' // возводим в степень и умножаем на два
}
);