Pages - Menu

Pages - Menu

Pages

2023年8月5日土曜日

RUSSIA. Rust, который является языком № 1 среди веб-языков по скорости и высокой безопасности (однако многие программисты избегают Rust из-за его сложности). Таким образом, язык V, как следующий кандидат, является быстрым, поэтому все, что остается, - это бюджет для постоянного обновления версии, чтобы сделать ее высокобезопасной, и для разработки новейшего Python на основе языка V или Rust, который одновременно является быстрым и высокобезопасным, а также для разработки фреймворка и WunderGraph'а. Мы будем рады, если правительства стран мира сократят свои военные расходы и каждый год и месяц будут выделять бюджет веб-индустрии и ее пользователям на поддержку FastAPI, React, Kintone и Database как можно скорее, в качестве мирного использования Интернета, который изначально предназначался для военных целей". (Генеральный директор: Масахиро Ишизука, 090-7555-5011) Представление SurrealDB, базы данных, созданной на языке Rust.

https://qiita.com/iisaka51/items/45f23df873ea5be433c7


@iisaka51 (Гоичи (Иисака) Юкава)


Rust

База данных

RocksDB

TiKV

surrealdb

Последнее обновление 15 мая 2023 г.

Опубликовано 21 января 2023 г.

Введение.

В данном документе приводится краткое описание SurrealDB, опубликованное в июле 2022 года.


История SurrealDB

Она находится в разработке с 2016 года, хотя публично доступна лишь короткое время

Февраль 2016 Разработка началась на языке GoLang

2017 Jul Начата эксплуатация в качестве SaaS back-end БД

2021 окт Принято решение о выпуске с открытым исходным кодом, перестроена на Rust

2022 июл Выпущена бета-версия.1

2022 авг Выпущена Beta.5

2022 окт Выпущена Beta.8

SurrealDB Inc.

Ноябрь 2021 Создана компания SurrealDB Ltd. в Лондоне

Январь 2023 г. Привлечено 6 млн долл. для создания DBaaS

История создания SurrealDB

Основные тенденции

Абстрагирование от баз данных, облака, бессерверные технологии

Все больше компаний переходят на DBaaS

61% разработчиков/операторов завершили или собираются завершить полный > переход на DBaaS, согласно опросу, проведенному компанией MariaDB

Растущий объем рынка DBaaS

До 24,8 млрд. долл. к 2025 году

Богатый инвестиционный климат для DBaaS

SingleStore привлекает 30 млн долларов (2022/окт)

EdgeDB привлекает $15 млн (2022/ноябрь)

SurrealDB привлекает $6 млн (январь 2023 г.)

Профиль SurrealDB

Новости Hacke's Top page

№ 4 2022/авг

№ 2 2022/сентябрь

Занимает первое место в рейтинге GitHub Featured Repositories

2022/авг

2022/дек

Звезда GitHub

От 180 звезд до 1500 звезд за 48 часов

5000 звезд за 3 недели после публикации

10 000 звезд в течение 4 недель после публикации

№1 в "Горячем списке" Reddit по разделам "Программирование" и "Rust

Лицензия SurrealDB

Исходный код SurrealDB лицензируется на условиях Business Software License 1.1.

SDK и библиотеки/драйверы находятся под лицензией MIT

SurrealDB BSL позволяет использовать SurrealDB на неограниченном количестве узлов при условии, что вы не предлагаете ее в качестве коммерческой DBaaS.

Может быть встроена в продукты

SurrealDB BSL действует в течение 4 лет

1 января 2026 года это ограничение истекает, и код становится открытым исходным кодом в соответствии с действующей лицензией Apache License 2.0

Свободное использование для любых целей

Особенности SurrealDB

Реализована на языке Rust

Устойчивость к сегментационным ошибкам

Кросс-компиляция

Относительно быстрый по сравнению с другими языками

Небольшой вес: размер двоичного файла для Linux - 24 МБ, для macOS - 44 МБ

Сервер и REPL-клиент в одном бинарном файле

Простая установка

Поддержка HTTP/Restful API

Поддержка WebSockets

Внутренние БД: EchoDB, RocksDB, TiKV, FoundationDB, IndexedDB

Особенности как базы данных

Бессхемность: нет необходимости определять схему

Различные форматы хранения данных: таблицы, документы, графы и т.д.

Многорядные, многотабличные ACID-транзакции

Связи по рекордным ссылкам и направленным графам

Не требуется JOIN, разумное избежание проблемы N+1

Предопределенные аналитические запросы

Выборка, агрегирование, группировка и упорядочивание данных по мере их написания

Возможность расширения запросов с помощью встроенного JavaScript

Возможность записи регулярных выражений в запросах (/regex/)

Поддержка GeoJSON

Возможность параллельного выполнения операций CRUD

Сильные стороны SurrealDB

Дифференцирующие функции в то же время

Пользователи могут обращаться к ней непосредственно из фронтенда

База данных может быть должным образом сконфигурирована для аутентификации и авторизации

Синхронизация данных в реальном времени

Мало конкурентов, например Google Firestore

Единственная в своем роде БД при локальном использовании

Аутентификация и авторизация на стороне базы данных...

Может обеспечивать контроль доступа на основе ролей

Контроль доступа на основе заданных ролей, таких как администратор, редактор, зритель и т.д.

Слабые стороны SurrealDB

Пока не опубликована (июль 2022 г.).

Возможны потенциальные уязвимости и ошибки в системе безопасности

PostgreSQL впервые опубликован в 1997 году, предшественник Postgress - в 1989 году)

Первая версия MySQL была опубликована в 1995 году

MariaDB впервые опубликована в 2009 году

MongoDB впервые опубликована в 2009 году

Мало информации.

Официальная документация находится в процессе производства

Если есть сомнения, читайте исходный код

Не все возможности реализованы

SurrealDB в настоящее время не приносит прибыли: сервис DBaaS будет доступен в 2023 году

Возможности, находящиеся в стадии разработки (в beta.9 еще не реализованы)

Поддержка нескольких узлов в распределенном режиме

Репликация

Проверка работоспособности

GraphQL

FULLTEXT - полнотекстовое индексирование

Поля LEARN

Автоматическая настройка на основе анализа заданных полей методом машинного обучения

Версионные временные таблицы

Возможность "возврата в прошлое" при обращении к данным

Подсветка кода в IDE (Atom, VSCode, Vim...)

Выпуск приложения для работы с пользовательскими интерфейсами планируется в версии 1.x

Обзор двухуровневой архитектуры SurrealDB

surrealdb_arch.png


Обзор работы SurrealDB

surrealdb_dataflow.png


Исходный код.

Цифрами обозначено количество строк, включая комментарии (SurrealDB 1.0.0-beta.8).


Кстати, MariaDB 10.9 имеет более 1,3 млн строк (только для клиента - более 12 тыс. строк).


|-- src // Уровень API 4172

     |-- net 1981

     |-- cli 895

     |-- rpc 174

     ...

|-- lib/src // BL Layer 32261

     |-- sql 19030

     |-- fnc 3524

    ...

     |-- kvs 3286

           |-- indexdb 220

           |-- rocksdb 316

           |-- tikv 269

      ...

В SurrealDB реализовано на удивление мало функций

Для разбора запросов используются функции nom

Функции могут быть соединены в цепочку для инкрементного построения парсеров

Использование echodb для хранения данных в памяти (Тоби)

In-Memory KVS DB с многоверсионным контролем параллелизма

Использование storekey для хранения в KVS (Tobie)

Двоичное кодирование с сохранением порядка в словаре

Полезно для создания отсортированных ключей KVS

Использование MsgPack и serde для сериализации/десериализации (rmp-serde)

Использование geo для парсинга GeoJSON

Использование RocksDB для использования локальных файлов в качестве хранилища данных

Использование TiKV и FoundationDB для функциональности распределенной БД

Элементы, которые SurrealDB хранит в KVS

Метаданные

Таблицы, индексы, диапазоны и другие структуры

Данные.

Значения объектов, хранящихся в SurrealDB

Как SurrealDB хранит данные в KVS

Структуры Map Rust и MsgPack с привязками serde

Экономия объема хранилища

Эффективность сериализации/десериализации

Два метода доступа к данным в KVS

По ключу: указываем конкретный ключ и получаем значение (быстро)

Сканирование: указать диапазон ключей и получить все значения (медленно)

Иерархическая структура сохраняется в ключах

Пространство имен -> База данных -> Таблица -> ID

SurrealDB преобразует иерархическую структуру ключей в диапазоны для сканирования

Установка (простая и быстрая)

Linux.

$ curl -sSf https://install.surrealdb.com | sh

macOS

$ brew install surrealdb/tap/surreal

Windows.

PS C:\> iwr https://windows.surrealdb.com -useb | iex

Docker/Podmandocker

$ docker run --rm -p 8000:8000 surrealdb/surrealdb:latest start

$ podman run --rm -p 8000:8000 surrealdb/surrealdb:latest start

Запуск SurrealDB

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


По умолчанию это память

$ surreal start --user root --pass root memory

Другие направления экспорта


file:///path/to/data.db файловая система (RocksDB)

rocksdb:///path/to/data.db RocksDB

tikv://endpoiint TiKV TiKV

fdb:[///path/to/clusterfile] FoundationDB (требуется пересборка)

Режим STRICT.


--strict Начните с указания опций.

NAMESPACE,, DATABASE должны быть определены, иначе возникнет ошибка

Ошибка, если не определена таблица TABLE

Сброс/восстановление

Выгрузка в файл с помощью подкоманды export

Восстановление из файла с помощью подкоманды import

$ surreal export --conn http://dev00:8000 --ns test --db test dump.db

$ surreal import --conn http://dev00:8000 --ns test --db test dump.db

Подключение из клиента CLI

Выполните подкоманду sql

$ surreal sql --conn http://dev00:8000 --ns test --db test --pretty

--user и --pass ROOT аутентификация пользователь/пароль

--pretty для форматирования и отображения вывода в формате JSON

--ns NAMESPACE --db instruct DATABASE

Когда SurrealDB работает в режиме STRICT, опции -ns и -db игнорируются.

HTTP RESTful API

PATH TYPE Описание.

/key/:table GET Получить все записи в таблице из базы данных.

/key/:table/:id GET Получить конкретную запись из базы данных.

/key/:table POST Создать запись в таблице в базе данных

/key/:table/:id POST Создать конкретную запись в таблице в базе данных

/key/:table/:table DELETE Удалить все записи таблицы из базы данных

/key/:table/:id PUT Обновление указанной записи в базе данных

/key/:table/:id PATCH Модифицирует указанную запись в базе данных

/key/:table/:id DELETE Удаляет указанную запись из базы данных.

HTTP RESTful API (продолжение)

PATH TYPE Описание.

/version GET Возвращает версию SurrealDB

/signup POST Регистрация для аутентификации SCOPE

/signin POST Вход в систему с аутентификацией SCOPE

/rpc POST Запрос к WebSocket с помощью JON-RPC

/sql POST Разрешить запросы на языке SurQL

/export GET Выгрузить содержимое базы данных

/import POST Применить содержимое запроса к базе данных (восстановить)

HTTP RESTful API (неполный)

PATH TYPE Описание.

/sync GET Репликация

/health GET Проверка состояния базы данных

/status GET Возврат статуса

Определения таблиц в общем виде SQL

CREATE TABLE human (

      id int, nickname text, прозвище

      текст прозвища,

      возраст int,

      PRIMARY KEY(id)

   );

SurealDB не имеет схемы

Нет необходимости определять таблицы и поля

Нет необходимости вносить изменения при добавлении полей

Если сервер запускается в режиме STRICT, то сначала требуется определить таблицы

CREATE human:freddie SET nickname="freddie", age=99 ;

CREATE human:brian SET nickname="brian", age=75, sex=true ;

ID == TableName:UniqID Обратите внимание, что ID содержит имя таблицы.


Указание типа поля без схемы

CREATE human:freddie SET

       nickname = <string> "freddie",.

       age = <int> 99 ;

Типы и приведения

bool, int, float, string, number, decimal, datetime, duration

Строки даты/времени преобразуются в формат ISO 8601: то же самое, что и приведение в <datetime>.

Если вы хотите рассматривать строку datetime как строку, как она есть, выполните приведение с помощью <string>.

SELECT * FROM "2023-01-01";.

SELECT * FROM <datetime> "2023-01-01";

SELECT * FROM <string> "2023-01-01T02:03:00Z" + "-test";

Определение схемы

DEFINE TABLE human SCHEMAFULL;

DEFINE FIELD nickname ON human TYPE string;

DEFINE FIELD age ON human TYPE int;

[re]определить таблицу как SCHMALESS

DEFINE TABLE human SCHEMALESS ;

DEFINE TABLE human SCHMAFULL; [re]define table as SCHMAFULL

DEFINE TABLE human SCHEMAFULL ;

SCHEMAFULL

Хранятся только данные, допустимые в определенных полях.

Может быть ограничено определенными типами данных

DEFINE FIELD может устанавливать значение по умолчанию, если данные не введены.

Устанавливаемое значение задается в $value

DEFINE TABLE person SCHEMAFULL;.

DEFINE FIELD name ON person TYPE string VALUE $value OR 'guest';.

Добавление данных

Если поле -id опущено, то идентификатор устанавливается автоматически

INSERT INTO human (nickname, age)

       VALUES ('brian', 75);


INSERT INTO human (id, nickname, age)

       VALUES ('human:freddie', 'freddie', -1);


CREATE human:robert SET nickname='robert', age=30;


CREATE human SET

       id = human:jack, nickname='jack', age=30;


CREATE human CONTENT

       { id: 'human:john', nickname: 'john', age: 99 };

INSERT

При наличии дублирующихся идентификаторов можно выполнить UPDATE

INSERT INTO test (id, test, something)

    VALUES ('tester', true, 'other');

INSERT INTO test (id, test, something)

    VALLUES ('tester', true, 'other' )

    ON DUPLICATE KEY UPDATE something = 'else' ;

Вложенные поля

Поля в таблице могут быть вложенными.

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

UPDATE person:test CONTENT {

              параметры: {

                  вложенные: {

                      объект: {

                          вещь: 'test'

                      }

                  }

              }

          };

SELECT settings.nested.object FROM person ;.

NONE и NULL

Поле может иметь значения NONE и NULL

NONE: значение не задается

NULL: задается пустое значение

CREATE person:test1 SET email = 'info@example.com';

CREATE person:test2 SET email = NONE;

CREATE person:test3 SET email = NULL;

USE.

Укажите NAMESPACE и DATABASE, которые будут использоваться.

Действует при доступе с аутентификацией ROOT.

USE NAMESPACE test ;

USE NAMESPACE test DATABASE db1 ;

USE NS test test DB db1 ;

Отношение в общем случае SQL

CREATE TABLE armor (

    id int,

    имя текст,

    сопротивление текст,

    PRIMARY KEY(id)

 );


INSERT INTO armor VALUES

    (0, "кожа", 3);

    (1, "platemail", 30),

    (2, "chainmail", 20),.


CREATE TABLE player (

   имя текст,

    сила int,

    armor_id int,

    PRIMARY KEY((name),.

    CONSTRAIN fk_armor

      FOREIGN KEY(armor_id)

      REFERENCECS armor(id)

);

Отношение в SurrealQL (SurQL)

CREATE armor:leather SET registance = 3;

CREATE armor:chainmail SET registance = 20;

CREATE armor:platemail SET registance = 30;

CREATE player:jack SET strength = 22, armor = armor:platemail;

CREATE player:brian SET strength = 20, armor = armor:leather;

Использование имен таблиц в идентификаторах


SurQL: Отношение с определенной схемой

DEFINE TABLE armor SCHEMAFULL;.

DEFINE FIELD resistance ON armor TYPE int;.


CREATE armor:leather SET resistance = 3;

CREATE armor:chainmail SET resistance = 20;

CREATE armor:platemail SET resistance = 30;


DEFINE TABLE player SCHEMAFULL;.

DEFINE FIELD strength ON player TYPE int;

DEFINE FIELD armor ON player TYPE record(armor);


CREATE player:jack SET strength = 22, armor = armor:platemail;

CREATE player:brian SET strength = 20, armor = armor:leather;

Общие отношения SQL: JOIN

SELECT

    player.name,

    player.strength,.

    armor.name AS armor_name,.

    armor.resistance AS armor_resistance

 FROM игрок

 JOIN armor

 ON armor.id = player.armor_id

Отношение на языке SurQL: JOIN не требуется

FETCH расширяет указанное поле

SELECT * FROM player FETCH armor;.

Ссылка на запись

CREATE armor:leather SET registance = 3;

CREATE armor:chainmail SET registance = 20;

CREATE armor:platemail SET registance = 30;


CREATE player:jack SET strength = 22, armor = armor:platemail;

CREATE player:brian SET strength = 20, armor = armor:leather;

Внешний ключ == Ссылка на запись


Отношение: один к одному

CREATE human:freddie SET nickname="freddie", age=99 ;

CREATE human:brian SET nickname="brian", age=75

UPDATE human:brian SET bff = human:freddie;


SELECT bff.nickname, bff.age FROM human:brian

Укажите поля во внешней таблице, соединив их точками


Отношение: один-ко-многим

CREATE car:tesla SET model='Model S', ev=True, price=99000;

CREATE car:mustang SET model='Mustang Cobra', ev=False, price=60000;


UPDATE human:brian SET cars=["car:tesla"];

UPDATE human:freddie SET cars=["car:mustang"];

UPDATE car:tesla SET owner = human:brian;

UPDATE car:mustang SET owner = human:freddie;


CREATE parts:tire SET brand='Michelin', size=5;

CREATE parts:gastank SET brand='Tanksy', size=10;

CREATE parts:battery SET brand='Xi Ping', size=20;


UPDATE car:mustang SET parts = ['parts:tire', 'parts:gastank'];

UPDATE car:tesla SET parts = ['parts:tire', 'parts:battery'];

ОТНОШЕНИЕ: ОДИН-КО-МНОГИМ

SELECT parts FROM car:mustang

SELECT cars.parts.brand FROM human:brian ;.

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


Графические связи

RELATE player:jack -> wants_to_buy -> armor:dragon;

RELATE player:jack -> wants_to_buy -> armor:platemail;


SELECT * FROM wants_to_buy;

SELECT id, -> wants_to_buy -> armor AS wtb FROM player;

SELECT id, <- wants_to_buy <- player AS players FROM armor:dragon

Указывайте поля во внешних таблицах, соединяя их символами "->" или "<-".


LIMIT.

Определяет количество результатов, возвращаемых командой SELECT.

В настоящее время прямое управление FETCH работает плохо

CREATE tag:rs SET name = 'Rust';.

CREATE tag:go SET name = 'Golang';

CREATE tag:js SET name = 'JavaScript';


CREATE person:tobie SET tags = [tag:rs, tag:go, tag:js];

CREATE person:jaime SET tags = [tag:js];


SELECT * FROM person LIMIT 1;

SELECT * FROM (SELECT * FROM person FETCH tags) LIMIT 1;

// SELECT * FROM person LIMIT 1 FETCH tags;

START.

Задает начальную позицию результата, возвращаемого SELECT (с нулевым базисом).

Хорошо работает, когда указывается FETCH

SELECT * FROM person START AT 1;

SELECT * FROM (SELECT * FROM person FETCH tags) START 1;

SELECT * FROM person START 1 FETCH tags;

SurQL: WHERE, ORDER BY, GROUP BY

SELECT * FROM armor ;


SELECT * armor WHERE resistance >= 30 ;

SELECT math::sum(strength) FROM player GROUP BY ALL ;


SELECT * FROM armor ORDER BY RAND();

SELECT * FROM armor ORDER RAND();


SELECT * FROM armor ORDER resistance NUMERIC ASC ;

SELECT * FROM armor ORDER resistance NUMERIC DESC ;

SurQL: BEFORE, AFTER, DIFF

CREATE, UPDATE и DELETE могут возвращать данные до, после и разность запросов

UPDATE human:freddie SET email = 'freddie@example.com';

UPDATE human:freddie SET email = 'freddie@dummy.com' RETURN DIFF ;.

Предварительное определение аналитических запросов

После записи данных выполните отбор, агрегирование, группировку, упорядочивание и т.д.

DEFINE TABLE person SCHEMALESS;.

DEFINE TABLE person_by_age AS

    SELECT

          count(),.

          возраст,.

          math::sum(age) AS total, math::mean(age) AS average

          math::mean(age) AS average

          FROM person

          GROUP BY age ;

СОБЫТИЕ.

Содержимое таблицы, заданной параметром ON TABLE: $before, $after

ID, по которому произошло событие: $this

Событие, которое произошло: $event

UPDATE human:freddie SET email = 'freddie@example.com';

UPDATE human:brian SET email = 'brian@example.com';


DEFINE EVENT changelog ON TABLE human

       WHEN $before.email ! = $after.email

       THEN ( CREATE changelog SET

              time = time::now(),.

              email = $after.email );

RANGE Повторять столько раз, сколько указано двоеточием

> CREATE |test:10| SET time = time::now()

[

  {

    "result": [

      {

        "id": "test:g9hq0dowz77us5yvxnst",.

        "time": "2022-12-20T04:10:19.282031670Z"

      },

      {

        "id": "test:nk45tn46dy2bn1hd6zj9", { "time".

        "time": "2022-12-20T04:10:19.282450969Z"

      },.

RANGE Повторить с указанием начального и конечного значений.

> CREATE |test:1..10| SET time = time::now()

[

  {

    "result": [

      {

        "id": "test:1",.

        "time": "2022-12-20T04:12:21.477667592Z"

      },

      {

        "id": "test:2", "time".

        "time": "2022-12-20T04:12:21.478289880Z"

      },.

Регулярное выражение.

> SELECT * FROM test WHERE id = /. *[24]. */

[

  {

    "result": [

      {

        "id": "test:2",.

        "time": "2022-12-20T04:12:21.478289880Z"

      },

      {

        "id": "test:4", "time".

        "time": "2022-12-20T04:12:21.478332436Z"

      }

    ],.

    "status": "OK",.

IF THEN ELSE

UPDATE person SET classtype =

    IF age <= 10 THEN

      'junior'.

    ELSE IF age <= 21 THEN

      'студент'

    ELSE IF age >= 65 THEN

      'старший'

    ELSE

      NULL

    END ;

MERGE.

Слияние полей таблицы: добавление, удаление

UPDATE person:test SET

      name.initials = 'TMH',.

      name.first = 'Tobie',.

      name.last = 'Morgan Hitchcock';.

UPDATE person:test MERGE {

      имя: {

          title: 'Mr',.

          инициалы: NONE,.

          суффикс: ['BSc', 'MSc'], }

          }

     };

Ограничения таблицы ASSERT

Каждое определяемое поле может задавать ограничения на данные в ASSERT

DEFINE FIELD countrycode ON user TYPE string

    // Убедитесь, что код страны соответствует стандарту ISO-3166

    ASSERT $value ! = NONE AND $value = /[A-Z]{3}/

    // Установить значение по умолчанию, если пусто

    VALUE $value OR 'GBR'

  ;.

Функция FUTURE

- Определение полей таблицы по значениям, которые будут заданы позже

UPDATE person:test SET

        can_drive = <future> {

             день рождения && time::now() > день рождения + 18y };


UPDATE person:test SET birthday = <datetime> '2007-06-22';

UPDATE person:test SET birthday = <datetime> '2001-06-22';

ПЕРМИССИИ.

Ограничение CRUD-операций над TABLE и FIELD

DEFINE TABLE user SCHEMALESS

    PERMISSIONS

    Для операций select, create, update

        WHERE id = $auth.id

    для удаления

        WHERE id = $auth.id OR $auth.admin = true ;.

ACID-транзакция

НАЧАТЬ ТРАНЗАКЦИЮ;.


UPDATE coin:one SET balance += -23.00 ;

UPDATE coin:two SET balance -= 23.00 ;


COMMIT TRANSACTION;.

ИЛИ

ОТМЕНИТЬ ТРАНЗАКЦИЮ;.

Если для таблицы с установленным DROP задана новая транзакция, то все незавершенные транзакции уничтожаются.

Если для таблицы установлен параметр DROP и указана новая транзакция, то все незавершенные транзакции уничтожаются.

По умолчанию новые транзакции доступны только для чтения (если это возможно).

WebSocket

Укажите URL типа ws://dev00:8000/rpc (ws://, wss://).

Передача POST со следующим JSON в качестве тела сообщения.

{

  "id": <ID для идентификации>,.

  "method": <команда>,.

  "params": <массив параметров, требуемых командой>.

}

Аутентификация SurrealDB

Аутентификация ROOT

Пользователь/пароль, указанный при запуске сервера

-pass Отсутствие опции отключает ROOT-аутентификацию

Аутентификация пользователя.

Пользователь/пароль, созданный с помощью DEFINE LOGIN

Токен-аутентификация

Аутентификация с помощью JSON Web Token (JWT) (RFC7519, RFC8725)

Аутентификация по OAuth сторонних производителей

Аутентификация с помощью SCOPE

Предварительно заданные SIGNUP и SIGNIN

Периоды доступа могут быть ограничены

LOGIN

Доступ может быть ограничен NAMESPACE и DATABASE

Пользователи без права доступа к NAMESPACE не могут создавать/удалять БД

DEFINE LOGIN admin ON NAMESPACE PASSWORD "admin.admin";.

DEFINE LOGIN guest ON DATABASE PASSWORD "guest.guest";.

Изображение LOGIN.

surrealdb_login.png


TOKEN.

Разрешить доступ только к запросам с определенным токеном в заголовке

Может быть задан для NAMESPACE, DATABASE и SCOPE

DEFINE TOKEN my_token ON DATABASE

    TYPE HS512 VALUE '1234567890';.

SCOPE.

Все поля JSON-RPC задаются переменными

SCOPE предоставляет возможность доступа к базе данных

Доступ к таблицам и полям зависит от PERMISSIONS.

SCOPE с TOKEN не может создавать/модифицировать/удалять таблицы или отображать информацию

DEFINE FIELD email ON TABLE user TYPE string ASSERT is::email($value);

DEFINE INDEX email ON TABLE user COLUMNS email UNIQUE;


DEFINE SCOPE account SESSION 24h

          SIGNUP ( CREATE user SET

                email = $email, pass = crypto::argon

                pass = crypto::argon2::generate($pass) )

        SIGNIN ( SELECT * FROM user

                WHERE email = $email

                AND crypto::argon2::compare(pass, $pass) );.

Изображение SCOPE

surrealdb_scope.png


ПОДПИСКА на аутентификацию SCOPE

let jwt = fetch('https://api.surrealdb.com/signup', {

        method: 'POST', {

        заголовки: {

                'Accept': 'application/json',.

                'NS': 'google', // Указываем пространство имен

                'DB': 'gmail', // Указываем базу данных

    }, }

        body: JSON.stringify({

                'NS': 'google', // Указываем пространство имен

                

                'SC': 'account',.

                email: 'tobie@surrealdb.com',.

                pass: 'a85b19*1@jnta0$b&!'

        }),.

});

Обращение к токенам и данным аутентификации из запросов.

Для $session, $scope, $token и $auth задается специальная информация, относящаяся к клиенту

При использовании NAMESPACE, DATABASE или TOKEN устанавливаются $session и $token

$toekn устанавливается на все поля JWT-токена

$scope устанавливается в имя SCOPE при аутентификации SCOPE

$auth устанавливается, если JWT имеет поле id в аутентификации SCOPE и данные, указанные в id, существуют в таблице

SELECT * FROM $session;.

SELECT * FROM $token;.

SELECT * FROM $scope;.

SELECT * FROM $auth;.

Живой запрос

Действителен при доступе через WebScoket

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

Передача данных во внешние библиотеки в реальном времени

Все клиентские устройства синхронизируются.

Чтобы прервать LIVE-запрос, укажите KILL

LIVE SELECT * FROM user WHERE age > 18 ;.

PARALLEL.

PARALLEL добавляется к CREATE, DELETE, UPDATE и SELECT.

Выполнение запроса обрабатывается параллельно

SELECT * FROM test PARALLEL ;

TIMEOUT

TIMEOUT добавляется в CREATE, DELETE, UPDATE и SELECT.

Ожидание в течение времени, указанного при выполнении запроса

SELECT * FROM

    http::get('https://ipinfo.io')

    TIMEOUT 10s;.

GeoJSON

Pont, Line, Polygon, MultiPoint, MultiLine, MultiPolygon, Collection

UPDATE university:oxford SET area = {

  

  координаты: [

    [[ [10.0, 11.2],[10.5, 11.9],[10.8, 12.0],[10.0, 11.2]],]

    [[ [9.0, 11.2], [10.5, 11.9],[10.3, 13.0], [9.0, 11.2]]

    ]

};


SELECT * FROM university:oxford;.

Встроенные функции SurQL

array::xxxx()

combine, complement, concat, difference, disinc, intersect, len, sort::asc, sort::desc, sort, union, all, any, add, append, insert, prepend, remove add, append, insert, prepend, remove, reverse, group, push, pop

count()

crypto::xxxx()

argon2::compare, argon2::generate, bcrypt::compare, bcrypt::generate, md5, pdkdf2::compare, pdkdf2::generate, scrypt::compare, scrypt:: generate, sha1, sha25, sha512

Встроенные функции SurQL (продолжение)

duration::xxxx()

дни, часы, минуты, секунды, недели, годы

geo::xxxx()

площадь, пеленг, центроид, расстояние, hash::decode, hash::encode

http::xxxx()

head, get, put, post, patch, delete

is::xxxx()

alphanum, alpha, domain, email, hexadecimal, latitude, longitude, numeric, semver, url, uuid, url, datetime

Встроенные функции SurQL (продолжение)

math::xxxx()

abs, bottom, ceil, fixed, floor, interquartile, max, mean, midhinge, min, mode, nearestrank, percentile, round, spread, sqrt, stddev, sum, top, trimean, variance, pow

not()

parse::email()

хост, пользователь

parse::url::xxxx)

домен, фрагмент, хост, порт, путь, запрос, схема

Встроенные функции SurQL (продолжение)

rand()

rand::xxxx()

bool, enum, float, guid, int, string, time, uuuid:v4, uuuid:v7, uuid

session::xxxxxx()

db, id, ip, ns, origin, sc, sd, token,.

Встроенные функции SurQL (продолжение)

string::xxxx()

concat, endsWith, join, length, lowercase, repeat, replace, reverse, slice, slug, split, startsWith, trim, uppercase, words

time::xxxx()

day, floor, format, group, hour, minute, month, nano, now, round, second, unix, wday, yday, year, timezone

type::xxxxxx()

bool, datetime, decimal, duration, float, int, number, point, regex, table, thin

Встроенные в SurQL обычные числа

math::xxxx

E, FRAC_1_PI, FRAC_1_SQORT_2, FRAC_2_PI, FRAC_2_SQRT_PI, FRAC_PI_2, FRAC_PI_3, FRAC_PI_4, FRAC_PI_6, FRAC_PI_8, LN_10, LN_2, LOGO10_2, LOG10_E,. LOG2_10, LOG2_E, PI, SQRT_2, TAU

Настройка параметров LET

В качестве переменных могут быть заданы числовые, строковые и другие объекты

На переменную можно ссылаться из запроса как на $variable

LET $test = { some: 'thing', other: true };

SELECT * FROM $test WHERE some = 'thing';

Расширение SurQL с помощью JavaScript

Все значения из SurrealDB автоматически преобразуются в типы JavaScript

Возвращаемые значения из функций JavaScript автоматически преобразуются в значения SurrealDB

Булевы, целые числа, плавающие числа, строки, массивы, объекты и объекты даты

все они автоматически преобразуются в значения SurrealDB или из них.

CREATE user:test SET created_at = function() {

        return new Date();

};

Пример JavaScript расширения 1

CREATE platform:test SET version = function() {

            const { platform } = await import('os');

            return platform();

        };

Пример расширения JavaScript 2

LET $value = 'SurrealDB';.

LET $words = ['awesome', 'advanced', 'cool'];

CREATE article:test SET summary = function($value, $words) {

    return `${arguments[0]} is ${arguments[1].join(', ')}`;

};

Пример JavaScript расширения 3

CREATE film:test SET

    рейтинги = [

        { рейтинг: 6.3 }

        { рейтинг: 8.7 }, }

    ],.

    display = function() {

        return this.ratings.filter(r => {

            return r.rating >= 7;

            }).map(r => {

                return { ... .r,.

                    rating: Math.round(r.rating * 10) }; }

            });

    };

Простой пример реализации RETful API на языке Python

from urllib.request import Request, urlopen

import base64

import json

from pprint import pprint as pp


_BASE_URL = "http://dev00:8000"

_NAMESPACE = "test"

_DATABASE = "test"

_USER = "root"

_PASS = "root"


auth_str = f"{_USER}:{_PASS}".encode("utf-8")

credential = base64.b64encode(auth_str)

auth = "Basic " + credential.decode("utf-8")


заголовки = {

    "Accept": "application/json",.

    "Authorization": auth,.

    "NS": _NAMESPACE,.

    "DB": _DATABASE,.

}



url = _BASE_URL + "/key/human"

request = Request(url, headers=headers)


with urlopen(request) as res:.

    data = res.read()

    pp(json.loads(data)[0]['result'])

Пример запроса через RETful API на языке Python

импортировать запросы

from requests.auth import HTTPBasicAuth

from pprint import pprint as pp


_URL = "http://dev00:8000/sql"

_NAMESPACE = "test"

_DATABASE = "test"

_USER = "root"

_PASS = "root"


_HEADERS = {

  'Content-Type':'application/json',.

  'Accept':'application/json',.

  'ns': _NAMESPACE,.

  'db': _DATABASE'

}

_auth = HTTPBasicAuth(_USER, _PASS)


def db(query):.

    res = requests.post( _URL,.

          headers=_headers,.

         auth = _auth,

         data=query )

  if "code" in res.json():.

      raise Exception(res.json())

  return res.json()


if __name__ == '__main__':.

    while True:.

      sql = input('SQL> ')

      if sql.upper() == 'Q':

          break

      val = db(sql)

      pp(val)

Пример запроса через WebSocket в Python

import asyncio

from surrealdb import WebsocketClient

from pprint import pprint as pp


_URL = "ws://dev00:8000/rpc"

_NAMESPACE = "test"

_DATABASE = "test"

_USER = "root"

_PASS = "root"


async def main():.

    async с WebsocketClient( url=_URL,.

        namespace=_NAMESPACE, database=_DATABASE,.

        username=_USER, password=_PASS,.

    ) as session:.

        while True:.

            sql = input('SQL> ')

            if sql.upper() == 'Q': break

            res = await session.query(sql)

            pp(res).

Материал PyPI плохо соединяется с WebSocket

Pull-запрос в процессе.

Этот репозиторий пока работает.

GitHub - iisaka51/surrealdb.py at develop

Другие примеры реализации

Flask


GitHub - santiagodonoso/python_flask_surrealdb

FastAPI


GitHub - santiagodonoso/fastapi_surrealdb_v_1

PHP


GitHub - santiagodonoso/php_surrealdb

React


GitHub - rvdende/surrealreact: пользовательский интерфейс проводника SurrealDB, написанный на react.

Deno


https://github.com/officialrajdeepsingh/surrealDb-deno

Typescript.


SurrealDB Explained With Express.js, Node.js, and TypeScript

Следует отметить

Внутренние БД, доступные в surrealdb-1.0.0-beat8

RocksDB, TiKV

FoundationDB, IndexedDB требуют пересборки

IndexedDB может быть использована для встроенных целей

Для доступа к DATABASE требуется NAMESPACE

Различные NAMESPACE приводят к появлению различных DATABASE с одним и тем же именем.

В одной NAMESPACE может быть создано любое количество DATABASE.

Однако для доступа к NAMESPACE/ необходимо разрешение.

Возможно, это ошибка!

Некоторые команды SurQL плохо работают в CLI

Подкоманда SurQL - это доступ к HTTP RestfulAPI с нажатием клавиши возврата

Точки с запятой не распознаются как отдельный запрос

В файлах, переданных подкомандой import, все нормально.

Копирование и вставка выполняется при нажатии клавиши Return.

Комментарии принимаются, но только комментарии являются ошибкой.

Пустой символ ('') считается выполненным.

Переменные, заданные с помощью LET, теряются при нажатии клавиши Return.

Если в середине транзакции нажать клавишу Return, то транзакция будет завершена.

Объявление USE также теряется при нажатии клавиши Return.

Выводы.

Удивительно простая установка и настройка

Очень, очень, очень совместим с веб-приложениями

Мощные запросы, расширяемые с помощью JavaScript

Бизнес-логика и аутентификация пользователей могут обрабатываться непосредственно в базе данных

Упрощенный стек технологий back-end → сокращение времени разработки → снижение затрат

Работа еще не закончена, но очень перспективна

Ссылки

Официальный сайт SurrealDB


GitHub - surrealdb/surrealdb: Масштабируемая, распределенная, совместная, документально-графовая база данных для реального времени в Интернете

StackOverflow


Сбой аутентификации при использовании внешнего JWT-токена в SurrealDB

Траверс отношений вверх в SurrealDB

Как войти в область видимости с помощью SurrealDB?

Как указать пространство имен при использовании веб-сокетов в SurrealDB

Рекомендуемые REST-клиенты

Surrealist


Разделяет результаты нескольких запросов на вкладки.

Отличный пример реализации SurrealDB

GitHub - StarlaneStudios/Surrealist: ⚡ Молниеносная графическая площадка для запросов к SurrealDB для рабочего стола

Tabbed Postman - REST-клиент


Расширение для Chrome

Запросы можно сохранять в виде коллекций

Коллекции можно экспортировать/импортировать

Tabbed Postman - REST-клиент

Клиент Thunder


Расширение для VSCode

Thunder Client - расширение клиента Rest API для VS Code

Приложение.

Параметры сервера и переменные окружения

DB_PATH: место хранения данных (память)

USER Имя пользователя для ROOT-аутентификации (root), --user/-u

PASS Пароль для пользователя ROOT-аутентификации, --pass/-p

ADDR Подсеть, разрешающая ROOT-аутентификацию (127.0.0.1/32), --addr

BIND Имя хоста/IP-адрес для прослушивания соединений (0.0.0.0:8000), --bind/-b

KEY Секретный ключ для ON-DISK шифрования, --key/-k

KVS_CA CA-файл для KVS-соединения, --kvs-ca

KVS_CRT CERT-файл для KVS-соединения, --kvs-crt

KVS_KEY Закрытый ключ для KVS-соединений, --kvs-key

WEB_CRT CERT-файл для прослушивания SSL-соединений, --web-crt

WEB_KEY Закрытый ключ для прослушивания SSL-соединений, --web-key

STRICT Запуск в режиме STRICT, если он установлен, --strict

LOG Уровень журнала "warn", ["info"], "debug", "trace", "full", --log

ACID: четыре характеристики, определяющие транзакцию

Атомарность.

Каждый оператор в транзакции рассматривается как единое целое.

Последовательность.

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

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

Изоляция

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

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

Долговечность

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

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

Проблема N+1

Проблема, при которой обращение к базе данных приводит к тому, что запросы выполняются в общей сложности N+1 раз.

SELECT выполняется один раз для получения N записей

Выполнение SELECT N раз для получения данных, связанных с N записями.

Склонна к возникновению за кулисами при использовании ORM.

Склонность к тяжелой (медленной) работе приложения

RocksDB

Популярная, высокопроизводительная встроенная база данных KVS.

Форк LevelDB, разработанный компанией Meta (Facebook).

Используется в производстве различными веб-сервисами, такими как Facebook, Yahoo!

Обеспечивает сохранение данных при одновременном повышении производительности и безопасности

Производительность линейно возрастает с увеличением числа процессоров

Производительность RocksDB сильно зависит от настройки платформы

Непростая задача из-за сложности многих настраиваемых параметров

TiKV

База данных KVS, работающая на внутреннем сервере TiDB

Персистентность данных (с RocksDB)

Гарантии целостности данных для распределенных баз данных

MVCC (Multi-Version Concurrency Control)

Реализация распределенных транзакций

Google Parcorator / 2PC (2 Phase Commit)

Сопроцессор

Обратите внимание, что по умолчанию TiKV сконфигурирован как 3-узловой кластер.

Пока кластер не сконфигурирован с 3 узлами, они хранятся во временных файлах.

Временные файлы автоматически удаляются при запуске.

Ссылка: https://docs.pingcap.com/tidb/dev/tikv-configuration-file

Конфигурация кластера: https://tikv.org/docs/5.1/deploy/install/production/

Архитектура TiKV

tikv_stack.png


IndexedDB

Встроенная в браузер база данных KVS

Хранит данные в браузере пользователя.

Позволяет создавать веб-приложения с расширенными возможностями запросов независимо от состояния сети

Без схем

Поддерживает транзакции ACID

Асинхронная обработка

Многоверсионный параллелизм

Пользователи могут удалять данные так же легко, как стирать cookies

Выбор браузера

Данные не могут быть зарегистрированы в IndexedDB, если их емкость превышает определенный уровень.

Объем данных, который может быть зарегистрирован в IndexedDB, зависит от среды.

FoundationDB

NoSQL с поддержкой ACID-транзакций.

Может работать с SQL.

Ключи сортируются.

Производительность 20 000 записей/с на одном ядре при использовании SSD-накопителей

Линейное масштабирование до 500 ядер

Чтение за 1 мс, запись за 5 мс

Возможность распределения/избыточности в кластерной конфигурации

В конфигурацию может быть включен как минимум один узел (без резервирования/добавления узлов в дальнейшем)

Конфигурация FDB по умолчанию работает плохо, поэтому требуется ее настройка


$ fdbcli --exec 'configure ssd'

$ fdbcli --exec 'writemode on'

$ fdbcli --exec 'getrangekeys \x00 \xff'

Ссылка: https://apple.github.io/foundationdb/command-line-interface.html

В бинарных релизах FoundationDB отключена

Зависит от версии FoundationDB, указанной в features

$ cargo feature surreal

   Доступные функции для `surreal`.

default = ["storage-rocksdb", "scripting", "http"].

http = ["surrealdb/http"].

scripting = ["surrealdb/scripting"].

storage-fdb = ["surrealdb/kv-fdb-6_3"].

storage-rocksdb = ["surrealdb/kv-rocksdb"]].

storage-tikv = ["surrealdb/kv-tikv"]


$ grep kv-fdb- lib/Cargo.toml

kv-fdb-5_1 = ["foundationdb/fdb-5_1", "kv-fdb"]

kv-fdb-5_2 = ["foundationdb/fdb-5_2", "kv-fdb"].

kv-fdb-6_0 = ["foundationdb/fdb-6_0", "kv-fdb"].

kv-fdb-6_1 = ["foundationdb/fdb-6_1", "kv-fdb"].

kv-fdb-6_2 = ["foundationdb/fdb-6_2", "kv-fdb"].

kv-fdb-6_3 = ["foundationdb/fdb-6_3", "kv-fdb"].

kv-fdb-7_0 = ["foundationdb/fdb-7_0", "kv-fdb"].

kv-fdb-7_1 = ["foundationdb/fdb-7_1", "kv-fdb"].

Доступная нотация является ошибкой в cargo-feature. Выпущены Pull-запросы.


Пересборка SurrealDB.

Настройка среды разработки rust (при необходимости).

$ curl -sSf https://sh.rustup.rs | sh

$ source $HOME/.cargo/env

$ rustup install stable

Клонируйте репозиторий SurrealDB

$ git clone https://github.com/surrealdb/surrealdb.git

$ cd surrealdb

Пересборка.

$ carrgo build -release -all-features # Не работает при 2 ГБ памяти.

# или

$ cargo build -release -features storage-fdb # TiKV будет отключен

TiKV против FoundationDB

Функциональность мониторинга FDB слаба


 $ fdbcli --exec 'status json'

TiKV с Prometheus для обращения к данным и Grafana для мониторинга состояния


FDB чувствительна к версиям


FDB по умолчанию хранится в памяти и требует изменения конфигурации


FDB реализована на C++, TiKV - на Rust


FDB может обслуживаться как минимум одним узлом, TiKV требует не менее трех узлов


Репликация RocksDB

Репликация в реальном времени возможна с помощью rocksplicator

https://github.com/pinterest/rocksplicator

Заметим, однако, что Rocksplicator - это архивный проект, который активно не поддерживается компанией Pinterest.

О хранении бинарных файлов.

SurrealDB не предназначена для такого использования.

Следует рассмотреть возможность использования объектного хранилища

GitHub - minio/minio: мультиоблачное хранилище объектов


GitHub - scality/Zenko: Zenko - мультиоблачный контроллер данных с открытым исходным кодом: владейте и контролируйте свои данные в любом облаке.


Синтаксис SurQL

INFO

INFO FOR [

        KV

        | NS | NAMESPACE

        | DATABASE

        | DATABASE

        | TABLE @table

];

DEFINE NAMESPACE | DATABASE ...

DEFINE [

        NAMESPACE @name

        DEFINE [ NAMESPACE | DATABASE @name

        | LOGIN @name ON [ NAMESPACE | DATABASE ]

            [ PASSWORD @pass | PASSHASH @hash ]

        | TOKEN @name ON [ NAMESPACE | DATABASE | SCOPE ]

            TYPE @algorithm VALUE @value

        | SCOPE @name [ SESSION @duration ]

            [ SIGNUP @expression ] [ SIGNIN @expression ]

        | EVENT @name ON [ TABLE ] @table

            WHEN @expression THEN @expression

@алгоритм

EDDSA, ES256, ES384, ES512, HS256, HS384, HS512,.

PS256, PS384, PS512, RS256, RS384, RS51


ОПРЕДЕЛЕНИЕ ТАБЛИЦЫ

DEFINE [

        | TABLE @name

                [DROP]

                [ SCHEMAFULL | SCHEMALESS ]

                [ AS SELECT @projections

                        FROM @tables

                        [ WHERE @condition ]

                        [ GROUP [ BY ] @groups ]

                ]

                [ PERMISSIONS [ NONE | FULL

                        | FOR select @expression

                        | FOR create @expression

                        | FOR update @expression

                        | FOR delete @expression

                ] ]

; ;

ОПРЕДЕЛЕНИЕ ПОЛЯ | ИНДЕКСА

ОПРЕДЕЛИТЬ [

        | FIELD @name ON [ TABLE ] @table

                [ TYPE @type ]

                [ VALUE @expression ]

                [ ASSERT @expression ]

                [ PERMISSIONS [ NONE | FULL

                        | FOR select @expression

                        | FOR create @expression

                        | FOR update @expression

                        | FOR delete @expression

                ]

        | INDEX @name ON [ TABLE ] @table [ FIELDS | COLUMNS ] @fields [ UNIQUE ]

] ;

CREATE

CREATE @targets

        [ CONTENT @value

          | SET @field = @value ...

        ]

        [ RETURN [ NONE | BEFORE | AFTER | DIFF | @projections ... ]

        [ TIMEOUT @duration ]

        [ PARALLEL ]

;

УДАЛИТЬ

УДАЛЕНИЕ [

        NAMESPACE @name

        | DATABASE @name

        | LOGIN @name ON [ NAMESPACE | DATABASE ]

        | TOKEN @name ON [ NAMESPACE | DATABASE ]

        | SCOPE @name

        | ТАБЛИЦА @name

        | TABLE @name

        | FIELD @name ON [ TABLE ] @table

        | INDEX @name ON [ TABLE ] @table

] ;

INSERT

INSERT [ IGNORE ] INTO @what

        [ @value

          | (@fields) VALUES (@values)

                [ ON DUPLICATE KEY UPDATE @field = @value ... ]

        ]

;

UPDATE

UPDATE @targets.

        [ CONTENT @value

          | MERGE @value

          | PATCH @value

          | SET @field = @value ...

        ]

        [ WHERE @condition ]

        [ RETURN [ NONE | BEFORE | AFTER | DIFF | @projections ... ]

        [ TIMEOUT @duration ]

        [ PARALLEL ]

;

DELETE

DELETE @targets ]

        [ WHERE @condition ]

        [ RETURN [ NONE | BEFORE | AFTER | DIFF | @projections ... ]

        [ TIMEOUT @duration ]

        [ PARALLEL ]

;

SELECT

SELECT @projections

        FROM @targets

        [ WHERE @condition ]

        [ SPLIT [ AT ] @field ... ]

        [ GROUP [ BY ] @field ... ]

        [ ORDER [ BY ]

                @field [RAND()| COLLATE| NUMERIC ] [ ASC | DESC ] ...

        ]

        [ LIMIT [ BY ] @limit ]

        [ START [ AT ] @start ]

        [ FETCH @field ... ]

        [ TIMEOUT @duration ]

        [ PARALLEL ]

;

USE

USE.

  [ NAMESPACE | NS ] @namespace ]

  [[ DATABASE | DB ] @database ] ;


поделиться



Зарегистрируйтесь сейчас и начните использовать Qiita с большим удобством!


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

В дальнейшем вы сможете эффективно перечитывать полезную информацию.

О функциях, которые можно использовать при входе в систему

iisaka51

@iisaka51(Гоити (Иисака) Юкава)

https://github.com/iisaka51

ссылка

rss

0 件のコメント:

コメントを投稿