Система управления базами данных Euphoria (EDS)


Введение

Многие пользователи Euphoria выражали заинтересованность в доступе к базам данных с помощью программ Euphoria, имея в виду не только доступ к базам широко известных систем управления базами данных из среды Euphoria, но и возможность создания простой и легкой в использовании собственной Euphoria-системы для хранения данных. EDS разработана для решения последней задачи. Она дает простую, очень гибкую систему управления базами данных, написанную на Euphoria и предназначенную для использования программами Euphoria.


Структура базы данных EDS

В EDS база данных - это единственный файл с расширением имени .edb. База данных EDS состоит из 0 или более таблиц. Каждая таблица имеет имя и содержит 0 или более записей. Каждая запись состоит из области ключа и области данных. Ключ может быть любым объектом Euphoria - атомом, рядом, глубоко вложенным рядом, если хотите. Сходным образом, данные могут быть также любым объектом Euphoria. На размер и структуру ключа и данных не налагается никаких ограничений. Внутри данной таблицы все ключи уникальны. Это означает, что в одной таблице невозможны две записи с одним и тем же ключом.

Записи в таблице размещены по возрастанию величин ключей. Когда вы обращаетесь к записи по ключу, применяется эффективный бинарный поиск. Возможен и прямой доступ к записям, без поиска, если вы знаете текущий номер записи в таблице. Записи имеют целочисленные номера от 1 до длины таблицы (текущего числа записей). Увеличивая номер записи, вы можете эффективно пройти все записи в порядке ключей. Однако имейте в виду, что номер записи может измениться при внесении новой записи или при удалении существующей записи.

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

Библиотека, содержащая EDS, database.e, без изменений будет работать под Windows, DOS, Linux или FreeBSD. Файлы баз данных EDS могут быть скопированы и использованы программами, исполняемыми под Linux/FreeBSD и DOS/Windows. Убедитесь, что при копировании файлов применяется двоичный режим, байт в байт, а не текстовый или "ASCII", при которых не исключены системные манипуляции символами конца строки.

 Пример:

    база_данных: "мои_данные.edb"
          первая таблица: "пароли"
               запись #1:  ключ: "джонс"   данные: "euphor123"
               запись #2:  ключ: "смит "   данные: "billgates"

          вторая таблица: "инструменты"
               запись #1:  ключ: 134525    данные: {"молоток", 15.95, 500}
               запись #2:  ключ: 134526    данные: {"пила", 25.95, 100}
               запись #3:  ключ: 134530    данные: {"отвёртка", 5.50, 1500}

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

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


Как получить доступ к данным

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

Вы можете сопоставить ключ и номер записи с помощью подпрограммы db_find_key(). Она осуществляет эффективный бинарный поиск. Большинство других подпрограмм, работающих на уровне записей, ожидает номер записи в качестве аргумента. Вы можете очень быстро получить доступ к любой записи, задав её номер. Вы можете получить доступ ко всем записям, начиная с записи номер 1 и задавая цикл с предельным значением, выдаваемым функцией db_table_size().


Как обновляется база данных?

Когда вы что-то удаляете из базы, например, запись, данные об освобождаемом пространстве подаются в перечень свободных участков для будущего использования. Смежные свободные участки объединяются в крупные свободные области. Когда требуется больше пространства, а в перечне свободных участков нет подходящего, файл увеличивается в размере. В настоящее время пока нет автоматизации уменьшения файла, но вы можете использовать подпрограмму db_compress(), чтобы полностью переписать базу данных, удалив свободные участки.


Безопасность и многопользовательский доступ

В данном выпуске EDS предусмотрен простой метод запирания всей базы данных для предотвращения небезопасного доступа со стороны других процессов.


О размерах баз данных

Внутренние пойнтеры, используемые в системе, занимают каждый 4 байта. Теоретически, это ограничивает размер файла базы данных 4-мя гигабайтами. Но на практике лимитом являются 2 Гб, что обусловлено особенностями различных файловых функций C, используемых Euphoria. Предельные размеры баз данных EDS могли бы быть увеличены сверх 2 Гб в будущем.

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

Бинарный поиск для ключей работает достаточно хорошо с большими таблицами.

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

На нижнем конце шкалы размеров возможно создание экстремально компактных баз данных без больших накладных расходов дискового пространства.


Ограничение ответственности

Не размещайте в этих базах ценные данные без резервирования баз. RDS не будет нести ответственности ни за какие повреждения или утрату данных.


Предупреждение

Файлы типа .edb являются двоичными файлами, это не текстовые файлы. Вы *должны* использовать ДВОИЧНЫЙ (бинарный) режим, передавая .edb-файлы через FTP с одной машины на другую. Вы должны также избегать загрузки .edb-файлов в редактор и перезаписывания их. Если вы открываете .edb-файл непосредственно с использованием подпрограммы Euphoria open(), что не рекомендуется, вы должны назначать двоичный режим, не текстовый. Отказ от выполнения этих правил может приводить, например, к неожиданному изменению в файле байтов 10 (перевод строки) и 13 (возврат каретки), что повлечёт за собой скрытую или не слишком скрытую порчу базы данных.


Подпрограммы управления базой данных

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

x - общий объект (object) (атом (atom) или ряд (sequence))
s - ряд (sequence)
a - атом (atom)
i - целое (integer)
fn - целое (integer), в качестве номера файла
st - строковый ряд, или односимвольный атом

db_create (бд_создать) - создает новую базу данных
db_open (бд_открыть) - открывает существующую базу данных
db_select (бд_выбрать) - назначает базу данных текущей
db_close (бд_закрыть) - закрывает базу данных
db_create_table (бд_создать_таблицу) - создает новую таблицу в базе данных
db_select_table (бд_выбрать_таблицу) - назначает таблицу текущей
db_rename_table (бд_переименовать_таблицу) - переименовывает таблицу
db_delete_table (бд_удалить_таблицу) - удаляет таблицу
db_table_list (бд_список_таблиц) - выдаёт список всех имен таблиц в базе данных
db_table_size (бд_размер_таблицы) - выдаёт число записей в текущей таблице
db_find_key (бд_найти_ключ) - быстро находит запись по значению ключа
db_record_key (бд_данные_ключа) - выдаёт ключевую область записи
db_record_data (бд_данные_записи) - выдаёт область данных записи
db_insert (бд_вставить) - вставляет новую запись в текущую таблицу
db_delete_record (бд_удалить_запись) - удаляет запись из текущей таблицы
db_replace_data (бд_заменить_данные) - заменяет область данных в записи
db_compress (бд_сжать) - сжимает базу данных
db_dump (бд_распечатать) - печатает содержимое базы данных
db_fatal_id (бд_фатальная_ошибка_N) - обрабатывает фатальные ошибки базы данных



db_create

Синтаксис: include database.e
i1 = db_create(s, i2)
Описание: Создает новую базу данных в файле, который задан путём s. Аргумент i2 обозначает режим замка, в котором запирается файл после его создания. Функция выдаёт результат i1, который является кодом, показывающим успешность или ошибочность операции. Величина для i2 может принимать значение или DB_LOCK_NO (бд_замка_нет), или DB_LOCK_EXCLUSIVE (бд_замок_исключительный). Величина i1 будет равна DB_OK, если новая база данных успешно создана. Эта база данных становится текущей базой, и к ней будут применяться все последующие операции, относящиеся к базам данных.
Комментарии: Если путь s не заканчивается расширением .edb, оно будет добавлено автоматически.

Если база данных уже существует, она не переписывается. db_create() выдаёт в этом случае DB_EXISTS_ALREADY (бд_уже_существует).

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

Пример:
if db_create("mydata", DB_LOCK_NO) != DB_OK then
    puts(2, "Не могу создать базу данных!\n")
    abort(1)
end if
См. также: db_open, db_close

db_open

Синтаксис: include database.e
i1 = db_open(s, i2)
Описание: Открывает существующую базу данных Euphoria. Файл, содержащий базу данных, задается в s. Фукция выдаёт i1, код, индицирующий успех или отказ операции. Аргумент i2 задаёт режим замка, который вы хотите включить, пока файл базы данных открыт вами. Открытая база данных становится текущей базой, к которой применяются все последующие операции.

Выдаваемые коды могут быть:

    global constant DB_OK = 0   -- успех
             DB_OPEN_FAIL = -1  -- не могу открыть файл бд
             DB_LOCK_FAIL = -3  -- не могу запереть файл бд
                                -- в запрошенном режиме
Комментарии: Имеются следующие режимы замка: DB_LOCK_NO (выключен, бд_нет_замка), DB_LOCK_SHARED (включен, общий доступ только на чтение, бд_замок_общий) и DB_LOCK_EXCLUSIVE (включен, бд_замок_исключительный, запрет чтения/записи). Режим DB_LOCK_SHARED поддерживается только на платформах Linux/FreeBSD. Он разрешает вам читать базу данных, но не позволяет ничего туда записывать. Если вы потребовали DB_LOCK_SHARED под WIN32 или DOS32, запрос будет обработан, как если бы вы потребовали DB_LOCK_EXCLUSIVE.

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

Программы DOS будут обычно получать сообщение "critical error" (критическая ошибка), если они попытаются получить доступ к базе, которая в данный момент заперта (термин "заперта" относится к режиму замка, т.е. открытая процессом база может быть запертой для других процессов).

Пример:
tries = 0
while 1 do
    err = db_open("mydata", DB_LOCK_SHARED)
    if err = DB_OK then
        exit
    elsif err = DB_LOCK_FAIL then
    	tries += 1
    	if tries > 10 then
            puts(2, "11 отказов, база данных заперта, повторите попытку входа позже... \n")
            abort(1)
    	else    
    	    sleep(5)
    	end if
    else
    	puts(2, "Не удалось открыть базу данных!\n")
    	abort(1)
    end if
end while

См. также: db_create, db_close

db_select

Синтаксис: include database.e
i = db_select(s)
Описание: Выбирает другую (уже открытую) базу данных в качестве текущей базы. Следующие операции системы управления будут выполняться с этой базой. Аргумент s является путем к файлу базы данных, по которому файл был открыт подпрограммами db_open() или db_create(). Выдаваемый код i индицирует успех (DB_OK) или неудачу операции.
Комментарии: Когда вы создаете (db_create) или открываете (db_open) базу данных, она автоматически становится текущей базой. Используйте db_select(), когда вам необходимо переходить между открытыми базами данных, к примеру, для копирования записей из одной в другую.

После выбора другой базы данных вы должны выбрать таблицу внутри этой базы данных с использованием подпрограммы db_select_table().

Пример:
if db_select("employees") != DB_OK then
    puts(2, "Не удалось выбрать базу данных employees\n")
end if
См. также: db_open

db_close

Синтаксис: include database.e
db_close()
Описание: Выключает замок и закрывает текущую базу данных.
Комментарии: Вызывайте эту подпрограмму, когда вы закончили работу с текущей базой данных. Любой из замков будет выключен, разрешая другим процессам доступ к файлу базы данных.
См. также: db_open

db_create_table

Синтаксис: include database.e
i = db_create_table(s)
Описание: Создает новую таблицу внутри текущей базы данных. Имя таблицы задается в ряде, s, и не должно совпадать с именем любой существующей таблицы в текущей базе.
Комментарии: Таблица, которую вы создаете, будет сначала иметь 0 записей. Она становится текущей таблицей.
Пример:
if db_create_table("my_new_table") != DB_OK then
    puts(2, "Не удалось создать my_new_table!\n")
end if
См. также: db_delete_table

db_select_table

Синтаксис: include database.e
i = db_select_table(s)
Описание: Таблица с именем, заданным в ряде s, становится текущей таблицей. Выдаваемый код, i, будет равен DB_OK, если таблица существует в текущей базе данных, в противном случае вы получите DB_OPEN_FAIL.
Комментарии: Все операции с записями в базе данных автоматически применяются к текущей таблице.
Пример:
if db_select_table("salary") != DB_OK then
    puts(2, "Не удалось найти таблицу salary!\n")
    abort(1)
end if
См. также: db_create_table, db_delete_table

db_rename_table

Синтаксис: include database.e
db_rename_table(s1, s2)
Описание: Переименовывается таблица в текущей базе данных. Текущее имя таблицы помещается в s1. Новое имя таблицы помещается в s2. /td>
Комментарии: Таблица, подлежащая переименованию, может быть текущей таблицей или какой-то иной таблицей в текущей базе данных. Если s1 не является именем таблицы в текущей базе данных или s2 является именем таблицы, уже существующей в текущей базе данных, вырабатывается сообщение об ошибке.
См. также: db_create_table db_select_table db_delete_table

db_delete_table

Синтаксис: include database.e
db_delete_table(s)
Описание: Удаляет таблицу из текущей базы данных. Имя удаляемой таблицы задается в ряде s.
Комментарии: Все записи удаляются и все дисковое пространство, занятое таблицей, освобождается. Если таблица была текущей таблицей, текущая таблица становится неопределённой.

Если таблицы с именем, заданным в s, не существует, не происходит ничего.

См. также: db_create_table db_select_table

db_table_list

Синтаксис: s = db_table_list()
Описание: Выдаёт ряд, содержащий все имена таблиц в текущей базе данных. Каждый элемент ряда s является рядом символов, из которых состоит имя таблицы.
Пример:
sequence names
names = db_table_list()
for i = 1 to length(names) do
    puts(1, names[i] & '\n')
end for
См. также: db_create_table

db_table_size

Синтаксис: include database.e
i = db_table_size()
Описание: Выдаёт текущее число записей в текущей таблице.
Пример:
-- просмотр всех записей в текущей таблице
for i = 1 to db_table_size() do
    if db_record_key(i) = 0 then
    	puts(1, "не найден ни один из ключей\n")
    	exit
    end if
end for
См. также: db_select_table

db_find_key

Синтаксис: include database.e
i = db_find_key(x)
Описание: Отыскивает запись в текущей таблице по величине ключа x. Если находит, выдаётся номер записи. Если не находит, выдаётся отрицательное число, которое соответствовало бы с обратным знаком номеру записи, если бы запись с заданной ключевой величиной была вставлена в базу данных.
Комментарии: Для отыскания ключа применяется эффективный бинарный поиск в текущей таблице. Число сравнений пропорционально логарифму числа записей в таблице.

Вы можете выбрать диапазон записей, задавая первую и последнюю ключевые величины этого диапазона. Если эти ключевые величины не существуют, вы получите отрицательное число, показывающее, где они могли бы быть, если бы существовали. Например, предположим, что вы хотите знать, какие записи имеют ключи, большие, чем "GGG", и меньшие, чем "MMM". Если функцией выдано для ключа "GGG" -5, это означает, что запись с ключом "GGG" могла бы быть вставлена как запись номер 5. Результат -27 для "MMM" означает, что запись с ключом "MMM" могла бы быть вставлена как запись номер 27. То есть, сразу видно, что все записи с номерами >= 5 и <= 27 допустимы.

Пример:
rec_num = db_find_key("Millennium")
if rec_num > 0 then
    ? db_record_key(rec_num)
    ? db_record_data(rec_num)
else
    puts(2, "Запись не найдена, но если вы вставите её,\n")
    printf(2, "её номер будет #%d\n", -rec_num)
end if
См. также: db_record_key, db_record_data, db_insert

db_record_key

Синтаксис: include database.e
x = db_record_key(i)
Описание: Выдаёт область ключа, соответствующую записи номер i в текущей таблице.
Комментарии: Каждая запись в базе данных Euphoria состоит из области ключа и области данных. Каждая из этих областей может быть любым атомом или рядом Euphoria.
Пример:
puts(1, "Для записи номер 6 ключ равен: ")
? db_record_key(6)
См. также: db_record_data

db_record_data

Синтаксис: include database.e
x = db_record_data(i)
Описание: Выдаёт область данных, соответствующую записи номер i в текущей таблице.
Комментарии: Каждая запись в базе данных Euphoria состоит из области ключа и области данных. Каждая из этих областей может быть любым атомом или рядом Euphoria.
Пример:
puts(1, "Для записи номер 6 данными является: ")
? db_record_data(6)
См. также: db_record_key

db_insert

Синтаксис: include database.e
i = db_insert(x1, x2)
Описание: Вставляет новую запись в текущую таблицу. Ключ записи должен находиться в x1, данные записи должны находиться в x2. И x1, и x2 могут быть любым из объектов данных Euphoria, атомов или рядов. Выдаваемый код, i, принимает значение DB_OK, если запись успешно вставлена.
Комментарии: Внутри таблицы все ключи должны быть уникальными. Операция db_insert() будет неуспешной с выдачей DB_EXISTS_ALREADY, если уже существует запись с той же самой ключевой величиной, что задана в x1.
Пример:
if db_insert("Smith", {"Peter", 100, 34.5}) != DB_OK then
    puts(2, "Не удалось вставить запись!\n")
end if
См. также: db_find_key, db_record_key, db_record_data

db_delete_record

Синтаксис: include database.e
db_delete_record(i)
Описание: Удаляет запись номер i из текущей таблицы.
Комментарии: Номер записи, i, должен быть типа integer с величиной от 1 до числа записей в текущей таблице.
Пример:
db_delete_record(55)
См. также: db_insert, db_table_size

db_replace_data

Синтаксис: include database.e
db_replace_data(i, x)
Описание: В текущей таблице заменяет область данных записи номер i на x. Объект x может быть любым атомом или рядом Euphoria.
Комментарии: Номер записи, i, должен быть целочисленной величиной от 1 до числа записей в текущей таблице.
Пример:
db_replace_data(67, {"Peter", 150, 34.5})

См. также: db_delete_record

db_compress

Синтаксис: include database.e
i = db_compress()
Описание: Сжимает текущую базу данных. Текущая база данных копируется в новый файл так, что все блоки с незанятым дисковым пространством удаляются. В случае успеха i устанавливается в DB_OK, а новый файл базы данных сохраняет старое имя. Текущая таблица будет неопределённой. В качестве резервной копии исходный несжатый файл будет переименован с расширением .t0 (или .t1, .t2 ,..., .t99). Если в результате какого-то крайне необычного случая сжатие не удалось, база данных сохраняется без изменений и резервная копия не создается. /td>
Комментарии: Когда вы удаляете пункты из базы данных, внутри файла остаются блоки свободного дискового пространства. Система сохраняет сведения об этих свободных блоках и старается использовать их для размещения тех новых данных, которые вы вставляете. Функция db_compress() копирует текущую базу данных без копирования свободных блоков. Поэтому размер файла базы данных может после сжатия уменьшиться.

Если имена резервных файлов достигают .t99, вы должны будете удалить некоторые из них.

Пример:
if db_compress() != DB_OK then
    puts(2, "Не удалось сжать базу данных!\n")
end if
См. также: db_create

db_dump

Синтаксис: include database.e
db_dump(fn, i)
Описание: Распечатывает содержимое уже открытой базы данных Euphoria. Содержимое распечатывается в файл или на устройство fn. Отображаются все записи во всех таблицах. Если i не-ноль, отображается также низкоуровневый по-байтный дамп. Этот низкоуровневый дамп будет понятен только тому, кто хорошо знаком с внутренним форматом базы данных Euphoria.
Пример:
if db_open("mydata", DB_LOCK_SHARED) != DB_OK then
    puts(2, "Не удалось открыть базу данных!\n")
    abort(1)
end if
fn = open("db.txt", "w")
db_dump(fn, 0)

См. также: db_open

db_fatal_id

Синтаксис: include database.e
db_fatal_id = i
Описание: Вы можете отлавливать определённые неустранимые ошибки базы данных, установив свой собственный обработчик таких ошибок. Просто присвойте глобальной переменной db_fatal_id (бд_фатальная_ошибка_N) значение номера одной из своих собственных подпрограмм, обрабатывающих ошибки. Подпрограмма должна принимать единственный аргумент, который является рядом. Когда эти определённые ошибки будут происходить, ваша подпрограмма будет вызвана с сообщением об ошибке, заданным как строковый аргумент. Ваша подпрограмма должна заканчиваться вызовом abort().
Пример:
procedure my_fatal(sequence msg)
    puts(2, "Произошла неустранимая ошибка - " & msg & '\n')
    abort(1)
end procedure
db_fatal_id = routine_id("my_fatal")
См. также: db_close