plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

commit c85cd9b72c57aedcaec989c8910a72ac3df46d32
parent 94ad0043622327173f2d441116da2304ce4ccb48
Author: ssnf <ssnf@ssnf.xyz>
Date:   Sun,  3 Aug 2025 08:30:59 +0000

sdb: fix bugs and add documentation

Diffstat:
Mman/man1/INDEX | 7+++++++
Aman/man1/sdb.1 | 302++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mman/man3/sdbr.3 | 13++++---------
Mman/man7/INDEX | 1+
Aman/man7/sdb.7 | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/cmd/sdb/sdbedit.c | 2+-
Msrc/cmd/sdb/sdbrval.c | 21+++++++++++++--------
Msrc/cmd/sdb/sdbuniq.c | 3++-
Msrc/libsdb/sdb_next.c | 20+++++++++++---------
Msrc/libsdbr/sdbr_match.c | 2++
Msrc/libsdbr/sdbr_r2str.c | 12++++++++----
11 files changed, 451 insertions(+), 32 deletions(-)

diff --git a/man/man1/INDEX b/man/man1/INDEX @@ -194,6 +194,13 @@ sam.save sam.1 samsave sam.1 samterm sam.1 scat scat.1 +sdbedit sdb.1 +sdbjoin sdb.1 +sdbmap sdb.1 +sdbpretty sdb.1 +sdbquery sdb.1 +sdbrval sdb.1 +sdbuniq sdb.1 aescbc secstore.1 ipso secstore.1 secstore secstore.1 diff --git a/man/man1/sdb.1 b/man/man1/sdb.1 @@ -0,0 +1,301 @@ +.TH SDB 1 +.SH NAME +sdbquery, sdbedit, sdbjoin, sdbmap, sdbpretty, sdbrval, sdbuniq \- simple database +.SH SYNOPSIS +.B sdbquery +[ +.B -f +.I file +] +.I "attr value" +[ +.I "attr value" +] +\&... +.br +.B sdbedit +[ +.B -f +.I file +] +.I "attr value" +[ +.I "attr value" +] +\&... +.br +.B sdbjoin +[ +.B -f +.I file0 +] +.I file1 +[ +.I "attr value" +] +\&... +.br +.B sdbmap +[ +.B -k +.I attr +] +[ +.B -f +.I file0 +] +.I mapfile +.I source-attr +.I dest-attr +.br +.B sdbpretty +[ +.B -f +.I file +] +.br +.B sdbrval +[ +.B -am +] +.I attr +\&... +.br +.B sdbuniq +[ +.B -f +.I file +] +[ +.I attr +] +\&... +.SH DESCRIPTION +These commands operate on simple databases using the same format as Plan 9's +network database (ndb). +Database files contain records with attribute=value pairs, +with records separated by blank lines and +continuation lines starting with whitespace. +Comments begin with '#'. +.PP +.I Sdbquery +searches the database for records matching the given attribute-value pairs. +With +.BR -f , +it reads from the specified +.IR file ; +otherwise it reads from standard input. +All records matching the query are printed. +.PP +.I Sdbedit +finds records matching the first attribute-value pair (the query) +and adds or updates them with the remaining attribute-value pairs (the edits). +If no matching record is found, it creates a new record. +With +.BR -f , +it reads from the specified +.IR file ; +otherwise it reads from standard input. +The updated database is written to standard output. +.PP +.I Sdbjoin +performs a database join operation between two files. +For each record in +.I file0 +(or standard input if +.B -f +is not given), +it uses the record's first attribute-value pair as a query to find matching records in +.IR file1 . +Records from +.I file1 +that contain the queried attribute-value pair are joined with the +.I file0 +record. +Additional attribute-value constraints can be specified. +The joined records are written to standard output. +.PP +.I Sdbmap +uses a mapping database +.I mapfile +to transform values in the input database. +It looks up values of +.I source-attr +in the mapping database and replaces them with the corresponding +.I dest-attr +values. +With +.BR -k , +it uses +.I attr +as the key attribute in the mapping database; +otherwise it uses +.IR source-attr . +With +.BR -f , +it reads from the specified +.IR file0 ; +otherwise it reads from standard input. +.PP +.I Sdbpretty +formats database records for display, printing each record with +the first attribute on its own line and subsequent attributes indented. +With +.BR -f , +it reads from the specified +.IR file ; +otherwise it reads from standard input. +.PP +.I Sdbrval +extracts specified attribute values from database records. +With +.BR -a , +it prints attribute names and values in the form +.BR attr=value . +With +.BR -m , +it uses pattern matching to select attributes. +It reads from standard input and prints tab-separated values for each record. +.PP +.I Sdbuniq +removes duplicate records based on specified key attributes. +Input must be sorted by the first attribute and key attributes for correct operation. +Records are considered duplicates if they have the same value for the first attribute +and the same values for all specified key attributes. +When duplicates are found, it merges their attributes and prints the combined record. +Only records with more than one attribute beyond the specified keys are output. +With +.BR -f , +it reads from the specified +.IR file ; +otherwise it reads from standard input. +.SH EXAMPLES +Create a starship database: +.IP +.EX +% cat >ships.db +ship=enterprise class=constitution captain=kirk + crew=430 warp=8 phasers=12 torpedoes=6 + +ship=defiant class=defiant captain=sisko + crew=50 warp=9.5 cloak=yes quantum=40 + +ship=voyager class=intrepid captain=janeway + crew=150 warp=9.975 bioneural=yes delta=far +.EE +.PP +Find warp-capable ships commanded by captains named 'k*': +.IP +.EX +% sdbquery -f ships.db captain 'k*' +ship=enterprise class=constitution captain=kirk + crew=430 warp=8 phasers=12 torpedoes=6 +.EE +.PP +Transfer command and update crew complement: +.IP +.EX +% sdbquery -f ships.db ship voyager | + sdbedit ship voyager captain chakotay crew 147 +ship=voyager class=intrepid captain=chakotay + crew=147 warp=9.975 bioneural=yes delta=far +.EE +.PP +Create quantum torpedo inventory and map to ships: +.IP +.EX +% cat >weapons.db +quantum=40 type=photon power=high + federation=yes klingon=no + +quantum=6 type=phaser power=medium + federation=yes cardassian=yes +% sdbquery -f ships.db quantum '*' | + sdbmap weapons.db quantum type +ship=defiant class=defiant captain=sisko + crew=50 warp=9.5 cloak=yes type=photon +.EE +.PP +Analyze fleet capabilities by extracting warp factors: +.IP +.EX +% sdbquery -f ships.db ship '*' | sdbrval -a warp | sort -t= -k2 -n +warp=8 +warp=9.5 +warp=9.975 +.EE +.PP +Join ship records with their class specifications: +.IP +.EX +% cat >classes.db +ship=enterprise registry=NCC-1701 commissioned=2245 + designer=cochrane yards=sf-fleet + +ship=defiant registry=NX-74205 commissioned=2366 + designer=sisko yards=utopia-planitia + +ship=voyager registry=NCC-74656 commissioned=2371 + designer=janeway yards=utopia-planitia +% sdbjoin -f ships.db classes.db +ship=enterprise class=constitution captain=kirk + crew=430 warp=8 phasers=12 torpedoes=6 + registry=NCC-1701 commissioned=2245 + designer=cochrane yards=sf-fleet +ship=defiant class=defiant captain=sisko + crew=50 warp=9.5 cloak=yes quantum=40 + registry=NX-74205 commissioned=2366 + designer=sisko yards=utopia-planitia +ship=voyager class=intrepid captain=janeway + crew=150 warp=9.975 bioneural=yes delta=far + registry=NCC-74656 commissioned=2371 + designer=janeway yards=utopia-planitia +.EE +.PP +Monitor temporal anomalies with duplicate detection: +.IP +.EX +% cat >timeline.db +ship=enterprise date=2267 incident=mirror_universe + spock=bearded kirk=evil + +ship=enterprise date=2267 incident=mirror_universe + mccoy=savage uhura=rebel + +ship=defiant date=2375 incident=mirror_universe + sisko=terran kira=intendant odo=security +% sdbuniq -f timeline.db date incident +ship=enterprise date=2267 incident=mirror_universe + spock=bearded kirk=evil mccoy=savage + uhura=rebel +ship=defiant date=2375 incident=mirror_universe + sisko=terran kira=intendant odo=security +.EE +.PP +Format fleet status for admiralty briefing: +.IP +.EX +% sdbpretty <ships.db +ship=enterprise + class=constitution + captain=kirk + crew=430 + warp=8 + phasers=12 + torpedoes=6 +ship=defiant + class=defiant + captain=sisko + crew=50 + warp=9.5 + cloak=yes + quantum=40 +.EE +.SH SOURCE +.B \*9/src/cmd/sdb +.SH SEE ALSO +.MR ndb (1) , +.MR ndb (3) , +.MR ndb (7) , +.MR sdb (3) , +.MR sdbr (3) +\ No newline at end of file diff --git a/man/man3/sdbr.3 b/man/man3/sdbr.3 @@ -135,8 +135,8 @@ parses a string containing space-separated attribute=value pairs and adds them to record .IR r . The input string is not modified. -Values containing spaces can be quoted with double quotes. -When values are quoted, the following characters are escaped: \e0, \ef, \en, \er, \et, \ev, \e", and \e\e. +Value quoting and escaping are described in +.MR sdb (7) . .PP .I Sdbr_arg2r parses command-line arguments @@ -165,13 +165,8 @@ tests whether .I value matches .IR pattern . -The pattern supports wildcards (*) and comparison operators -(>, >=, <, <=). -The * wildcard matches zero or more characters. -When a * is followed by a literal character, it finds the first occurrence -of that character in the remaining string and continues matching from there. -Comparison operators perform lexicographic string comparison, not numeric. -Literal asterisks can be escaped with backslash. +Pattern matching is described in +.MR sdb (7) . .PP .I Sdbr_query tests whether record diff --git a/man/man7/INDEX b/man/man7/INDEX @@ -16,6 +16,7 @@ ndb ndb.7 plot plot.7 plumb plumb.7 regexp regexp.7 +sdb sdb.7 thumbprint thumbprint.7 ASCII utf.7 UTF utf.7 diff --git a/man/man7/sdb.7 b/man/man7/sdb.7 @@ -0,0 +1,99 @@ +.TH SDB 7 +.SH NAME +sdb \- simple database format +.SH DESCRIPTION +The simple database (sdb) format is a text-based database format +that is 100% backward compatible with Plan 9's network database (ndb) format. +Database files contain records with attribute=value pairs, +with records separated by blank lines. +.PP +Each record consists of one or more lines. +The first line contains space-separated attribute=value pairs. +Continuation lines begin with whitespace (spaces or tabs) and +contain additional attribute=value pairs for the same record. +.PP +Within a line, attribute=value pairs are separated by spaces or tabs. +The attribute name and value are separated by an equals sign (=). +Attribute names cannot contain equals signs or whitespace. +.PP +Values can be quoted with double quotes to include spaces. +When values are quoted, the following escape sequences are recognized: +\e0 (null), \ef (form feed), \en (newline), \er (carriage return), +\et (tab), \ev (vertical tab), \e" (quote), and \e\e (backslash). +All other bytes, including those with values above 127, can be stored directly. +SDB fully supports binary data. +.PP +When values are not quoted, tabs cannot appear in values as they +are field separators. To include a tab in a value, quote the value +and use \et. +.PP +Comments begin with # and extend to the end of the line. +Comments can appear on their own line or after attribute=value pairs. +.SH PATTERN MATCHING +Several sdb tools support pattern matching for attribute values. +The pattern language includes: +.TP +.B * +Wildcard that matches zero or more characters. +However, a pattern consisting of a single * matches one or more characters. +.TP +.B > +.PD 0 +.TP +.B >= +.TP +.B < +.TP +.B <= +.PD +Comparison operators for lexicographic string comparison. +.TP +.B \e* +Literal asterisk (escaped with backslash). +.PP +When a * is followed by a literal character, it finds the first occurrence +of that character in the remaining string and continues matching from there. +.SH EXAMPLES +A simple database of network services: +.IP +.EX +service=http port=80 protocol=tcp + description="Web server" + +service=https port=443 protocol=tcp + description="Secure web server" + +service=ssh port=22 protocol=tcp + description="Secure shell" security=high +.EE +.PP +Using special characters in values: +.IP +.EX +# Tab-separated data +data=raw value="one\ttwo\tthree" + +# Multi-line text +message="First line\enSecond line\enThird line" + +# Path with spaces +file="/home/user/My Documents/report.txt" + +# Binary data with null bytes and high-bit characters +signature="GIF89a\0\0ÿÿ" +checksum="Á¢£¤¥¦§¨©" +.EE +.SH SEE ALSO +.MR ndb (1) , +.MR ndb (7) , +.MR sdb (1) , +.MR sdb (3) , +.MR sdbr (3) +.SH NOTES +The sdb format is designed to be both human-readable and +suitable for processing with standard Unix text tools. +Single-line record format enables easy sorting and filtering +with tools like +.MR sort (1) +and +.MR grep (1) . +\ No newline at end of file diff --git a/src/cmd/sdb/sdbedit.c b/src/cmd/sdb/sdbedit.c @@ -5,7 +5,7 @@ static char *file; static void usage(void) { - fprint(2, "sdbedit: [-f file] attr0 value0 [attr1 value1]..."); + fprint(2, "sdbedit: [-f file] attr0 value0 [attr1 value1]...\n"); exits("usage"); } diff --git a/src/cmd/sdb/sdbrval.c b/src/cmd/sdb/sdbrval.c @@ -3,6 +3,7 @@ static void print_col(Sdbr*, char*); static String val; +static String ln; static char *fmt = "%.0s%s"; static void (*print_fn)(Sdbr*, char*) = print_col; @@ -11,13 +12,15 @@ print_col(Sdbr *r, char *attr) { int n; + if (ln.n) + Straddc(&ln, '\t'); n = sdbr_attr(*r, attr); if (n < 0) { - print(fmt, attr, ""); + Strprint(&ln, fmt, attr, ""); return; } sdbr_escape(&val, r->val[n]); - print(fmt, r->attr[n].s, val.s); + Strprint(&ln, fmt, r->attr[n].s, val.s); } static void @@ -28,8 +31,10 @@ print_match(Sdbr *r, char *attr) for (i = 0; i < sdbr_n(*r); ++i) { if (!sdbr_match(attr, r->attr[i].s)) continue; + if (ln.n) + Straddc(&ln, '\t'); sdbr_escape(&val, r->val[i]); - print(fmt, r->attr[i].s, val.s); + Strprint(&ln, fmt, r->attr[i].s, val.s); } } @@ -60,15 +65,15 @@ main(int argc, char *argv[]) if (argc < 1) usage(); Strinit(&val); + Strinit(&ln); sdb_open(&db, nil); for (;sdb_next(&db);) { r = db.r + db.n; - print_fn(r, argv[0]); - for (n = 1; n < argc; ++n) { - write(1, "\t", 1); + Strzero(&ln); + for (n = 0; n < argc; ++n) print_fn(r, argv[n]); - } - write(1, "\n", 1); + if (ln.n || print_fn == print_col) + print("%s\n", ln.s); } exits(0); } diff --git a/src/cmd/sdb/sdbuniq.c b/src/cmd/sdb/sdbuniq.c @@ -56,7 +56,8 @@ main(int argc, char *argv[]) sdbr_init(&nr); sdbr_init(&r); sdb_open(&db, file); - sdb_next(&db); + if (!sdb_next(&db)) + exits(0); sdbr_dup(&cr, db.r[db.n]); sdbr_dup(&nr, db.r[db.n]); for (;sdb_next(&db);) { diff --git a/src/libsdb/sdb_next.c b/src/libsdb/sdb_next.c @@ -9,29 +9,31 @@ sdb_next(Sdb *db) if (db->n + 1 < Vecsiz(db->r)) return ++db->n; if (!db->ln.n) { - for (;Strgets(&db->ln, db->b);) { + for (;;) { + if (!Strgets(&db->ln, db->b)) + return 0; for (n = 0; n < db->ln.n && isspace(db->ln.s[n]); ++n); - if (db->ln.s[n] != '#' - && !isspace(db->ln.s[n])) + if (n < db->ln.n && db->ln.s[n] != '#') break; } - if (!db->ln.n) - return 0; } if (Vecsiz(db->r) && Vecsiz(db->r[db->n].attr)) ++db->n; Strinit(&s); - Stradds(&s, db->ln); - for (;Strgets(&db->ln, db->b);) { - if (db->ln.s[0] != '#' && !isspace(db->ln.s[0])) - break; + for (;;) { for (n = 0; n < db->ln.n; ++n) if (db->ln.s[n] == '#') { Strdelete(&db->ln, n, db->ln.n); break; } Stradds(&s, db->ln); + if (!Strgets(&db->ln, db->b)) { + Strzero(&db->ln); + break; + } + if (!isspace(db->ln.s[0])) + break; } sdbr_str2r(Vecadd(&db->r), s); Strclose(&s); diff --git a/src/libsdbr/sdbr_match.c b/src/libsdbr/sdbr_match.c @@ -9,6 +9,8 @@ sdbr_match(char *k, char *v) sysfatal("sdbr_match: nil key"); if (!v) sysfatal("sdbr_match: nil value"); + if (k[0] == '*' && k[1] == '\0') + return *v ? 1 : 0; if (*k == '>') { if (*++k == '=') return strcmp(v, k + 1) >= 0 ? 1 : 0; diff --git a/src/libsdbr/sdbr_r2str.c b/src/libsdbr/sdbr_r2str.c @@ -10,10 +10,14 @@ sdbr_r2str(String *s, Sdbr r) sysfatal("sdbr_r2str: empty record"); Strinit(&t); Strzero(s); - Strprint(s, "%s=%s", r.attr[0].s, sdbr_escape(&t, r.val[0])); - for (i = 1; i < Vecsiz(r.attr); ++i) - Strprint(s, "\t%s=%s", r.attr[i].s - , sdbr_escape(&t, r.val[i])); + Strprint(s, "%s=", r.attr[0].s); + sdbr_escape(&t, r.val[0]); + Stradds(s, t); + for (i = 1; i < Vecsiz(r.attr); ++i) { + Strprint(s, "\t%s=", r.attr[i].s); + sdbr_escape(&t, r.val[i]); + Stradds(s, t); + } Straddc(s, '\n'); Strclose(&t); }