This article will show how to use BDB (BerkeleyDB) on VMS.
BDB is an excellent NoSQL Key Value Store database.
There is not actually any VMS specific Java code in this article. The VMS aspect is that it tries to relate BDB to VMS index-sequential files. And there are VMS scripts to build and run the code.
Many of the examples will be borrowed from the BDB section in my NoSQL KVS article.
The biggest downside of BDB is probably the licensing. BDB is available under either AGPL or a commercial license from Oracle.
Before using BDB for anything important please consult an expert in such licenses.
My understanding as a non-lawyer is:
your code | AGPL | commercial license |
---|---|---|
internal usage only | OK | OK (but no reason to pay unless you need support) |
provide either software product or service based on open source externally and willing to supply source code under AGPL license |
OK | OK (but no reason to pay unless you need support) |
provide either software product or service based on open source externally and *NOT* willing to supply source code under AGPL license |
*not* OK | OK |
But if it is important then you need advice from an expert.
Let us assume that we have an older VMS application written in native language (Pascal/Cobol/Basic) using index-sequential files and it has been decided to migrate to a newer language (C/C++/Java) but wanting to stay on VMS.
For persistence there are a few options:
Relational database is the obvious choice. I have already written about that choice numerous times:
But maybe there are reasons for not to go that route and sticking to the Key Value Store / ISAM model.
So if one decide to go for another NoSQL Key Value Store database, then BDB is a possibility.
First problem is to get BDB itself build on VMS.
Luckily JoukJ has already ported to VMS, so no big problem.
Process:
$ unzip [netsrc]db-18_1_40.zip
$ unzip -o [netsrc]db-18_1_40_vmspatch.zip
$ rename db-18^.1^.40.dir bdb.dir
$ set def [.bdb]
$ set def [.build_vms]
$ copy [netsrc]vms_x_fix.h *.*
$ define/nolog sys$library 'f$env("default")',sys$sysroot:[syslib]
$ mmk
The build takes a little time but proceeeds fine with only a single warning (tested on VMS Alpha).
Next is the client libraries.
The native code for the Java client build with:
$ def/nolog dbinc disk2:[arne.bdb.db-18_1_40.src.dbinc]
$ def/nolog dbinc_auto disk2:[arne.bdb.db-18_1_40.src.dbinc_auto]
$ cc/def="vms"/name=(as_is,shortened)/reent=multi/float=ieee/ieee=denorm/include=(dbdir,disk0:[vms$common.java$150.include],disk0:[vms$common.java$150.include.vms]) db_java_wrap
$ @scan_globals_for_option db_java_wrap.obj wrap.opt
$ link/share=db_java_wrap_shr.exe db_java_wrap + wrap.opt/option + sys$input/option
java$java_shr/share
dbdir:libdb/lib
$
The Java code for the Java client build totally standard with ant:
<project name="db" default="build">
<target name="build">
<javac source="1.5" target="1.5" srcdir="src" destdir="bin"/>
<jar destfile="db.jar" basedir="bin"/>
</target>
</project>
If VMS Alpha Java 1.5 or VMS Itanium Java 1.6 then 32 bit pointers should be fine. If VMS Itanium Java 1.8 then 64 bit pointers will be necessary and that *may* require changes to the build.
The C client is very C centric and probably very difficult to call from other native VMS languages (of course with the exception of C++).
To make it easier I write a little VMS wrapper in C that expose an API with descriptors etc..
And that API got wrapped in a Pascal API.
That code including build script and test is available here (take the pbdb library).
BDB expose an API where both keys and values are just byte arrays. One can store a chunk of bytes (a BLOB) and later retrieve the same chunk of bytes (BLOB).
This is somewhat equivalent of the RMS API where one just pass address of data and length of data. For an example see the C code here.
Example showing various operations:
/* standard C headers */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* BDB headers */
#include "db.h"
/* data structure */
struct data
{
int iv;
double xv;
char sv[50];
};
static void serialize(struct data *d, char *buf)
{
memcpy(buf, d, sizeof(struct data));
}
static void deserialize(char *buf, struct data *d)
{
memcpy(d, buf, sizeof(struct data));
}
/* error handling functions */
void db_exit(char *msg, int stat)
{
char buf[256];
printf("DB error %s: %s\n", msg, db_strerror(stat));
exit(1);
}
static void dump(DB *db, char *keybuf)
{
int stat;
DBT key;
DBT value;
struct data d;
printf("Key=%s\n", keybuf);
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
key.data = keybuf;
key.size = strlen(keybuf);
value.size = sizeof(struct data);
stat = db->get(db, NULL, &key, &value, 0);
if(stat == 0)
{
deserialize(value.data, &d);
printf("(%d,%f,%s)\n", d.iv, d.xv, d.sv);
}
else if(stat == DB_NOTFOUND)
{
printf("Not found\n");
}
else
{
db_exit("db->get", stat);
}
}
static const char *DBNAME = "test2.db";
static const int NREC = 1000;
static const int C_OFFSET = 1000; // 1 * NREC;
int main()
{
int stat;
DB *db;
DBC *it, *it2;
DBT key, value, itkey;
struct data d;
char keybuf[32], termkeybuf[32], valuebuf[256];
int n, n2;
/* initialize */
stat = db_create(&db, NULL, 0);
if(stat != 0)
{
db_exit("db_create", stat);
}
/* get connection */
stat = db->open(db, NULL, DBNAME, NULL, DB_BTREE, DB_CREATE, 0664); /* DB_HASH is alternative to DB_BTREE */
if(stat != 0)
{
db_exit("db->open", stat);
}
/* put data */
for(int i = 0; i < NREC; i++)
{
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
sprintf(keybuf, "Key#%d", C_OFFSET + i + 1);
key.data = keybuf;
key.size = strlen(keybuf);
d.iv = i + 1;
d.xv = i + 1.0;
sprintf(d.sv, "This is value %d", i + 1);
serialize(&d, valuebuf);
value.data = valuebuf;
value.size = sizeof(struct data);
stat = db->put(db, NULL, &key, &value, 0);
if(stat != 0)
{
db_exit("db->put", stat);
}
}
/* */
sprintf(keybuf, "Key#%d", C_OFFSET + 77);
/* get */
dump(db, keybuf);
/* delete */
memset(&key, 0, sizeof(DBT));
key.data = keybuf;
key.size = strlen(keybuf);
stat = db->del(db, NULL, &key, 0);
if(stat != 0)
{
db_exit("db->del", stat);
}
/* get non existing */
dump(db, keybuf);
/* */
sprintf(keybuf, "Key#%d", C_OFFSET + 88);
/* update and get */
dump(db, keybuf);
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
key.data = keybuf;
key.size = strlen(keybuf);
value.size = sizeof(struct data);
stat = db->get(db, NULL, &key, &value, 0);
if(stat != 0)
{
db_exit("db->get", stat);
}
deserialize(value.data, &d);
d.iv = d.iv + 1;
d.xv = d.xv + 0.1;
strcat(d.sv, " updated");
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
serialize(&d, valuebuf);
key.data = keybuf;
key.size = strlen(keybuf);
value.data = valuebuf;
value.size = sizeof(struct data);
stat = db->put(db, NULL, &key, &value, 0);
if(stat != 0)
{
db_exit("db->put", stat);
}
dump(db, keybuf);
/* list all */
n = 0;
stat = db->cursor(db, NULL, &it, 0);
if(stat != 0)
{
db_exit("db->cursor", stat);
}
for(;;)
{
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
stat = it->get(it, &key, &value, DB_NEXT);
if(stat == 0)
{
memcpy(keybuf, key.data, key.size);
keybuf[key.size] = '\0';
if(strstr(keybuf, "Key#") != keybuf)
{
printf("Unexpected key: %s\n", keybuf);
}
deserialize(value.data, &d);
if (d.iv < 1 || NREC < d.iv)
{
printf("Unexpected value: (%d,%f,%s)\n", d.iv, d.xv, d.sv);
}
n++;
}
else if(stat == DB_NOTFOUND)
{
/* done */
break;
}
else
{
db_exit("dbc->get", stat);
}
}
printf("%d\n", n);
/* list keys where "Key#n075" <= key < "Key#n085" */
n2 = 0;
stat = db->cursor(db, NULL, &it2, 0);
if(stat != 0)
{
db_exit("db->cursor", stat);
}
sprintf(keybuf, "Key#%d", C_OFFSET + 75);
sprintf(termkeybuf, "Key#%d", C_OFFSET + 85);
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
key.data = keybuf;
key.size = strlen(keybuf);
value.size = sizeof(struct data);
stat = it2->get(it2, &key, &value, DB_SET);
for(;;)
{
if(stat == 0)
{
memcpy(keybuf, key.data, key.size);
keybuf[key.size] = '\0';
if(strcmp(keybuf, termkeybuf) >= 0) break;
n2++;
}
else if(stat == DB_NOTFOUND)
{
/* done */
break;
}
else
{
db_exit("dbc->get", stat);
}
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
stat = it2->get(it2, &key, &value, DB_NEXT);
}
printf("%d\n", n2);
/* close */
db->close(db, 0);
return 0;
}
Build and run:
$ cc/name=as_is/include=dbdir/float=ieee test2
$ link test2 + dbdir:libdb/lib
$ run test2
import java.io.Serializable;
public class Data implements Serializable {
private static final long serialVersionUID = 1L;
private int iv;
private double xv;
private String sv;
public Data() {
this(0, 0.0, "");
}
public Data(int iv, double xv, String sv) {
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
@Override
public String toString() {
return String.format("{iv: %d, xv: %f, sv: %s}", iv, xv, sv);
}
}
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import com.sleepycat.db.Cursor;
import com.sleepycat.db.Database;
import com.sleepycat.db.DatabaseConfig;
import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.OperationStatus;
import dk.vajhoej.record.Alignment;
import dk.vajhoej.record.Endian;
import dk.vajhoej.record.FieldType;
import dk.vajhoej.record.RecordException;
import dk.vajhoej.record.Struct;
import dk.vajhoej.record.StructField;
import dk.vajhoej.record.StructReader;
import dk.vajhoej.record.StructWriter;
public class Test2 {
@Struct(endianess=Endian.LITTLE, alignment=Alignment.ALIGN8, endpad=true)
public static class CData {
@StructField(n=0, type=FieldType.INT4)
public int iv;
@StructField(n=1, type=FieldType.FP8)
public double xv;
@StructField(n=2, type=FieldType.FIXSTRNULTERM, length=50)
public String sv;
//
public CData() {
iv = 0;
xv = 0.0;
sv = "";
}
public CData(Data d) {
iv = d.getIv();
xv = d.getXv();
sv = d.getSv();
}
public Data getData() {
return new Data(iv, xv, sv);
}
}
private static DatabaseEntry prep(String key) throws UnsupportedEncodingException {
return new DatabaseEntry(key.getBytes("UTF-8"));
}
private static DatabaseEntry serialize(Data o) throws IOException, RecordException {
CData o2 = new CData(o);
StructWriter sw = new StructWriter();
sw.write(o2);
return new DatabaseEntry(sw.getBytes());
}
private static Data deserialize(DatabaseEntry de) throws IOException, ClassNotFoundException, RecordException {
StructReader sr = new StructReader(de.getData());
CData o = sr.read(CData.class);
return o.getData();
}
private static void dump(Database db, String key) throws ClassNotFoundException, IOException, DatabaseException, RecordException {
System.out.printf("Key=%s\n", key);
DatabaseEntry rawvalue = new DatabaseEntry();
if(db.get(null, prep(key), rawvalue, null) == OperationStatus.SUCCESS) {
Data value = deserialize(rawvalue);
System.out.println(value);
} else {
System.out.println("Not found");
}
}
private static final String DBNAME = "test2.db";
private static final int NREC = 1000;
private static final int JAVA_OFFSET = 2 * NREC;
public static void main(String[] args) throws DatabaseException, UnsupportedEncodingException, IOException, ClassNotFoundException, RecordException {
Database db = new Database(DBNAME, null, new DatabaseConfig());
// put data
for(int i = 0; i < NREC; i++) {
String key = "Key#" + (JAVA_OFFSET + i + 1);
Data value = new Data();
value.setIv(i + 1);
value.setXv(i + 1.0);
value.setSv(String.format("This is value %d", i + 1));
db.put(null, prep(key), serialize(value));
}
//
String key;
key = "Key#" + (JAVA_OFFSET + 77);
// get
dump(db, key);
// delete
db.delete(null, prep(key));
// get non existing
dump(db, key);
//
key = "Key#" + (JAVA_OFFSET + 88);
// update and get
dump(db, key);
DatabaseEntry rawvalue = new DatabaseEntry();
db.get(null, prep(key), rawvalue, null);
Data value = (Data)deserialize(rawvalue);
value.setIv(value.getIv() + 1);
value.setXv(value.getXv() + 0.1);
value.setSv(value.getSv() + " updated");
db.put(null, prep(key), serialize(value));
dump(db, key);
// list all
Cursor it = db.openCursor(null, null);
DatabaseEntry itrawkey = new DatabaseEntry();
DatabaseEntry itrawvalue = new DatabaseEntry();
OperationStatus itstat = it.getFirst(itrawkey, itrawvalue, null);
int n = 0;
while(itstat == OperationStatus.SUCCESS) {
String itkey = new String(itrawkey.getData(), "UTF-8");
if(!itkey.startsWith("Key#")) {
System.out.println("Unexpected key: " + itkey);
}
Data itvalue = (Data)deserialize(itrawvalue);
if(itvalue.getIv() < 1 || NREC < itvalue.getIv()) {
System.out.println("Unexpected value :" + itvalue);
}
n++;
itstat = it.getNext(itrawkey, itrawvalue, null);
}
it.close();
System.out.println(n);
// list keys where "Key#1075" <= key < "Key#1085"
Cursor it2 = db.openCursor(null, null);
DatabaseEntry it2rawkey = prep("Key#" + 1075);
DatabaseEntry it2rawvalue = new DatabaseEntry();
OperationStatus it2stat = it2.getSearchKey(it2rawkey, it2rawvalue, null);
int n2 = 0;
while(it2stat == OperationStatus.SUCCESS) {
String it2key = new String(it2rawkey.getData(), "UTF-8");
if(it2key.compareTo("Key#" + 1085) >= 0) break;
n2++;
it2stat = it2.getNext(it2rawkey, it2rawvalue, null);
}
it2.close();
System.out.println(n2);
//
db.close();
}
}
Build and run:
$ javac -classpath db.jar:/isamdir/record.jar Test2.java Data.java
$ define/nolog db_java_wrap_shr disk2:[arne.bdb]db_java_wrap_shr.exe
$ java "-Xmx384m" "-Dsleepycat.db.libname=db_java_wrap_shr" -classpath .:db.jar:/isamdir/record.jar "Test2"
[inherit('pbdbdir:pbdb')]
program test2(input, output);
label
finished;
const
NREC = 1000;
PAS_OFFSET = 3 * NREC;
type
keystr = packed array [1..8] of char;
strdata = packed array [1..50] of char;
data = packed record
iv : integer;
xv : double;
sv : strdata;
end;
function trim(s : strdata) : pstr;
var
temp : pstr;
ix : integer;
begin
temp := s;
ix := temp.length;
while ((temp[ix] = chr(0)) or (temp[ix] = ' ')) and (ix > 1) do ix := ix - 1;
trim := substr(temp, 1, ix);
end;
var
db : db_ptr;
procedure dump(id : keystr);
var
d : data;
stat, dummy : integer;
begin
writeln('Key=', id);
stat := db_sk_get(db, id, iaddress(d), size(d), dummy);
if stat = 0 then begin
writeln('(', d.iv:1, ',', d.xv:3:1, ',', trim(d.sv), ')');
end else begin
writeln('Not found');
end;
end;
var
it, it2 : cursor_ptr;
id : keystr;
d : data;
stat, i, dummy, n, n2 : integer;
wdummy : word;
begin
(* open database *)
db := db_open('test2.db');
(* put data *)
for i := 1 to NREC do begin
id := 'Key#' + dec(PAS_OFFSET + i, 4);
d.iv := i;
d.xv := i;
d.sv := 'This is value ' + dec(i, 4);
db_sk_put(db, id, iaddress(d), size(d));
end;
(* *)
id := 'Key#' + dec(PAS_OFFSET + 77, 4);
(* get *)
dump(id);
(* delete *)
db_sk_delete(db, id);
(* get non existing *)
dump(id);
(* *)
id := 'Key#' + dec(PAS_OFFSET + 88, 4);
(* update and get *)
dump(id);
db_sk_get(db, id, iaddress(d), size(d), dummy);
d.iv := d.iv + 1;
d.xv := d.xv + 0.1;
d.sv := trim(d.sv) + ' updated';
db_sk_put(db, id, iaddress(d), size(d));
dump(id);
(* list all *)
it := db_cursor_open(db);
n := 0;
while db_cursor_sk_next(it, id, wdummy, iaddress(d), size(d), dummy) = 0 do begin
if (d.iv < 1) or (NREC < d.iv) then begin
writeln('Unepected value: ', '(', d.iv:1, ',', d.xv:3:1, ',', trim(d.sv), ')');
end;
n := n + 1;
end;
writeln(n:1);
(* list keys where "Key#n075" <= key < "Key#n085" *)
it2 := db_cursor_open(db);
id := 'Key#' + dec(PAS_OFFSET + 75, 4);
stat := db_cursor_sk_find(it2, id, iaddress(d), size(d), dummy);
n2 := 0;
while stat = 0 do begin
if id >= ('Key#' + dec(PAS_OFFSET + 85, 4)) then goto finished;
n2 := n2 + 1;
stat := db_cursor_sk_next(it2, id, wdummy, iaddress(d), size(d), dummy);
end;
finished:
writeln(n2:1);
(* close database *)
db_close(db);
end.
Build and run:
$ define/nolog pbdbdir disk2:[arne.vmspascal.pbdb]
$ pas test2
$ link test2 + pbdbdir:pbdb/opt + pbdbdir:bdb/opt
$ run test2
program test
implicit none
structure /mydata/
integer*4 iv
real*8 xv
character*50 sv
endstructure
record /mydata/d
character*8 id,stopid
integer*4 NREC,FOR_OFFSET
parameter (NREC=1000,FOR_OFFSET=4*NREC)
integer*4 db,it,it2,i,dummy,slen,stat,n,n2
integer*2 wdummy
cdec$ alias db_open, 'db_open'
cdec$ alias db_sk_put, 'db_sk_put'
cdec$ alias db_sk_get, 'db_sk_get'
cdec$ alias db_sk_delete, 'db_sk_delete'
cdec$ alias db_cursor_open, 'db_cursor_open'
cdec$ alias db_cursor_sk_find, 'db_cursor_sk_find'
cdec$ alias db_cursor_sk_next, 'db_cursor_sk_next'
cdec$ alias db_close, 'db_close'
integer*4 db_open,db_cursor_open,db_cursor_sk_next,
+ db_cursor_sk_find,real_len
c open
db=db_open('test2.db')
c put data
do 100 i=1,NREC
write(id,'(A4,I4)') 'Key#',(FOR_OFFSET+i)
d.iv=i
d.xv=i
write(d.sv,'(A,I4.4)') 'This is value ',i
call db_sk_put(db,id,%val(%loc(d)),sizeof(d))
100 continue
c
write(id,'(A4,I4)') 'Key#',(FOR_OFFSET + 77)
c get
call dump(db,id)
c delete
call db_sk_delete(db,id)
c get non-existing
call dump(db,id)
c get and update
write(id,'(A4,I4)') 'Key#',(FOR_OFFSET + 88)
call dump(db,id)
call db_sk_get(db,id,%val(%loc(d)),sizeof(d),dummy)
d.iv=d.iv+1
d.xv=d.xv+0.1
slen=real_len(d.sv)
d.sv(slen+1:slen+8)=' updated'
call db_sk_put(db,id,%val(%loc(d)),sizeof(d))
call dump(db,id)
c list all
n=0
it=db_cursor_open(db)
200 stat=db_cursor_sk_next(it,id,wdummy,%val(%loc(d)),sizeof(d),dummy)
if(stat.eq.0) then
if(id(1:4).ne.'Key#') then
write(*,*) 'Unexpected key: ',id
end if
if(d.iv.lt.1.or.NREC.lt.d.iv) then
slen=real_len(d.sv)
write(*,*) 'Unexpected value: ',
+ '(',d.iv,',',d.xv,',',d.sv(1:slen),')'
endif
n=n+1
goto 200
end if
write(*,*) n
c list keys where "Key#n075" <= key < "Key#n085"
write(id,'(A4,I4)') 'Key#',(FOR_OFFSET+75)
write(stopid,'(A4,I4)') 'Key#',(FOR_OFFSET+85)
n2=0
it2=db_cursor_open(db)
stat=db_cursor_sk_find(it2,id,%val(%loc(d)),sizeof(d),dummy)
300 if(stat.eq.0) then
if(id.ge.stopid) goto 400
n2=n2+1
stat=db_cursor_sk_next(it2,id,wdummy,
+ %val(%loc(d)),sizeof(d),dummy)
goto 300
end if
400 write(*,*) n2
c close
call db_close(db)
end
c
subroutine dump(db,id)
implicit none
integer*4 db
character*8 id
structure /mydata/
integer*4 iv
real*8 xv
character*50 sv
endstructure
record /mydata/d
integer*4 stat,dummy,slen
cdec$ alias db_sk_get, 'db_sk_get'
integer*4 db_sk_get,real_len
write(*,*) 'Key=',id
stat=db_sk_get(db,id,%val(%loc(d)),sizeof(d),dummy)
if(stat.eq.0) then
slen=real_len(d.sv)
write(*,*) '(',d.iv,',',d.xv,',',d.sv(1:slen),')'
else
write(*,*) 'Not found'
end if
return
end
c
integer*4 function real_len(sv)
implicit none
character*50 sv
integer*4 ix
ix=50
900 if((sv(ix:ix).eq.char(0).or.sv(ix:ix).eq.' ').and.ix.gt.1) then
ix=ix-1
goto 900
endif
real_len=ix
end
Build and run:
$ define/nolog pbdbdir disk2:[arne.vmspascal.pbdb]
$ for test2
$ link test2 + pbdbdir:pbdb/opt + pbdbdir:bdb/opt
$ run test2
BDB does not have a 32 KB limit on records like RMS index-sequential files.
No problems with big records.
Here is an example working with 1.5 MB texts:
/* standard C headers */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* BDB headers */
#include "db.h"
/* error handling functions */
void db_exit(char *msg, int stat)
{
char buf[256];
printf("DB error %s: %s\n", msg, db_strerror(stat));
exit(1);
}
/* IO function */
static char buf[10000000];
void load(char *fnm)
{
FILE *fp;
char *line;
int ix;
fp = fopen(fnm, "r");
ix = 0;
while((line = fgets(buf + ix, sizeof(buf) - ix, fp)) != NULL)
{
ix += strlen(line);
}
fclose(fp);
}
void save(char *fnm)
{
FILE *fp;
fp = fopen(fnm, "w");
fwrite(buf, strlen(buf), 1, fp);
fclose(fp);
}
static const char *DBNAME = "big.db";
int main()
{
int stat;
DB *db;
DBC *it, *it2;
DBT key, value;
char *copy1 = "copy1";
char *copy2 = "copy2";
/* initialize */
stat = db_create(&db, NULL, 0);
if(stat != 0)
{
db_exit("db_create", stat);
}
/* get connection */
stat = db->open(db, NULL, DBNAME, NULL, DB_BTREE, DB_CREATE, 0664); /* DB_HASH is alternative to DB_BTREE */
if(stat != 0)
{
db_exit("db->open", stat);
}
/* put data */
load("4300-0.txt");
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
key.data = copy1;
key.size = strlen(copy1);
value.data = buf;
value.size = strlen(buf);
stat = db->put(db, NULL, &key, &value, 0);
if(stat != 0)
{
db_exit("db->put", stat);
}
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
key.data = copy2;
key.size = strlen(copy2);
value.data = buf;
value.size = strlen(buf);
stat = db->put(db, NULL, &key, &value, 0);
if(stat != 0)
{
db_exit("db->put", stat);
}
/* get data */
memset(buf, 0, sizeof(buf));
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
key.data = copy1;
key.size = strlen(copy1);
value.size = sizeof(buf);
stat = db->get(db, NULL, &key, &value, 0);
if(stat != 0)
{
db_exit("db->get", stat);
}
strncpy(buf, value.data, value.size);
save("copy1.txt");
memset(&key, 0, sizeof(DBT));
memset(&value, 0, sizeof(DBT));
key.data = copy2;
key.size = strlen(copy2);
value.size = sizeof(buf);
stat = db->get(db, NULL, &key, &value, 0);
if(stat != 0)
{
db_exit("db->get", stat);
}
strncpy(buf, value.data, value.size);
save("copy2.txt");
/* close */
db->close(db, 0);
return 0;
}
Build and run:
$ cc/name=as_is/include=dbdir/float=ieee big
$ link big + dbdir:libdb/lib
$ run big
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import com.sleepycat.db.Database;
import com.sleepycat.db.DatabaseConfig;
import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.DatabaseType;
public class Big {
private static DatabaseEntry toEntry(String s) {
return new DatabaseEntry(s.getBytes());
}
private static String fromEntry(DatabaseEntry dbe) {
return new String(dbe.getData());
}
private static String load(String fnm) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new FileReader(fnm));
String line;
while((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
return sb.toString();
}
private static void store(String txt, String fnm) throws IOException {
PrintWriter pw = new PrintWriter(new FileWriter(fnm));
pw.print(txt);
pw.close();
}
public static void main(String[] args) throws IOException, DatabaseException {
// open database
DatabaseConfig dbcfg = new DatabaseConfig();
dbcfg.setAllowCreate(true);
dbcfg.setType(DatabaseType.BTREE);
Database db = new Database("big.db", null, dbcfg);
// put
db.put(null, toEntry("copy1"), toEntry(load("4300-0.txt")));
db.put(null, toEntry("copy2"), toEntry(load("4300-0.txt")));
// get
DatabaseEntry copy1 = new DatabaseEntry();
db.get(null, toEntry("copy1"), copy1, null);
store(fromEntry(copy1), "copy1.txt");
DatabaseEntry copy2 = new DatabaseEntry();
db.get(null, toEntry("copy2"), copy2, null);
store(fromEntry(copy2), "copy2.txt");
// close database
db.close();
}
}
Build and run:
$ javac -classpath db.jar Big.java
$ define/nolog db_java_wrap_shr disk2:[arne.bdb]db_java_wrap_shr.exe
$ java "-Xmx384m" "-Dsleepycat.db.libname=db_java_wrap_shr" -classpath .:db.jar "Big"
[inherit('pbdbdir:pbdb')]
program big(input, output);
var
buf : packed array [1..10000000] of chr;
buflen : integer;
(*
Note: these IO routines are neither typical Pascal nor typical VMS,
but they are comptible with the way the C code works.
*)
procedure load(fnm : pstr);
var
f : text;
c : char;
begin
open(f, fnm, old);
reset(f);
while not eof(f) do begin
if eoln(f) then begin
buflen := buflen + 1;
buf[buflen] := chr(10);
readln(f);
end else begin
read(f, c);
bufen := buflen + 1;
buf[buflen] := c;
end;
end;
close(f);
end;
procedure store(fnm : pstr);
var
f : text;
i : integer;
begin
open(f, fnm, new, record_format := 'stmlf');
rewrite(f);
for i := 1 to buflen do begin
write(f, buf[i]);
end;
close(f);
end;
var
db : db_ptr;
stat, dummy : integer;
wdummy : word;
begin
(* open database *)
db := db_open('big.db');
(* put *)
load('4300-0.txt');
db_sk_put(db, 'copy1', buf, buflen);
db_sk_put(db, 'copy2', buf, buflen);
(* get *)
buflen := 0;
db_sk_get(db, 'copy1', buf, size(buf), buflen);
store('copy1.txt');
buflen := 0;
db_sk_get(db, 'copy2', buf, size(buf), buflen);
store('copy2.txt');
(* close database *)
db_close(db);
end.
Build and run:
$ define/nolog pbdbdir disk2:[arne.vmspascal.pbdb]
$ pas big
$ link big + pbdbdir:pbdb/opt + pbdbdir:bdb/opt
$ run big
An important capability of VMS index-sequential files is the support for multiple keys.
BDB has support for that even though the API is sligtly complex.
No examples yet.
BDB supports traditional transactions:
BEGIN operation 1 operation 2 ... operation n COMMIT or ROLLBACK
Two benefits:
The first is handled in VMS index-sequential files using explicit locking.
For more info about traditional transactions and transaction isolation level see here.
This can be very convenient.
No examples yet.
BDB is a very capable NoSQL Key Value Store database. And it works fine on VMS.
But I doubt that it will be used much on VMS. Most likely a rewrite would move to more modern persistence paradigms.
Version | Date | Description |
---|---|---|
1.0 | February 8th 2023 | Initial version |
See list of all articles here
Please send comments to Arne Vajhøj