Несмотря на доступность огромного выбора различных хранилищ данных NoSQL, реляционные базы данных остаются одним из самых популярных вариантов сохранения данных, несмотря на несоответствие импеданса между базами данных SQL и кодом приложения, помимо дополнительных усилий, необходимых для взаимодействия с любым тип базы данных, во многих случаях остается проблемой, и это особенно верно, когда нет необходимости во всех функциях, предоставляемых автономной СУБД, таких как серверное хранилище или многопользовательская поддержка, а нужен только способ локального сохранения данных .

В этой серии статей будет рассмотрен другой подход, в котором используется встраиваемый язык программирования Cell, имеющий встроенную поддержку реляционной модели. Вы можете написать схемы своей базы данных в Cell вместе со всей связанной логикой (как запросы, так и обновления), и на основе этого компилятор Cell сгенерирует соответствующий класс C # или Java, который вы можете включить в существующий проект. В дополнение к предоставлению всех функций, которые вы ожидаете от любой встроенной базы данных SQL, таких как постоянство, транзакции и проверки целостности данных, Cell предлагает следующие преимущества:

  • Обычно вообще не требуется выполнять объектно-реляционное сопоставление. Компилятор позаботится обо всех необходимых преобразованиях данных. При небольшом тщательном проектировании классы, сгенерированные компилятором Cell, могут быть почти так же просты в использовании, как и рукописные.
  • Когда вы реализуете логику своей базы данных, вы не ограничены в своих возможностях из-за отсутствия выразительности странного подъязыка данных, такого как SQL, но в вашем распоряжении есть язык программирования общего назначения, который позволяет вам реализовать любое поведение.
  • Сгенерированный код выполняется очень быстро, предлагая производительность, схожую с производительностью эквивалентного рукописного объектно-ориентированного кода C # или Java.
  • В вашем распоряжении гораздо лучшая модель данных, чем модель, предоставляемая SQL: среди прочего, столбцы ваших отношений / таблиц могут иметь любой определяемый пользователем тип, иерархии наследования могут быть смоделированы напрямую, а схемы могут быть определены по модульному принципу.
  • Сочетание универсальных возможностей программирования, скорости и очень гибкой системы типов позволяет Cell естественным образом моделировать наборы данных, которые вы обычно не храните в реляционной базе данных. Он также может быть альтернативой, например, базе данных графов, такой как Neo4j, или хранилищам документов, или просто записи и чтению непосредственно из / в файл JSON.

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

Однако одним из внутренних ограничений Cell является то, что его можно использовать только для создания баз данных в памяти: весь набор данных загружается в ОЗУ при запуске, и с этого момента к диску касаются только для сохранения изменений в данных. Для наборов данных, которые слишком велики для размещения в памяти, Cell в настоящее время не подходит.

База данных Northwind в Cell

В этой серии статей мы будем использовать базу данных Northwind, образец базы данных, используемый Microsoft для демонстрации возможностей SQL Server и Microsoft Access. Он содержит данные о продажах Northwind Traders, фиктивной компании по импорту / экспорту специализированных продуктов питания. Вот структура базы данных:

Вот как выглядит определение схемы базы данных в Cell:

Мы не будем здесь подробно описывать синтаксис (это тема для будущих публикаций этой серии), но мы выделим наиболее важные отличия от SQL. В качестве примера мы воспользуемся следующим подмножеством таблицы Товары:

Первое существенное отличие состоит в том, что, хотя это не сразу ясно из синтаксиса, данные, которые в SQL хранятся в одной таблице, разделены на несколько различных отношений в Cell. Первое - это унарное отношение (то есть таблица с одним столбцом, которая представляет собой просто набор) product, единственная цель которого - хранить идентификаторы всех товары в базе:

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

Значения типа ProductId - это просто целые числа с тегами. Если вы не знакомы с языками функционального программирования, такими как Haskell, F # или Elm, подумайте об этом как о более или менее эквиваленте структуры / класса с одним анонимным полем типа long в C # или Java:

Существует множество типов, подобных этому, в верхней части приведенного выше определения схемы (строки 1–9). Использование определяемых пользователем типов для идентификации сущностей в вашем домене - одна из вещей, которые позволяют моделировать иерархии наследования. Эти типы не несут никакой информации о соответствующих объектах, атрибуты которых хранятся в отдельных отношениях:

Каковы преимущества использования отношений атрибутов? Во-первых, «атомарными» отношениями гораздо легче манипулировать с помощью языка общего назначения. Мы скоро увидим, как это работает. С другой стороны, «широкие» таблицы в стиле SQL могут использоваться только для хранения однозначных атрибутов: многозначные (представьте, например, что у клиента есть несколько телефонных номеров или адресов) в любом случае должны храниться в отдельной таблице. Вам также понадобится проблемная функция, например NULL для необязательных атрибутов. С другой стороны, атомарные отношения могут единообразно кодировать любую комбинацию необязательных или обязательных, одно- или многозначных атрибутов, просто определяя соответствующие ключи и внешние ключи.

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

Составление базы данных

Теперь, когда мы определили схему базы данных, мы можем попробовать ее скомпилировать. Это сгенерирует следующий класс C #:

На данный момент существует всего несколько методов, которые сохраняют или загружают состояние базы данных в объект типа System.IO.Stream или из него. Cell сохраняет данные в настраиваемом, удобочитаемом текстовом формате. Думайте об этом как о разновидности JSON, но с отношениями и алгебраическими типами данных. В будущей версии также будет добавлена ​​возможность сохранять фактический JSON для лучшей совместимости и двоичную версию настраиваемого текстового формата для повышения эффективности.

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

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

Запрос базы данных

Пришло время написать реальный код. Начнем с этого простого SQL-запроса:

В Cell вам нужно написать метод для автомата Northwind, определенного выше:

Первая строка в теле метода (строка 4) является выражением понимания набора: он выполняет итерацию по всем идентификаторам сотрудников в отношении employee и для каждого из них создает кортеж из трех элементов, содержащий идентификатор, имя и фамилию сотрудника. Выражения first_name (e) и last_name (e), где e - идентификатор сотрудника, получить значение соответствующего атрибута. Вторая строка сортирует набор по фамилии, третье поле в кортеже.

Теперь мы можем перекомпилировать базу данных. Созданный класс C # теперь имеет новый метод SortedEmployeesNames ():

Сигнатура нового метода почти идентична сигнатуре метода Cell, от которого он происходит. Единственное отличие - это тип первого поля возвращаемых кортежей. Чтобы перемещать данные между Cell и C # частями базы кода, компилятор должен сопоставить каждый тип Cell с соответствующим типом C #. В некоторых случаях сопоставление тривиально: это то, что происходит в приведенном выше коде со строками, кортежами и массивами. Для тегированного значения, такого как EmployeeId, компилятор просто отбрасывает тег (который известен во время компиляции и, следовательно, не несет никакой информации) и возвращает значение, заключенное внутри.

Теперь мы попробуем кое-что посложнее: для каждого заказа мы хотим рассчитать общую сумму, которую нам нужно взимать. Это наш SQL:

и это версия Cell:

Версия Cell длиннее, но написана в более модульной форме. Это пригодится позже. Это соответствующие методы в сгенерированном классе C #:

Обратите внимание, что orders_totals возвращает записи вместо кортежей, которые по умолчанию сопоставляются с именованными кортежами в C #.

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

Это реализация Cell:

и это сигнатура соответствующего метода в сгенерированном классе C #:

Вы можете увидеть, как тип Date в ячейке был автоматически сопоставлен с System.DateTime в C #.

Следующие шаги

Это все для части 1. В части 2 мы увидим, как реализовать более сложные запросы, которые трудно выразить в SQL, но легко написать на языке общего назначения, и как мы может настраивать интерфейсы сгенерированных классов.

Весь код и примеры для частей 1 и 2 доступны на github для Windows или Linux / macOS. Вы можете запускать все запросы, показанные в этих сообщениях, а также легко реализовать свои собственные.