Глобалы MUMPS: Экстремальное программирование баз данных. Часть 1

в 13:41, , рубрики: cache, MUMPS, nosql

Примечание от переводчика.

Есть интересная технология в мире БД — MUMPS. Этот язык программирования и доступа к данным известен уже несколько десятилетий, отлажен и является взрослой проверенной технологией.

Приведу аналогию: если SQL можно сравнить с Бейсиком, то MUMPS больше похож на Си — даёт высочайшую производительность и гибкость.

Перед вами перевод первой части статьи «Extreme Database programming with MUMPS Globals». Если сообществу он покажется интересным, то последует перевод второй части.

Глобалы MUMPS: Экстремальное программирование баз данных

Часть 1. Глобалы: альтернатива реляционному подходу

Истинное сердце технологии MUMPS — механизм хранения данных. Он основан на том, что называется глобальными переменными, или (более популярное название) глобалами (Globals). Глобалы — невероятно простая концепция, однако, при этом невероятно мощная.

Многие люди не приемлют глобалы. Это примитивные структуры. В них нет элементов управления, систем подстраховки или расширенного функционала, предоставляемого «приличными» базами данных. В результате, MUMPS часто списывают со счетов как бесполезную, недостаточную, или, вообще, неправильную систему. Если вы тоже так делаете — вы игнорируете простой, удобный и очень гибкий механизм хранения данных. В умелых руках отсутствие лишнего багажа и нетребовательность к ресурсам дают невероятное ощущение свободы. В неумелых руках все то же самое может привести к катастрофе. Это немного напоминает экстремальный вид спорта, например, свободное скалолазание. Немногие «настоящие» спортсмены будут рекомендовать его — потому что это самый быстрый, эффективный и увлекательный способ взобраться вон на ту гору, если вы справитесь со скалолазанием без страховки. Однако, компании — производители альпинистского оборудования ни за что не порекомендуют этот спорт, поскольку это значит снизить спрос на свою собственную продукцию!

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

Это первая глава, в которой раскрываются основы глобалов. Вторая глава концентрируется на их применении в терминах, понятных программисту RDBMS/SQL. Если хотите, вы можете перейти сразу ко второй главе и вернуться к этому тексту позже, чтобы ознакомиться с основами.

Все системы MUMPS и их современные модификации, например, Caché, используют глобалы в качестве основы механизма хранения. Многие современные системы MUMPS и/или расширения предлагают более «привычный» взгляд на ключевые конструкции глобалов. Например, на глобалах можно создать представления (view) которые будут работать как объектная, реляционная или XML база данных. Более того, в теории та же самая физическая база данных на основе глобалов может быть логически представлена и использоваться в одной из этих форм (и даже во всех сразу). В итоге, появилось множество разработчиков, которые не в курсе, что в основе инструментов лежат глобалы. Они не знают, что это такое, как работает и как используется. Этот текст поможет вам узнать этот тайный и, мы надеемся, что вы согласитесь, очень увлекательный мир.

Так что же такое глобалы?

Вкратце, глобал — это постоянный, разреженный, динамический, многомерный массив, содержащий текстовые значения. MUMPS разрешает использовать как сохраняемые на диске (persistent), так и содержащиеся только в RAM многомерные массивы, известные как «локальные массивы».

В отличие от многих систем, MUMPS позволяет использовать одновременно буквенные и числовые индексы. Так что в MUMPS вы можете работать с подобным массивом:

Employee(company,country,office,employeeNumber) = employeeDetails 

и отдельной записью:

Employee(“MGW”,”UK”,”London”,1) = “Rob Tweed`Director`020 8404 3054”

В этом примере элементы данных, из которых состоит информация о сотруднике (имя, должность, номер телефона), прикрепляются друг к другу в сочетании с символом обратного апострофа в качестве разделителя. MUMPS никак не управляет и не контролирует то, как вы организуете свои структуры: нет никакой схемы или словаря данных, описывающего ваши записи. Это предоставляет невероятную гибкость и ускоряет разработку. Вы можете произвольно назначить один или более разделителей данных, которые будут разбивать текстовое значение записи массива на любое количество «полей». Максимальная общая длина одной записи массива зависит от имплементации MUMPS, однако в Caché она составляет до 32k. Длина строки является переменной, и, как вы понимаете, исходя из наличия символа-разделителя, длина отдельных полей — тоже переменная. Это делает глобалы MUMPS очень эффективным механизмом хранения данных (в отличие от таблиц): в файлах MUMPS на диске практически нет места, которое бы хранило пустые, неиспользуемые пространства.

В примере выше запись сотрудника содержится в том, что называется «локальным массивом». Если бы вы вышли из сессии MUMPS, массив бы исчез, так же, как массив PHP после исчезновения страницы или сессии.

А теперь начнем веселье! Чтобы хранить запись сотрудника на диске постоянно, то есть, в виде глобала, просто добавьте "^" перед именем массива:

^Employee(company,country,office,employeeNumber) = employeeDetails 

пример:

^Employee(“MGW”,”UK”,”London”,1) = “Rob Tweed`Director`020 8404 3054” Вот и все!

Чтобы создать такой элемент в глобале, вам нужно использовать команду MUMPS «set»:

set ^Employee(“MGW”,”UK”,”London”,1) = “Rob Tweed`Director`020 8404 3054”

Больше всего в MUMPS людей путает и пугает то, что основная часть команд может сокращаться до одной буквы (строчной или прописной), так что часто вы можете увидеть вместо команды, записанной выше, следующее:

s ^Employee(“MGW”,”UK”,”London”,1) = “Rob Tweed`Director`020 8404 3054” 

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

^Employee(“MGW”,”UK”,”London”,1)

Чтобы извлечь значение из глобала и присвоить его переменной обычно используют команду “set”, например:

Set companyRecord= ^Employee(“MGW”,”UK”,”London”,1)

Переменная companyRecord теперь содержит строковое значение: “Rob Tweed`Director`020 8404 3054”

Глобалы могут содержать столько индексов, сколько вы захотите, а эти индексы могут быть любым сочетанием текста и чисел (real или integer). Строковые индексы должны окружаться двойными кавычками, а численные — нет.

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

Чтобы удалить запись из глобала, вы можете использовать команду “kill”:

Kill ^Employee(“MGW”,”UK”,”London”,1)

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

Запомните, что ничто в MUMPS не поддерживает какую-либо методологию дизайна баз данных. Вы можете добавлять инструменты контроля и проверки, которые будут следить за логикой, единообразием и отсутствием ошибок в вашей базе. Это означает, что вы будете выполнять ту работу, которую за вас выполнили бы привычные СУБД. Однако, вы быстро поймете, что можно автоматизировать наиболее часто повторяющиеся задачи и легко управлять базой данных на основе MUMPS

Создание простой многоуровневой структуры

Вы можете использовать несколько размерностей в одном и том же глобале одновременно, например:

^Employee(“MGW”,”UK”,”London”) = 2
^Employee(“MGW”,”UK”,”London”,1) = “Rob Tweed`Director`020 8404 3054”
^Employee(“MGW”,”UK”,”London”,2) = “Chris Munt`Director`01737 371457”

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

Ссылки между разными глобалами должен указывать программист. MUMPS, «из коробки», не предоставляет автоматическую индексацию и не устанавливает перекрестные ссылки.

Для того, чтобы сделать быструю выборку по номеру телефона мы создадим глобал ^EmployeeTelephone, где индексами будут телефонные номера, а значениями индексы глобала ^Employee, указывающими на запись сотрудника:

^EmployeeTelephone(“020 8404 3054) = “MGW`UK`London`1”
^EmployeeTelephone(“01737 371457”) = “MGW`UK`London`2”

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

Пример:

S telNo=”020 8404 3054”
S indexData=^EmployeeTelephone(telNo) S company=$piece(indexData,”`”,1)
S country=$piece(indexData,”`”,2) S office=$piece(indexData,”`”,3)
S employeeNo=$piece(indexData,”`”,4)
S record=^Employee(company,country,office,employeeNo) S employee=$piece(record,”`”,1)

Обратите внимание на использование $piece — функции MUMPS для разделения значения на поля.

Одно из великих свойств MUMPS — вам не нужно ничего объявлять заранее. В SQL-мире изменение структуры данных требует изменения структуры таблиц (ALTER TABLE и т.п.). В MUMPS вы решаете, когда и как создать, изменить и удалять записи в глобале — все это автоматически и динамически контролируется только вами. Вы можете добавить дополнительные индексы и «поля» (pieces) в свои глобалы в любой момент безо всяких объявлений или чего-то подобного. Если вы хотите начать использовать другой глобал, просто начните это делать и он будет создан для вас динамически.

Создание, считывание и удаление записи в глобале

Вкратце, в MUMPS записи в глобалах создаются и извлекаются командой “Set”, и удаляются командой “Kill”.

  1. Создание записи в глобале:
    	Set ^Employee(“MGW”,”UK”,”London”,1)=“Rob Tweed`Director`020 8404 3054”
    	

    Этот код создает индекс в глобале и сохраняет запись на диск.

  2. Считывание записи из глобала
    	Set data=^Employee(“MGW”,”UK”,”London”,1)
    	

    Этот код извлекает элемент из глобала и помещает значение в локальную переменную с названием “data”.

  3. Удаление записи из глобала:
    	kill ^Employee(“MGW”,”UK”,”London”,1)
    	

Этот код удаляет указанную запись глобала с диска немедленно и навсегда. Осторожнее с командой Kill- ее невероятно легко и невероятно опасно использовать. Если вы укажете меньше индексов, чем есть в глобале — вы удалите все элементы индексируемые большим числом индексов. Если вы не укажете индекс вообще — вы удалите всю глобаль, пример:

Kill ^Employee(“MGW”,”UK”)

Эта строка удалит все записи всех офисов в Англии

Kill ^Employee

А эта команда удалит весь глобал ^Employee — сразу, навсегда и безвозвратно (если вы не сделали ее бекап).

Обход глобала

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

Этот функционал не будет интуитивно понятен программисту «традиционных» баз данных, но его стоит понять, поскольку он одновременно очень мощен и прост.

Функция $order работает на одном уровне индексации внутри глобала и возвращает значение следующего индекса в выборке, которое существует на этом уровне глобала. Вы можете указать начальное значение, и функция $order найдет следующее значение, которое находится в выбранной последовательности. Чтобы найти первый индекс на указанном уровне, используйте пустое начальное значение (“”). Чтобы найти первый индекс на указанном уровне, который начинается на “C”, используйте начальное значение, которое идет по порядку сразу перед “C”, например, “B~”.
Так, чтобы найти первого сотрудника в глобале:

S company=””
S company=$order(^Employee(company))

Переменная company теперь содержит значение первого индекса company в первой записи глобала ^Employee.

Когда найдено последнее значение, при следующем запуске функции $order вернет пустое значение. Так, если бы в нашем глобале была только одна компания и мы повторили $order:

S company=$order(^Employee(company))

Тогда переменная company содержала бы пустое значение (“”)
Чтобы получить и обработать все компании в глобале Employee, нужно создать цикл:

S company=””
For  s company=$order(^Employee(company)) quit:company=”” do
. ; сделать что-нибудь с company

Этот код демонстрирует интересные функции лаконичного кодирования MUMPS:

  • Команда For с двумя последующими пробелами устанавливает бесконечную петлю.
  • Quit:company=”” указывает условие выхода из цикла и использует конструкцию, известную как «пост-условие». Эта конструкция говорит «если значение компании пустое, выйти из цикла For». За Quit должно идти два пробела, если после команды идет любая другая команда.
  • “do” в конце строки означает выполнить код, который идет на следующем уровне «точки». “do” будет выполняться на каждой итерации цикла For, пока значение компании не пустое
  • Код, который нужно выполнять в каждом цикле, пишется после одиночной точки. В принципе, любая строка, начинающаяся с точки, формирует подпрограмму, которая выполняется командой “do” в конце второй строки в примере.

Так, мы задаем $order пустое значение, чтобы убедиться, что команда начнет с поиска первого индекса, хранящегося в глобале. Мы проходим через каждое значение, пока не закончатся сохраненные данные и получаем пустое значение, возвращенное $order, после чего мы завершаем цикл.

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

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

s company=””
for  s company=$order(^Employee(company)) quit:company=”” do
. s country=””
.  for  s country=$order(^Employee(company,country)) quit:country=”” do
. . s office=””
. . for  s office=$order(^Employee(company,country,office)) quit:office=”” do
. . . s employeeNo=””
. . . for  s employeeNo=$order(^Employee(company,country,office,employeeNo)) quit:employeeNo=”” do
. . . . s record=^Employee(company,country,office,employeeNo)
. . . . ; сделать что-нибудь с записью

Обратите внимание на то, каким образом мы обозначили уровни «точек», чтобы создать иерархию вложенных подпрограмм. Также, обратите внимание на то, как $order используется для того, чтобы обойти значения всех 4-х индексов глобала.

Прим. переводчика: в Caché для вложенных структур можно использовать фигурные скобки.

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

s company=”B~”
f  s company=$o(^Employee(company)) quit:$e(company,1) ’= ”C” do
. ; сделать что-нибудь с записью

Обратите внимание на функцию $extract (или $e). Она извлекает символ из указанной позиции значения строки: в данном случае первую букву названия компании. Также заметьте способ указания «не равно C»: он использует оператор MUMPS NOT, который записывается одиночной кавычкой (‘).

Этот цикл можно прочесть так:

  • Начальное значение $order — величина перед “C” в последовательной выборке
  • Запустить бесконечный цикл для поиска всех записей компаний в выборке
  • Если первая буква названия компании не “C”, выйти из цикла
  • В противном случае продолжить обрабатывать записи компаний.

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

Проверка существования записи в глобале

Вам часто нужно знать, существует ли конкретная запись в глобале. Для этого вы можете использовать функцию MUMPS $data:

if $data(^Employee(company)) do xxx

Строка читается как «если ^Employee(company) существует, тогда выполнить подпрограмму xxx». Функцию $data можно сокращать до $d.

$data вернет несколько разных значений.

  • Если данные существуют на указанном уровне индекса и нет подузлов, вернется значение 1
  • Если данные существуют на указанном уровне индекса и подузлы есть, вернется значение 11
  • Если данных не существует на указанном уровне индекса, но есть подузлы, вернется значение 10
  • Если данных не существует на указанном уровне индекса, вернется значение 0.

В MUMPS любое ненулевое значение при использовании логического оператора if оценивается как true. Таким образом, первые три значения, возвращенные $data (1, 10 и 11), будут считаться true. Последняя ситуация (нет данных и подузлов) оценивается как false.

В качестве примера рассмотрим следующий глобал:

^test=3
^test(“a”)=1
^test(“a”,”b”,”c”)=2
^test(“a”,”b”,”d”)=3

$data(^test) = 11
$data(^test(“a”,”b”)=10
$data(^test(“a”,”b”,”c”)=1
$data(^test(“b”)=0

Предотвращение ошибок типа «данные не определены»

Если вы попробуете извлечь несуществующую запись из глобала, MUMPS выдаст ошибку этапа выполнения (run-time error), например . Чтобы избежать этого, вы можете либо использовать функцию $data для проверки существования узла, либо использовать более удобную функцию $get. Это вернет значение глобального узла, если он существует, или пустое значение, если нет. Функцию $get можно сократить до $g

Так, основываясь на примере, который мы использовали в предыдущей секции с $data:

$get(^test) = 3
$get(^test(“a”,”b”)=”” ; поскольку на этом уровне индекса нет данных
$get(^test(“a”,”b”,”c”)=1
$get(^test(“b”)=””
$get(^nonExistentGlobal)=””

Просмотр глобалов

Caché и все остальные системы MUMPS включают в себя способы просмотра глобалов. Самый простой способ — команда ^%G для командной строки, которую вы обнаружите в Caché и некоторых других реализациях MUMPS. Запустите в сессии терминала следующее:

USER> D ^%G

Global ^ Employee

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

Employee()

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

Employee(“MGW”

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

Employee(“MGW”,)

Перечислит все вторые индексы записей сотрудников с первым индексом “MGW”

Caché предоставляет приложение для браузера с графическим интерфейсом под названием Caché Explorer, который позволяет просматривать и редактировать глобалы.

Резюме

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

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

MUMPS полагается на вас, программисты. Страховки нет, однако нет и границ того, чего можно достичь или как вы будете этого достигать. Вы поймете MUMPS — великолепная и захватывающая рабочая среда, благодаря эффективности программирования и выполнения команд.
Когда вы освоите глобалы MUMPS, вы, наверное, будете удивлены: «Почему же все базы данных не могут работать также!» Это очень просто, интуитивно понятно, гибко и по производительности превосходит любую реляционную базу данных. К тому же, MUMPS доступен для практически любой платформы и масштабируется до размера огромных систем — некоторые крупнейшие интерактивные системы в мире основаны на MUMPS, иногда с десятками тысяч одновременно работающих пользователей.

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

Автор: inetstar

Источник

Поделиться