OberonCore https://forum.oberoncore.ru/ |
|
Использование Files и Stores для построения простейшей БД https://forum.oberoncore.ru/viewtopic.php?f=23&t=4396 |
Страница 1 из 1 |
Автор: | Иван Кузьмицкий [ Четверг, 27 Июнь, 2013 14:02 ] |
Заголовок сообщения: | Использование Files и Stores для построения простейшей БД |
Задача: хранить структурированные данные в файле. Для не очень больших объёмов вполне подойдёт .odc. Для хранения данных используем персистентные сущности (наследники Stores.Store). В каждом типе определяем процедуры Internalize, Externalize. Создаём экземпляр файла Files.File, подсоединяем к нему записыватель Stores.Writer и пишем в файл все наши сущности с помощью wr.WriteStore. Чтобы прочитать данные, теперь нужно подсоединить к файлу читателя Stores.Reader и читать из него с помощью rd.ReadStore. И вот тут некоторая закавыка. Stores.Reader не умеет читать из файла нулевой длины. После ConnectTo(f), если f.Length() = 0, вызов rd.ReadStore() приводит к трапу. То есть, пытаться сразу читать персистентные объекты из файла некорректно. Плюс, легко нарваться на попытку чтения несуществующего Store. Хотелось бы в этом треде осветить вопрос использования файлов таким образом. Что правильно делать, что неправильно, какие возможности можно использовать и каким образом. В итоге, я планирую собрать из треда всю информацию в отдельную статью-руководство. |
Автор: | albobin [ Четверг, 27 Июнь, 2013 14:49 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Тему лучше бы было назвать без "БД" : Использование Files и Stores для хранения структурированных данных в файле. |
Автор: | Иван Кузьмицкий [ Четверг, 27 Июнь, 2013 14:56 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
К.Дж.Дейт даёт такое определение: Цитата: База данных состоит из некоторого набора постоянных данных, которые используются прикладными системами Структурированные данные в файле и есть база данных. То, о чём думаете Вы, называется "система управления базами данных", или СУБД. В данной теме стоит вопрос создания простейшей БД, но для работы с сохранёнными данными придётся написать ядро системы управления этими данными.
|
Автор: | Иван Кузьмицкий [ Четверг, 27 Июнь, 2013 15:03 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Написал концептуальный пример. С помощью Init создаём и заполняем списки, с помощью Write пишем их в файл, а с помощью Read пытаемся прочитать. И как раз процедура Read приводит к трапу. Она неправильная. Код: MODULE PrivFilesDb;
IMPORT Log := StdLog, Files, Stores; CONST repLocation = 'Priv'; repName = 'repo.odc'; minVersion = 0; maxVersion = 1; TYPE (* Сохраняем в файл структуру вида Item1 Element1-1 Element1-2 Element1-3 ... Item2 Element2-1 Element2-2 Element2-3 ... ... *) Name = ARRAY 50 OF CHAR; Element = POINTER TO RECORD (Stores.Store) name: Name; next: Element END; Item = POINTER TO RECORD (Stores.Store) name: Name; e: Element; next: Item END; VAR repo: Item; f: Files.File; (* Element *) PROCEDURE (s: Element) Externalize- (VAR wr: Stores.Writer); BEGIN wr.WriteVersion(maxVersion); wr.WriteString(s.name); wr.WriteStore(s.next) END Externalize; PROCEDURE (s: Element) Internalize- (VAR rd: Stores.Reader); VAR version: INTEGER; store: Stores.Store; BEGIN rd.ReadVersion(minVersion, maxVersion, version); IF ~rd.cancelled THEN rd.ReadString(s.name); rd.ReadStore(store); IF store # NIL THEN s.next := store(Element) ELSE s.next := NIL END; END END Internalize; (* Item *) PROCEDURE (s: Item) Externalize- (VAR wr: Stores.Writer); BEGIN wr.WriteVersion(maxVersion); wr.WriteString(s.name); wr.WriteStore(s.e); wr.WriteStore(s.next) END Externalize; PROCEDURE (s: Item) Internalize- (VAR rd: Stores.Reader); VAR version: INTEGER; store: Stores.Store; BEGIN rd.ReadVersion(minVersion, maxVersion, version); IF ~rd.cancelled THEN rd.ReadString(s.name); rd.ReadStore(store); IF store # NIL THEN s.e := store(Element) ELSE s.e := NIL END; rd.ReadStore(store); IF store # NIL THEN s.next := store(Item) ELSE s.next := NIL END; END END Internalize; PROCEDURE AddElemTo (name: Name); VAR new: Element; BEGIN NEW(new); new.name := name; IF repo.e = NIL THEN repo.e := new ELSE new.next := repo.e; repo.e := new END END AddElemTo; PROCEDURE NewItem (name: Name); VAR new: Item; BEGIN NEW(new); new.name := name; IF repo = NIL THEN repo := new ELSE new.next := repo; repo := new END END NewItem; PROCEDURE OpenFile; VAR loc: Files.Locator; res: INTEGER; BEGIN loc := Files.dir.This(repLocation); IF loc.res = 0 THEN (* попытка открыть уже существующий реп *) f := Files.dir.Old(loc, repName, Files.exclusive); IF (f = NIL) & (loc.res = 2) THEN (* репа нет, создаём его *) f := Files.dir.New(loc, Files.dontAsk); f.Register(repName, '', Files.ask, res); IF res = 0 THEN Log.String('PrivFilesDb: репозиторий создан.'); Log.Ln; ELSE Log.String('PrivFilesDb: ошибка создания репозитория:'); Log.Int(res); Log.Ln; END; (* повторно открываем *) f := Files.dir.Old(loc, repName, Files.exclusive); ELSIF f # NIL THEN Log.String('PrivFilesDb: репозиторий открыт'); Log.Ln; END ELSE Log.String('PrivFilesDb: неверный каталог реп-я.'); Log.Ln; END END OpenFile; PROCEDURE Init*; BEGIN NewItem('Second Item'); AddElemTo('Element 2-3'); AddElemTo('Element 2-2'); AddElemTo('Element 2-1'); NewItem('First Item'); AddElemTo('Element 1-3'); AddElemTo('Element 1-2'); AddElemTo('Element 1-1'); OpenFile; END Init; PROCEDURE Write*; VAR wr: Stores.Writer; item: Item; BEGIN wr.ConnectTo(f); item := repo; WHILE item # NIL DO wr.WriteStore(item); item := item.next END END Write; PROCEDURE Read*; VAR rd: Stores.Reader; st: Stores.Store; BEGIN rd.ConnectTo(f); rd.SetPos(0); rd.ReadStore(st); END Read; CLOSE f.Close END PrivFilesDb. 1) Создадим и заполним данными наш репозиторий, заодно подключимся к файлу PrivFilesDb.Init 2) Запишем данные в файл PrivFilesDb.Write 3) Прочитаем данные из файла PrivFilesDb.Read |
Автор: | Иван Кузьмицкий [ Четверг, 27 Июнь, 2013 15:06 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Кстати, процедура Write тоже не совсем правильная, она может быть куда проще, ведь все Item записываются рекурсивно. Поэтому достаточно один раз вызвать WriteStore, а список Item запишется каскадным вызовом методов Internalize: Код: PROCEDURE Write*;
VAR wr: Stores.Writer; BEGIN wr.ConnectTo(f); wr.WriteStore(repo) END Write; |
Автор: | Иван Кузьмицкий [ Четверг, 27 Июнь, 2013 15:29 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Переписал процедуру Read таким образом, чтобы прочитать ранее записанные данные из файла. Заодно убрал лишний вызов rd.SetPos(0), ведь Stores.Reader делает это сам в методе ConnectTo. Кроме этого, попытка прочесть персистентный объект из пустого файла приведёт к трапу, ведь метод ReadStore не занимается проверкой конца файла перед чтением. Поэтому необходимо убедиться в том, что файл не пустой. Код: PROCEDURE Read*; Для пущего удобства, надо ещё экспортировать OpenFile и использовать следующие сценарии:VAR rd: Stores.Reader; st: Stores.Store; BEGIN repo := NIL; rd.ConnectTo(f); IF f.Length() > 0 THEN rd.ReadStore(st) END; IF st # NIL THEN repo := st(Item) END END Read; Запись в файл 1) Создадим и заполним данными наш репозиторий, заодно подключимся к файлу PrivFilesDb.Init 2) Запишем данные в файл PrivFilesDb.Write Чтение из файла 1) Подключение к файлу-репозиторию PrivFilesDb.OpenFile 3) Прочитаем данные из файла PrivFilesDb.Read |
Автор: | Иван Кузьмицкий [ Четверг, 27 Июнь, 2013 18:05 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
У метода rd.ReadStore есть одна особенность - он не взводит у бегунка флаг конца файла rd.rider.eof. Если очередной вызов rd.ReadStore прочитал последнюю запись в файле, то следующая попытка ReadStore приведёт к трапу, даже если вы перед этим проверите конец файла: Код: PROCEDURE Read*; VAR rd: Stores.Reader; st: Stores.Store; BEGIN repo := NIL; rd.ConnectTo(f); IF f.Length() > 0 THEN rd.ReadStore(st); (* попытка прочитать несуществующий элемент *) IF ~rd.rider.eof THEN rd.ReadStore(st) END END; IF st # NIL THEN repo := st(Item) END END Read; Контролировать конец файла можно только проверкой текущей позиции бегунка относительно размера всего файла. Вот корректный алгоритм, который не прочитает несуществующую запись: Код: PROCEDURE Read*;
VAR rd: Stores.Reader; st: Stores.Store; BEGIN repo := NIL; rd.ConnectTo(f); IF f.Length() > 0 THEN rd.ReadStore(st); (* контроль позиции бегунка *) IF rd.Pos() < f.Length() THEN rd.ReadStore(st) END END; IF st # NIL THEN repo := st(Item) END END Read; |
Автор: | Александр Ильин [ Четверг, 27 Июнь, 2013 19:44 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
А как вам вариант иметь один Store на файл? Через методы этого корневого Store можно было бы узнать количество записей в файле и получить нужную из них по индексу. В этом случае либо размер файла = 0, и ничего читать не нужно, либо корневой Store будет прочитан, и он в свою очередь сообщит вам количество записей в файле. |
Автор: | Иван Кузьмицкий [ Четверг, 27 Июнь, 2013 20:24 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Как я понимаю это дело, тут наблюдается концепция "1 файл = 1 домен = граф Stores". При этом нет требования записывать в файл единственный корневой Store. В принципе, такая схема вполне понятна и прозрачна, как и применение шаблона Carrier-Rider-Mapper для чтения Stores. Сейчас меня смущают только некоторые невнятности с флагом rider.eof, которые затрудняют "чистоту" кода и проблема с ограничением на глубину рекурсии. Логика произвольного доступа к сохранённым Stores вроде бы просматривается. Так-то да, тема достаточно интересная, ведь у нас тут не просто структурки в файле лежат, а сразу произвольный граф. Что можно с этим поделать полезного, пока не знаю! Ведь граф может загрузиться из файла сразу весь, а это неумолимо накладывает ограничения на объёмы. |
Автор: | Иван Кузьмицкий [ Вторник, 02 Июль, 2013 15:33 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Всё-таки графы Stores не подходят для БД в плане частичной выборки данных. Если каждая Store экстернализируется по отдельности, тогда ещё можно выкрутиться, но как только вы в Internalize\Externalize делаете rd.ReadStore\wr.WriteStore, то произвольное чтение отдельных Store и тем более независимая запись становятся невозможными (попробовал писать связанные Store по отдельности и получил массу неприятных эффектов, связанных с нарушением структуры данных). Тут уж либо весь граф целиком читать-писать, либо что-то другое использовать для хранения объектов. Ну а если граф относительно небольшой (насколько небольшой, не могу сказать, не проверял на больших объёмах - но очевидно, по-моему, что такое решение плохо масштабируется), то его сохранение и чтение становятся очень простым делом! |
Автор: | Илья Ермаков [ Вторник, 02 Июль, 2013 19:22 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Иван Кузьмицкий писал(а): Ведь граф может загрузиться из файла сразу весь, а это неумолимо накладывает ограничения на объёмы. В принципе, никто не заставляет читать сразу всё... Допустим, TextModel не читает всё в память, а запоминает Files.Reader и потом обращается к нужным участкам файла. Т.е. можно представить корневой управляющий файлом Store, который запоминает Reader и потом уже по запросу считывает другие сторы... |
Автор: | Иван Кузьмицкий [ Среда, 03 Июль, 2013 09:17 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Для этого надо переделывать модуль Stores... |
Автор: | Илья Ермаков [ Среда, 03 Июль, 2013 10:24 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
да нет, почему? Просто соответствующее поведение Extze/Intze у главной управляющей сторы... |
Автор: | Иван Кузьмицкий [ Среда, 03 Июль, 2013 10:31 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Так ведь вызовы ReadStore в Internalize каскадные. Чтобы прочесть из файла стору - надо сперва прочесть предыдущую, прочитанная инициирует чтение последующей, и так из файла поднимутся все ветви графа. А если у вас, допустим, стора после себя записывает каскадную цепочку из 5 элементов, затем вы её прочитали, убрали один элемент и записываете заново - в файле же остался пятый элемент, он никуда не девается. У меня как раз на этом этапе начались трапы, порождаемые ReadStore и связанные с чтением невалидных элементов из файла. Поэтому я и думаю, что для частичной записи\чтения надо перетряхнуть внутренности Store. |
Автор: | Илья Ермаков [ Среда, 03 Июль, 2013 10:42 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Иван Кузьмицкий писал(а): Так ведь вызовы ReadStore в Internalize каскадные. Ну так можно между вызовами явно управлять позицией ридера! Цитата: Чтобы прочесть из файла стору - надо сперва прочесть предыдущую, прочитанная инициирует чтение последующей, и так из файла поднимутся все ветви графа. Так это потому, что Вы выбрали рекурсивную загрузку последовательности (которая, вообще, и к переполнению стека может привести). У меня обычно родительская стора своих детей записывает/загружает в цикле. Цитата: А если у вас, допустим, стора после себя записывает каскадную цепочку из 5 элементов, затем вы её прочитали, убрали один элемент и записываете заново - в файле же остался пятый элемент, он никуда не девается. [/quote][/quote]Да, вот если мы динамически изменяем файл, уже сложнее. Здесь уже скорее нужно поведение типа copy-on-write, с периодическим полным пересохранением в компактном виде. |
Автор: | Иван Кузьмицкий [ Среда, 03 Июль, 2013 11:20 ] |
Заголовок сообщения: | Re: Использование Files и Stores для построения простейшей Б |
Рекурсивную загрузку я выбрал из-за желания поэкспериментировать, пощупать граничные условия... Вначале, да, делал сохранение в цикле, заодно с произвольной записью в файл. Получил трапы, задумался, и решил довести концепт до края, так сказать Рекурсия крайне проста в реализации и очень наглядна, требует минимум усилий. Вот статистика поднакопится, и либо файл начнёт тормозить, либо рекурсия упрётся в переполнение стека, вот тогда я и зафиксирую предел для такого простого решения! |
Страница 1 из 1 | Часовой пояс: UTC + 3 часа |
Powered by phpBB® Forum Software © phpBB Group https://www.phpbb.com/ |