Новшества в управлении данными
CLIP поддерживает хранение в MEMO-полях переменных любого типа, включая
объекты, массивы.
field->memo_field := {"A",1.00,date(),.t.,tbrowseNew()}
? field->memofield
Параллельно с функциями db*() имеется пакет функций rdd*() работающий
не с алиасами, а с указателями на открытые таблицы и связанные с ними
индексы. Для облегчения работы подобным образом написан
класс RDD
clip при dbUseArea на самом деле файлы не открывает в том понятии,
в котором это понимает DOS.
На самом деле открывается не файл, а на него создается
MEMORYMAP - отображение содержимого файла в виртуальном адресном
пространстве памяти ОС.
Т.е. clip обращается не к файловым операциям, а берет данные
прямо из памяти. И только, если система не дает этого сделать,
тогда идет чтение из файла.
Так вот чтобы брать много данных, надо дать процессу возможность
адресоваться к такому размеру памяти, в котором должны уместится
все используемые данные + индексы.
Такие ограничения/разрешения даются при помощи команды shell~а
ulimit -v.
Clip-программа сама пытается выставить ограничение по максимуму,
но это зависит от настроек этого самого ulimit. Вполне возможно,
что такое и не удаcтся сделать и тогда clip пишет в log-файл
сколько же ему удалось запросить про запас.
Эту память можно увидеть в программе top под названием shared.
Не надо пугаться, что для этого механизма требуется много физической
памяти. Это совсем не так.
Эта память используется одновременно несколькими процессами и она
одна для всех.
Например, ядро пользуется этим же механизмом для обеспечения
shared-code, т.е. программы грузятся в память и несколько программ
используют статический код, находящийся в одной и той же памяти.
Поэтому top, практически у всех программ, показывает некоторое
кол-во shared памяти.
В связи с этим получилось так, что некоторые операции, которые раньше
могли исполнятся только в excl режиме, теперь могут быть
выполнены и share-режиме. Например, zap.
Не советую, конечно, пользоваться этими возможностями, лучше писать
"классически".
Параллельно с пакетом функций db...(), которые управляют данными посредством
механизма alias & select, имеется пакет функций rdd....(), которые управляют
данными через указатель на файл. Например:
fd:=rddUseArea(.t.,file_name,......)
rddGoTop(fd)
rddSkip(fd,nskip)
ну и так далее......
/*
Есть RDD с аббревиатурой DBFMEM работает так же, как DBFNTX, но
не использует дисковых операций! При вызове закрывающих функций
для индексов и данных уничтожается память, в которой он существовал.
Очень рекомендую использовать для всяческих временных таблиц не
слишком большого размера - иначе можете влететь в длинюююююююющий своп.
*/
Чтобы использовать разные кодировки данных поставьте
set("DBF_CHARSET",code_page_file_name), при вызове
dbusearea эти установки будут учтены.
Имеются функции:
dbread() - возвращает объект со структурой, соответствующей
структуре dbf текущего select
Например:
use test // имеет поля field1,field2,....fieldn
rec=dbread()
? rec:field1, rec:field2, ....
dbwrite(rec) - записывает объект в dbf, равносилен конструкции
replace field1 with value1, field2 with value2, ....
Например:
rec=map()
или
rec=dbread()
rec:field1="asdf"
rec:field2=date()
dbwrite(rec)
dbappend(rec) - добавляет запись и записывает в нее объект.
Оторванные индексы (Independed Indices)
Оторванный индекс представляет собой обычный индексный файл.
Но ключи в него добавляются вручную. Фактически это сортированный
список ключей с некоторыми ассоциированными с ними данными.
Набор функций для манипулирования оторванными индексами включает в себя
функции создания/открытия/закрытия индекса/тега, функции
добавления/удаления ключей, а также набор функций для навигации
по ключам. В данный момент в качестве оторванного индекса можно
использовать только CDX.
Реализованы следующие функции:
II_CREATE([<driver>],<name>) -> index handle
создает пустой индекс (tagbag) без единого тега, используя указанный
трехбуквенный драйвер <driver> (например "CDX"). Если драйвер не указан,
используется драйвер индексов от текущего RDD (rddsetdefault()).
После создания индекс остается открытым.
Возвращается хендл для работы с этим индексом.
II_CREATETAG(<index handle>,<tag name>,<expr>) -> tag handle
создает в индексе новый тег типа возвращаемого <expr> значения.
Возвращается хендл для работы с вновь созданным тегом.
II_OPEN([<driver>],<name>) -> index handle
открывает индекс.
II_OPENTAG(<index handle>,<name>) -> tag handle
открывает тег.
II_CLOSE(<index handle>) -> NIL
закрывает индекс.
II_CLOSETAG(<tag handle>) -> NIL
закрывает тег.
II_ADDKEY(<tag handle>,<id>,<key>) -> NIL
добавляет ключ в тег. <id> - набор байт (для CDX'а - 4)
которые будут храниться в ассоциации с ключом. <id> должен
быть типа CHARACTER. Указатель текущего ключа устанавливается
на вновь добавленный ключ
II_DELKEY(<tag handle>) -> NIL
удаляет текущий ключ.
II_GOTOP(<tag handle>) -> NIL
устанавливает указатель текущего ключа на самый первый ключ
(ключ с наименьшим значением).
II_GOBOTTOM(<tag handle>) -> NIL
устанавливает указатель текущего ключа на самый последний ключ
(ключ с наибольшим значением).
II_BOF(<tag handle>) -> BOF
возвращает признак перемещения за первый ключ.
II_EOF(<tag handle>) -> EOF
возвращает признак перемещения за последний ключ.
II_ID(<tag handle>) -> associated data
возвращает строку байт ассоциированных с текущим ключом
(для CDX - четыре байта).
II_KEY(<tag handle>) -> key value
возвращает значение текущего ключа.
II_SKIP(<tag handle>,<count>) -> NIL
перемещает указатель текущего ключа вперед или назад.
SIX
В мемо-полях можно хранить данные любого типа, включая массивы и обЪекты.
Например:
dbcreate("test",{{"mmm","M",10,0}})
use test
append blank
? valtype(field->mmm) // "M"
field->mmm := 123
? valtype(field->mmm) // "N"
field->mmm := {1,2,3}
? valtype(field->mmm) // "A"
m := map()
m:fname := "Bob"
m:lname := "Smith"
field->mmm := m
? valtype(field->mmm) // "O"
Для хранения/восстановления обЪектов с методами ОО-модель предоставляет
возможность "регенерации" обЪектов.
См. описание ОО-модели.
VariFields
Тип "V" позволяет хранить в таблице строки любой длины. При этом
непосредственно в DBF-файле хранятся лишь первые символы строки, а остаток -
в FPT-файле. В DBF-файле также хранится 6 байт служебной информации.
Например:
dbcreate("test",{{"vchar","V",20,0}})
field->vchar := "123456789012345678901234567890"
? field->vchar // 123456789012345678901234567890
Если строка умещается в <длина V-поля>-2 символах, то она целиком записывается
в DBF. В противном случае в DBF запишется первые <длина V-поля>-6 символов,
а остаток в FPT.
В V-поле можно хранить даты (V-дата занимает на диске всего 3 байта вместо 8).
Для этого нужно указать длину V-поля равную 3.
Аналогично, в V-поле с длиной 4 можно хранить целые числа. Например:
a := {}
aadd(a,{"date","V",3,0})
aadd(a,{"int","V",4,0})
dbcreate("test",a)
use test
append blank
field->date := date()
field->int := 1234
? valtype(field->date),field->date // D 11/05/2001
? valtype(field->int),field->int // N 1234
Триггеры
Триггер - это пользовательская функция, которая вызывается Клипом при
возникновении следующих ситуаций:
EVENT_PREUSE - перед открытием базы данных
* EVENT_POSTUSE - после открытия базы данных
* EVENT_UPDATE - перед изменением индекса
EVENT_APPEND - перед добавлением записи
EVENT_DELETE - перед удалением записи
EVENT_RECALL - перед восстановлением записи
EVENT_PACK - перед PACK
EVENT_ZAP - перед ZAP
EVENT_PUT - перед записью данных в таблицу
* EVENT_GET - после чтения данных из таблицы
EVENT_PRECLOSE - перед закрытием базы данных
* EVENT_POSTCLOSE - после закрытия базы данных
EVENT_PREMEMOPACK - перед MEMOPACK
* EVENT_POSTMEMOPACK - после MEMOPACK
Триггеру передается 4 параметра:
- номер типа события (константы EVENT_* определены в six2clip.ch)
- номер рабочей области
- номер поля (только для событий EVENT_GET, EVENT_PUT)
- значение поля (только для событий EVENT_GET, EVENT_PUT)
Триггер должен возвращать логическое значение. Если триггер возвращает .T.
операция выполняется, в противном случае - нет. (Операции соответствующие
событиям, помеченным (*), выполняются в любом случае).
Например, триггер может иметь следующий вид:
function MyTrigger(nEvent,nArea,nFieldPos,xTrigVal)
do case
case nEvent == EVENT_DELETE
// Каскадное удаление записей из дочерней таблицы
DeleteChilds()
case nEvent == EVENT_ZAP
if alert("Are you sure?",{"Yes","No"}) != 1
return .f.
endif
case nEvent == EVENT_PUT
if FieldName(nFieldPos) == "AGE"
if xTrigVal < 18 .OR. xTrigVal > 100
alert("Bad age!")
return .f.
endif
endif
endcase
return .t.
Триггер устанавливается предложением TRIGGER команды USE или
функцией rm_setTrigger(). (Команда USE ... TRIGGER определена
в six2clip.ch). Например:
USE test TRIGGER "MyTrigger"
или
USE test
rm_setTrigger(EVENT_INSTALL,"MyTrigger")
Если вы создатите "триггер по умолчанию" (функцию rm_defTrigger())
он будет использоваться со всеми рабочими областями, на которые триггер
не устанавливался.
!!! Обратите внимание на то что SIX-овые фичи работают только с DBFCDX
© Ю.Хныкин, uri@itk.ru, 2000