ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/attrdb.b/
implement Attrdb;
#
# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
#
include "sys.m";
sys: Sys;
include "bufio.m";
bufio: Bufio;
Iobuf: import bufio;
include "attrdb.m";
init(): string
{
sys = load Sys Sys->PATH;
bufio = load Bufio Bufio->PATH;
if(bufio == nil)
return sys->sprint("can't load Bufio: %r");
return nil;
}
parseentry(s: string, lno: int): (ref Dbentry, int, string)
{
(nil, flds) := sys->tokenize(s, "\n");
lines: list of ref Tuples;
errs: string;
for(; flds != nil; flds = tl flds){
(ts, err) := parseline(hd flds, lno);
if(ts != nil)
lines = ts :: lines;
else if(err != nil && errs == nil)
errs = err;
lno++;
}
return (ref Dbentry(0, lines), lno, errs);
}
parseline(s: string, lno: int): (ref Tuples, string)
{
attrs: list of ref Attr;
quote := 0;
word := "";
lastword := "";
name := "";
Line:
for(i := 0; i < len s; i++) {
if(quote) {
if(s[i] == quote) {
if(i+1 >= len s || s[i+1] != quote){
quote = 0;
continue;
}
i++;
}
word[len word] = s[i];
continue;
}
case s[i] {
'\'' or '\"' =>
quote = s[i];
'#' =>
break Line;
' ' or '\t' or '\n' =>
if(word == nil)
continue;
if(lastword != nil) {
# lastword space word space
attrs = ref Attr(lastword, nil, 0) :: attrs;
}
lastword = word;
word = nil;
if(name != nil) {
# name = lastword space
attrs = ref Attr(name, lastword, 0) :: attrs;
name = lastword = nil;
}
'=' =>
if(lastword == nil) {
# word=
lastword = word;
word = nil;
}
if(word != nil) {
# lastword word=
attrs = ref Attr(lastword, nil, 0) :: attrs;
lastword = word;
word = nil;
}
if(lastword == nil)
return (nil, "empty name");
name = lastword;
lastword = nil;
* =>
word[len word] = s[i];
}
}
if(quote)
return (nil, "missing quote");
if(lastword == nil) {
lastword = word;
word = nil;
}
if(name == nil) {
name = lastword;
lastword = nil;
}
if(name != nil)
attrs = ref Attr(name, lastword, 0) :: attrs;
if(attrs == nil)
return (nil, nil);
return (ref Tuples(lno, rev(attrs)), nil);
}
Tuples.hasattr(ts: self ref Tuples, attr: string): int
{
for(pl := ts.pairs; pl != nil; pl = tl pl){
a := hd pl;
if(a.attr == attr)
return 1;
}
return 0;
}
Tuples.haspair(ts: self ref Tuples, attr: string, value: string): int
{
for(pl := ts.pairs; pl != nil; pl = tl pl){
a := hd pl;
if(a.attr == attr && a.val == value)
return 1;
}
return 0;
}
Tuples.find(ts: self ref Tuples, attr: string): list of ref Attr
{
ra: list of ref Attr;
for(pl := ts.pairs; pl != nil; pl = tl pl){
a := hd pl;
if(a.attr == attr)
ra = a :: ra;
}
return rev(ra);
}
Tuples.findbyattr(ts: self ref Tuples, attr: string, value: string, rattr: string): list of ref Attr
{
if(ts.haspair(attr, value))
return ts.find(rattr);
return nil;
}
Dbentry.find(e: self ref Dbentry, attr: string): list of (ref Tuples, list of ref Attr)
{
rrt: list of (ref Tuples, list of ref Attr);
for(lines := e.lines; lines != nil; lines = tl lines){
l := hd lines;
if((ra := l.find(attr)) != nil)
rrt = (l, rev(ra)) :: rrt;
}
rt: list of (ref Tuples, list of ref Attr);
for(; rrt != nil; rrt = tl rrt)
rt = hd rrt :: rt;
return rt;
}
Dbentry.findfirst(e: self ref Dbentry, attr: string): string
{
for(lines := e.lines; lines != nil; lines = tl lines){
l := hd lines;
for(pl := l.pairs; pl != nil; pl = tl pl)
if((hd pl).attr == attr)
return (hd pl).val;
}
return nil;
}
Dbentry.findpair(e: self ref Dbentry, attr: string, value: string): list of ref Tuples
{
rts: list of ref Tuples;
for(lines := e.lines; lines != nil; lines = tl lines){
l := hd lines;
if(l.haspair(attr, value))
rts = l :: rts;
}
for(; rts != nil; rts = tl rts)
lines = hd rts :: lines;
return lines;
}
Dbentry.findbyattr(e: self ref Dbentry, attr: string, value: string, rattr: string): list of (ref Tuples, list of ref Attr)
{
rm: list of (ref Tuples, list of ref Attr); # lines with attr=value and rattr
rnm: list of (ref Tuples, list of ref Attr); # lines with rattr alone
for(lines := e.lines; lines != nil; lines = tl lines){
l := hd lines;
ra: list of ref Attr = nil;
match := 0;
for(pl := l.pairs; pl != nil; pl = tl pl){
a := hd pl;
if(a.attr == attr && a.val == value)
match = 1;
if(a.attr == rattr)
ra = a :: ra;
}
if(ra != nil){
if(match)
rm = (l, rev(ra)) :: rm;
else
rnm = (l, rev(ra)) :: rnm;
}
}
rt: list of (ref Tuples, list of ref Attr);
for(; rnm != nil; rnm = tl rnm)
rt = hd rnm :: rt;
for(; rm != nil; rm = tl rm)
rt = hd rm :: rt;
return rt;
}
Dbf.open(path: string): ref Dbf
{
df := ref Dbf;
df.lockc = chan[1] of int;
df.fd = bufio->open(path, Bufio->OREAD);
if(df.fd == nil)
return nil;
df.name = path;
(ok, d) := sys->fstat(df.fd.fd);
if(ok >= 0)
df.dir = ref d;
# TO DO: indices
return df;
}
Dbf.sopen(data: string): ref Dbf
{
df := ref Dbf;
df.lockc = chan[1] of int;
df.fd = bufio->sopen(data);
if(df.fd == nil)
return nil;
df.name = nil;
df.dir = nil;
return df;
}
Dbf.reopen(df: self ref Dbf): int
{
lock(df);
if(df.name == nil){
unlock(df);
return 0;
}
fd := bufio->open(df.name, Bufio->OREAD);
if(fd == nil){
unlock(df);
return -1;
}
df.fd = fd;
df.dir = nil;
(ok, d) := sys->fstat(fd.fd);
if(ok >= 0)
df.dir = ref d;
# TO DO: cache, hash tables
unlock(df);
return 0;
}
Dbf.changed(df: self ref Dbf): int
{
r: int;
lock(df);
if(df.name == nil){
unlock(df);
return 0;
}
(ok, d) := sys->stat(df.name);
if(ok < 0)
r = df.fd != nil || df.dir == nil;
else
r = df.dir == nil || !samefile(*df.dir, d);
unlock(df);
return r;
}
samefile(d1, d2: Sys->Dir): int
{
# ``it was black ... it was white! it was dark ... it was light! ah yes, i remember it well...''
return d1.dev==d2.dev && d1.dtype==d2.dtype &&
d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers &&
d1.mtime == d2.mtime;
}
flatten(ts: list of (ref Tuples, list of ref Attr), attr: string): list of ref Attr
{
l: list of ref Attr;
for(; ts != nil; ts = tl ts){
(line, nil) := hd ts;
t := line.find(attr);
for(; t != nil; t = tl t)
l = hd t :: l;
}
return rev(l);
}
Db.open(path: string): ref Db
{
df := Dbf.open(path);
if(df == nil)
return nil;
db := ref Db(df :: nil);
(e, nil) := db.findpair(nil, "database", "");
if(e != nil){
files := flatten(e.find("file"), "file");
if(files != nil){
dbs: list of ref Dbf;
for(; files != nil; files = tl files){
name := (hd files).val;
if(name == path && df != nil){
dbs = df :: dbs;
df = nil;
}else if((tf := Dbf.open(name)) != nil)
dbs = tf :: dbs;
}
db.dbs = rev(dbs);
if(df != nil)
db.dbs = df :: db.dbs;
}
}
return db;
}
Db.sopen(data: string): ref Db
{
df := Dbf.sopen(data);
if(df == nil)
return nil;
return ref Db(df :: nil);
}
Db.append(db1: self ref Db, db2: ref Db): ref Db
{
if(db1 == nil)
return db2;
if(db2 == nil)
return db1;
dbs := db2.dbs;
for(rl := rev(db1.dbs); rl != nil; rl = tl rl)
dbs = hd rl :: dbs;
return ref Db(dbs);
}
Db.reopen(db: self ref Db): int
{
f := 0;
for(dbs := db.dbs; dbs != nil; dbs = tl dbs)
if((hd dbs).reopen() < 0)
f = -1;
return f;
}
Db.changed(db: self ref Db): int
{
f := 0;
for(dbs := db.dbs; dbs != nil; dbs = tl dbs)
f |= (hd dbs).changed();
return f;
}
isentry(l: string): int
{
return l!=nil && l[0]!='\t' && l[0]!='\n' && l[0]!=' ' && l[0]!='#';
}
Dbf.readentry(dbf: self ref Dbf, offset: int, attr: string, value: string, useval: int): (ref Dbentry, int, int)
{
lock(dbf);
fd := dbf.fd;
fd.seek(big offset, 0);
lines: list of ref Tuples;
match := attr == nil;
while((l := fd.gets('\n')) != nil){
while(isentry(l)){
lines = nil;
do{
offset = int fd.offset();
(t, nil) := parseline(l, 0);
if(t != nil){
lines = t :: lines;
if(!match){
if(useval)
match = t.haspair(attr, value);
else
match = t.hasattr(attr);
}
}
l = fd.gets('\n');
}while(l != nil && !isentry(l));
if(match && lines != nil){
rl := lines;
for(lines = nil; rl != nil; rl = tl rl)
lines = hd rl :: lines;
unlock(dbf);
return (ref Dbentry(0, lines), 1, offset);
}
}
}
unlock(dbf);
return (nil, 0, int fd.offset());
}
nextentry(db: ref Db, ptr: ref Dbptr, attr: string, value: string, useval: int): (ref Dbentry, ref Dbptr)
{
if(ptr == nil){
ptr = ref Dbptr.Direct(db.dbs, nil, 0);
# TO DO: index
}
while(ptr.dbs != nil){
offset: int;
dbf := hd ptr.dbs;
pick p := ptr {
Direct =>
offset = p.offset;
Hash =>
raise "not done yet";
}
(e, match, next) := dbf.readentry(offset, attr, value, useval);
if(match)
return (e, ref Dbptr.Direct(ptr.dbs, nil, next));
if(e == nil)
ptr = ref Dbptr.Direct(tl ptr.dbs, nil, 0);
else
ptr = ref Dbptr.Direct(ptr.dbs, nil, next);
}
return (nil, ptr);
}
Db.find(db: self ref Db, ptr: ref Dbptr, attr: string): (ref Dbentry, ref Dbptr)
{
return nextentry(db, ptr, attr, nil, 0);
}
Db.findpair(db: self ref Db, ptr: ref Dbptr, attr: string, value: string): (ref Dbentry, ref Dbptr)
{
return nextentry(db, ptr, attr, value, 1);
}
Db.findbyattr(db: self ref Db, ptr: ref Dbptr, attr: string, value: string, rattr: string): (ref Dbentry, ref Dbptr)
{
for(;;){
e: ref Dbentry;
(e, ptr) = nextentry(db, ptr, attr, value, 1);
if(e == nil || e.find(rattr) != nil)
return (e, ptr);
}
}
rev[T](l: list of T): list of T
{
rl: list of T;
for(; l != nil; l = tl l)
rl = hd l :: rl;
return rl;
}
lock(dbf: ref Dbf)
{
dbf.lockc <-= 1;
}
unlock(dbf: ref Dbf)
{
<-dbf.lockc;
}