Simple database

From Rosetta Code
Revision as of 17:06, 25 July 2016 by rosettacode>Gerard Schildberger (expanded types of tracked data, removed pronouns from types of data, added other whitespace to the task's preamble, added bullet points.)
Task
Simple database
You are encouraged to solve this task according to the task description, using any language you may know.
Task

Write a simple tool to track a small set of data.

The tool should have a command-line interface to enter at least two different values.

The entered data should be stored in a structured format and saved to disk.

It does not matter what kind of data is being tracked.   It could be a collection (CDs, coins, baseball cards, books), a diary, an electronic organizer (birthdays/anniversaries/phone numbers/addresses), etc.


You should track the following details:

  • A description of the item. (e.g., title, name)
  • A category or tag (genre, topic, relationship such as “friend” or “family”)
  • A date (either the date when the entry was made or some other date that is meaningful, like the birthday); the date may be generated or entered manually
  • Other optional fields


The command should support the following Command-line arguments to run:

  • Add a new entry
  • Print the latest entry
  • Print the latest entry for each category
  • Print all entries sorted by a date


The category may be realized as a tag or as structure (by making all entries in that category subitems)

The file format on disk should be human readable, but it need not be standardized.   A natively available format that doesn't need an external library is preferred.   Avoid developing your own format if you can use an already existing one.   If there is no existing format available, pick one of:


Related task



Bracmat

This is a rather minimal solution. The program is run from the command line of the operating system, in this example the Windows command prompt. The program is stored in a file called 'sdb': <lang bracmat> whl ' ( arg$:?command

 & ( get'db
   | (db=1)&lst$(db,db,NEW)
   )
 &   !command
   : (   add
       & :?name:?tag:?date
       &   whl
         ' ( arg$:?argmnt
           & arg$:?value
           &   (!argmnt.!value)
             : ( (title|name.?name)
               | (category|tag.?tag)
               | (date.?date)
               )
           )
       & (   !name:~
           & !tag:~
           & !date:~
           & (   !db:?*!tag^(?+(!date.!name)+?)*?
               & out$"This record already exists"
             |   !tag^(!date.!name)*!db:?db
               & lst$(db,db,NEW)
             )
         | out$"invalid data"
         )
     |   latest
       & :?date
       & nothing found:?latest
       & (   !db
           :   ?
             *   ?tag
               ^ ( ?
                 + ( (>!date:?date.?name)
                   & (!name,!tag,!date):?latest
                   & ~
                   )
                 + ?
                 )
             * ?
         | out$!latest
         )
     |   latest/category
       & :?date:?latests:?latest
       & (   !db
           :   ?
             *   ( ?tag
                 & !latests !latest:?latests
                 & :?latest:?date
                 )
               ^ ( ?
                 + ( (>!date:?date.?name)
                   & (!name,!tag,!date):?latest
                   & ~
                   )
                 + ?
                 )
             * ?
         | !latests !latest:?latests&out$!latests
         )
     |   sorted
       & 0:?sorted
       & (   !db
           :   ?
             *   ?tag
               ^ ( ?
                 + ( (?date.?name)
                   & (!date.!name,!tag,!date)+!sorted:?sorted
                   & ~
                   )
                 + ?
                 )
             * ?
         |   whl
           ' (!sorted:(?.?row)+?sorted&out$!row)
         )
     )
 );

</lang> First we add some records, a some ships that arrived at the harbour in Rotterdam today.

bracmat "get$sdb" add name "CORONA BULKER" tag "BULK CARRIER" date "2014.10.21.04:00"
bracmat "get$sdb" add name "FPMC 21" tag "CHEMICAL TANKER" date "2014.10.15.12:00"
bracmat "get$sdb" add name "CHINA PROGRESS" tag "BULK CARRIER" date "2014.10.13.22:00"
bracmat "get$sdb" add name "FAIRCHEM YUKA" tag "CHEMICAL TANKER" date "2014.10.13.12:00"
bracmat "get$sdb" add name "NAVE COSMOS" tag "CHEMICAL TANKER" date "2014.10.13.10:00"
bracmat "get$sdb" add name "GOLDEN ICE" tag "BULK CARRIER" date "2014.10.10.12:00"
bracmat "get$sdb" add name "GHAZAL" tag "CRUDE OIL" date "2014.10.10.12:00"
bracmat "get$sdb" add name "HS MEDEA" tag "CRUDE OIL" date "2014.10.10.02:00"

Instead of 'name' you can use 'title' and instead of 'tag' you can use 'category'. The date has to be year first and day last, followed by time information if you like. No attempt is made to validate the date/time. Now the queries:

prompt> bracmat "get$sdb" latest
CORONA BULKER,BULK CARRIER,2014.10.21.04:00

prompt> bracmat "get$sdb" latest/category
  (CORONA BULKER,BULK CARRIER,2014.10.21.04:00)
  (FPMC 21,CHEMICAL TANKER,2014.10.15.12:00)
  (GHAZAL,CRUDE OIL,2014.10.10.12:00)

prompt> bracmat "get$sdb" sorted
HS MEDEA,CRUDE OIL,2014.10.10.02:00
GHAZAL,CRUDE OIL,2014.10.10.12:00
GOLDEN ICE,BULK CARRIER,2014.10.10.12:00
NAVE COSMOS,CHEMICAL TANKER,2014.10.13.10:00
FAIRCHEM YUKA,CHEMICAL TANKER,2014.10.13.12:00
CHINA PROGRESS,BULK CARRIER,2014.10.13.22:00
FPMC 21,CHEMICAL TANKER,2014.10.15.12:00
CORONA BULKER,BULK CARRIER,2014.10.21.04:00

The database file 'db' looks like this:

db= "BULK CARRIER"
  ^ ( ("2014.10.10.12:00"."GOLDEN ICE")
    + ("2014.10.13.22:00"."CHINA PROGRESS")
    + ("2014.10.21.04:00"."CORONA BULKER")
    )
*   "CHEMICAL TANKER"
  ^ ( ("2014.10.13.10:00"."NAVE COSMOS")
    + ("2014.10.13.12:00"."FAIRCHEM YUKA")
    + ("2014.10.15.12:00"."FPMC 21")
    )
* "CRUDE OIL"^(("2014.10.10.02:00"."HS MEDEA")+("2014.10.10.12:00".GHAZAL));

Use is made of Bracmat's automatic normalization of algebraic formula to turn the data into a hierarchical structure, with the tag as the top level and the date/time immediately below that level.

C

A simple database in C with some error checking, even. A quick test with Valgrind revealed no obvious memory leaks. The following data was used for testing. -> database.csv

"Soon Rising","Dee","Lesace","10-12-2000","New Hat Press" 
"Brave Chicken","Tang","Owe","04-01-2008","Nowhere Press" 
"Aardvark Point","Dee","Lesace","5-24-2001","New Hat Press" 
"Bat Whisperer, The","Tang","Owe","01-03-2004","Nowhere Press" 
"Treasure Beach","Argus","Jemky","09-22-1999","Lancast" 

<lang C>#include <stdio.h>

  1. include <stdlib.h> /* malloc */
  2. include <string.h> /* strlen */
  3. define _XOPEN_SOURCE /* requred for time functions */
  4. define __USE_XOPEN
  5. include <time.h>
  6. define DB "database.csv" /* database name */
  7. define TRY(a) if (!(a)) {perror(#a);exit(1);}
  8. define TRY2(a) if((a)<0) {perror(#a);exit(1);}
  9. define FREE(a) if(a) {free(a);a=NULL;}
  10. define sort_by(foo) \

static int by_##foo (const void*p1, const void*p2) { \

   return strcmp ((*(const pdb_t*)p1)->foo, (*(const pdb_t*)p2)->foo); }

typedef struct db {

   char title[26];
   char first_name[26];
   char last_name[26];
   time_t date;
   char publ[100];
   struct db *next;

} db_t,*pdb_t; typedef int (sort)(const void*, const void*); enum {CREATE,PRINT,TITLE,DATE,AUTH,READLINE,READ,SORT,DESTROY}; static pdb_t dao (int cmd, FILE *f, pdb_t db, sort sortby); static char *time2str (time_t *time); static time_t str2time (char *date); /* qsort callbacks */ sort_by(last_name); sort_by(title); static int by_date(pdb_t *p1, pdb_t *p2); /* main */ int main (int argc, char **argv) {

   char buf[100];
   const char *commands[]={"-c", "-p", "-t", "-d", "-a", NULL};
   db_t db;
   db.next=NULL;
   pdb_t dblist;
   int i;
   FILE *f;
   TRY (f=fopen(DB,"a+"));
   if (argc<2) {

usage: printf ("Usage: %s [commands]\n"

       "-c  Create new entry.\n"
       "-p  Print the latest entry.\n"
       "-t  Print all entries sorted by title.\n"
       "-d  Print all entries sorted by date.\n"
       "-a  Print all entries sorted by author.\n",argv[0]);
       fclose (f);
       return 0;
   }
   for (i=0;commands[i]&&strcmp(argv[1],commands[i]);i++);
   switch (i) {
       case CREATE:
       printf("-c  Create a new entry.\n");
       printf("Title           :");if((scanf(" %25[^\n]",db.title     ))<0)break;
       printf("Author Firstname:");if((scanf(" %25[^\n]",db.first_name))<0)break;
       printf("Author Lastname :");if((scanf(" %25[^\n]",db.last_name ))<0)break;
       printf("Date 10-12-2000 :");if((scanf(" %10[^\n]",buf          ))<0)break;
       printf("Publication     :");if((scanf(" %99[^\n]",db.publ      ))<0)break;
       db.date=str2time (buf);
       dao (CREATE,f,&db,NULL);
       break;
       case PRINT:
       printf ("-p  Print the latest entry.\n");
       while (!feof(f)) dao (READLINE,f,&db,NULL);
       dao (PRINT,f,&db,NULL);
       break;
       case TITLE:
       printf ("-t  Print all entries sorted by title.\n");
       dblist = dao (READ,f,&db,NULL);
       dblist = dao (SORT,f,dblist,by_title);
       dao (PRINT,f,dblist,NULL);
       dao (DESTROY,f,dblist,NULL);
       break;
       case DATE:
       printf ("-d  Print all entries sorted by date.\n");
       dblist = dao (READ,f,&db,NULL);
       dblist = dao (SORT,f,dblist,(int (*)(const void *,const  void *)) by_date);
       dao (PRINT,f,dblist,NULL);
       dao (DESTROY,f,dblist,NULL);
       break;
       case AUTH:
       printf ("-a  Print all entries sorted by author.\n");
       dblist = dao (READ,f,&db,NULL);
       dblist = dao (SORT,f,dblist,by_last_name);
       dao (PRINT,f,dblist,NULL);
       dao (DESTROY,f,dblist,NULL);
       break;
       default: {
           printf ("Unknown command: %s.\n",strlen(argv[1])<10?argv[1]:"");
           goto usage;
   }   }
   fclose (f);
   return 0;

} /* Data Access Object (DAO) */ static pdb_t dao (int cmd, FILE *f, pdb_t in_db, sort sortby) {

   pdb_t *pdb=NULL,rec=NULL,hd=NULL;
   int i=0,ret;
   char buf[100];
   switch (cmd) {
       case CREATE:
       fprintf (f,"\"%s\",",in_db->title);
       fprintf (f,"\"%s\",",in_db->first_name);
       fprintf (f,"\"%s\",",in_db->last_name);
       fprintf (f,"\"%s\",",time2str(&in_db->date));
       fprintf (f,"\"%s\" \n",in_db->publ);
       break;
       case PRINT:
       for (;in_db;i++) {
           printf ("Title       : %s\n",     in_db->title);
           printf ("Author      : %s %s\n",  in_db->first_name, in_db->last_name);
           printf ("Date        : %s\n",     time2str(&in_db->date));
           printf ("Publication : %s\n\n",   in_db->publ);
           if (!((i+1)%3)) {
               printf ("Press Enter to continue.\n");
               ret = scanf ("%*[^\n]");
               if (ret<0) return rec; /* handle EOF */
               else getchar();
           }
           in_db=in_db->next;
       }
       break;
       case READLINE:
       if((fscanf(f," \"%[^\"]\",",in_db->title     ))<0)break;
       if((fscanf(f," \"%[^\"]\",",in_db->first_name))<0)break;
       if((fscanf(f," \"%[^\"]\",",in_db->last_name ))<0)break;
       if((fscanf(f," \"%[^\"]\",",buf              ))<0)break;
       if((fscanf(f," \"%[^\"]\" ",in_db->publ      ))<0)break;
       in_db->date=str2time (buf);
       break;
       case READ:
       while (!feof(f)) {
           dao (READLINE,f,in_db,NULL);
           TRY (rec=malloc(sizeof(db_t)));
           *rec=*in_db; /* copy contents */
           rec->next=hd;/* to linked list */
           hd=rec;i++;
       }
       if (i<2) {
           puts ("Empty database. Please create some entries.");
           fclose (f);
           exit (0);
       }
       break;
       case SORT:
       rec=in_db;
       for (;in_db;i++) in_db=in_db->next;
       TRY (pdb=malloc(i*sizeof(pdb_t)));
       in_db=rec;
       for (i=0;in_db;i++) {
           pdb[i]=in_db;
           in_db=in_db->next;
       }
       qsort (pdb,i,sizeof in_db,sortby);
       pdb[i-1]->next=NULL;
       for (i=i-1;i;i--) {
           pdb[i-1]->next=pdb[i];
       }
       rec=pdb[0];
       FREE (pdb);
       pdb=NULL;
       break;
       case DESTROY: {
           while ((rec=in_db)) {
               in_db=in_db->next;
               FREE (rec);
   }   }   }
   return rec;

} /* convert numeric time to date string */ static char *time2str (time_t *time) {

   static char buf[255];
   struct tm *ptm;
   ptm=localtime (time);
   strftime(buf, 255, "%m-%d-%Y", ptm);
   return buf;

} /* convert date string to numeric time */ static time_t str2time (char *date) {

   struct tm tm;
   memset (&tm, 0, sizeof(struct tm));
   strptime(date, "%m-%d-%Y", &tm);
   return mktime(&tm);

} /* sort by date callback for qsort */ static int by_date (pdb_t *p1, pdb_t *p2) {

   if ((*p1)->date < (*p2)->date) {
       return -1;
   }
   else return ((*p1)->date > (*p2)->date);

}</lang>

COBOL

This is a souped-up version of the task from Take notes on the command line. It stores the current date, a tag, a title and a note as an entry in a file. The database produced is not particularly human-readable or easy to modify, but it is in a well-structured format.

Works with: OpenCOBOL

<lang cobol> IDENTIFICATION DIVISION.

      PROGRAM-ID. simple-database.
      ENVIRONMENT DIVISION.
      INPUT-OUTPUT SECTION.
      FILE-CONTROL.
          SELECT OPTIONAL database-file ASSIGN Database-Path
              ORGANIZATION INDEXED
              ACCESS SEQUENTIAL
              RECORD KEY data-title
              ALTERNATE RECORD KEY data-tag
                  WITH DUPLICATES
              ALTERNATE RECORD KEY date-added
                  WITH DUPLICATES
              FILE STATUS file-status
              .
      DATA DIVISION.
      FILE SECTION.
      FD  database-file.
      01  database-record.
          *> Date is in YYYYMMDD format.
          03  date-added          PIC 9(8).
          03  data-tag            PIC X(20).
          03  data-title          PIC X(50).
          03  data-contents       PIC X(200).
          *> Adding extra space is considered good style so the record
          *> can be expanded in the future.
          03  FILLER              PIC X(50).
      WORKING-STORAGE SECTION.
      78  Database-Path           VALUE "database.dat".
      01  file-status             PIC XX.
          88  file-ok             VALUE "00".
          88  duplicate-key       VALUE "22".
          88  key-not-found       VALUE "23".
      01  num-args                PIC 99.
      01  action                  PIC XX.
          88  create-entry        VALUE "-c".
          88  remove-entry        VALUE "-r".
          88  find-entry          VALUE "-f".
          88  print-latest        VALUE "-l".
          88  print-database      VALUES "-a", "-d", "-t".
          *> Printed by title.
          88  print-by-title      VALUE "-a".
          88  print-by-date       VALUE "-d".
          88  print-by-tag        VALUE "-t".
          88  print-help          VALUES "-h", SPACES.
      01  read-direction-flag     PIC X VALUE SPACE.
          88  read-backwards      VALUE "B".
      01  edited-date             PIC 9(4)/99/99.
      PROCEDURE DIVISION.
      DECLARATIVES.
      database-file-error SECTION.
          USE AFTER