shithub: purgatorio

ref: a411870ee4640241e3c494367d922847da84f972
dir: purgatorio/appl/lib/attrdb.b

View raw version
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;
}