Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

COMP2017 9017

Assignment 2

Task description

In this assignment we will develop a key value store called YmirDB in the C programming language using dynamic data structures, ensuring that no memory errors occur. Each entry of the database is identified by a unique key string and contains a dynamically sized list of integer values and possibly other keys. You are encouraged to ask questions on Ed using the assignments category. As with any assignment, make sure that your work is your own, and you do not share your code or solutions with other students.

Simple entry (strictly integer values)

The simple entry is a key value store, where only integers are used as values. The following defines and initialises an entry with key identifier a with values:

>  SET  a  1  2  3

ok

>  GET  a

[1  2  3]

The values can be updated with other commands.

>  SET  a  1  2  3

ok

>  PUSH  a  5

ok

>  GET  a

[5  1  2  3]

>  APPEND  a  7

ok

>  GET  a

[5  1  2  3  7]

>  SET  a   9  8  7

ok

>  GET  a

[9  8  7]

An entry is termed simple if it’s values are only integers. It is otherwise general and described later.

Managing states of the database

What is state?

The state of the database is comprised of all the keys, and their associated values at a given point in time. For example, when creating multiple keys, and their values, their state is preserved in memory.

What are snapshots?

A user may choose to save the current state of the database by calling SNAPSHOT. By doing so, a copy of the current state is saved and can be later referred to by it’s snapshot number.

>  SET  a  1  2  3

ok

>  SNAPSHOT

saved  as  snapshot  1

At a later time, the user may choose to replace the current state with a snapshot created at an earlier time. This is done using the CHECKOUT command. This does not affect other snapshots in memory.

>  SET  a  1  2  3

ok

>  SNAPSHOT

saved  as  snapshot  1

>  SET  a  4  5   6

ok

>  SNAPSHOT

saved  as  snapshot  2

>  SET  a  7  8   9

ok

>  CHECKOUT  1

ok

>  GET  a

[  1  2  3   ]

>  CHECKOUT  2

ok

>  GET  a

[  4  5   6   ]

Alternatively, the user may choose to restore the state by loading a particular snapshot and discard all changes or snapshots made since that time. This is the ROLLBACK command.

>  SET  a  1  2  3

ok

>  SNAPSHOT

saved  as  snapshot  1

>  SET  a  4  5   6

ok

>  SNAPSHOT

saved  as  snapshot  2

>  SET  a  7  8   9

ok

>  ROLLBACK  1

ok

>  GET  a

[  1  2  3   ]

>  CHECKOUT  2

no  such  snapshot

Actions on current state with regards to snapshot

Snapshots and the current state are mutually disjoint. Actions on the current state should not influence any other snapshots. Note, for PURGE, it is not an action ONLY on the current state, see notes on

PURGE.

General entry (mixed integer and key values)

We recommend completing all commands with the simple entry before beginning this part of the assignment

A general entry contains at least one value that is a key to another entry in the database.

>  SET  a  1  2

ok

>  SET  b  4  a   6

ok

>  LIST  ENTRIES

B   [  4  a   6   ]

A   [  1  2   ]

>  SUM  a

3

>  SUM  b

13

An entry cannot store a value being it’s own key, or to a key that does not exist in the current state.

>  SET  a  a

not  permitted

>  SET  b  z

no  such  key

When an entry refers to another key, a bidirectional relationship of forward and backward references between these entries need to be maintained.

• Entry X has a forward reference of an entry Y if X contains the key of Y as one of its values

• Entry X has a backward reference of an entry Y if Y contains the key of X as one of it’s values To understand forward and backward references, consider the following example.

>  SET  c  7

ok

>  SET  b  3  5  c

ok

>  SET  a  1  2  b

ok

>  SUM  c

7

>  SUM  b

15

>  SUM  a

18

•  a has a forward reference to b

•  b has a forward reference to c

•  c has no forward reference

•  c has a backward reference to b

•  b has a backward reference to a

•  a has no backward references

Forward and backward references of a entry, and including the subsequent entries within, can be queried using the FORWARD and BACKWARD commands.

>  SET  c  7

ok

>  SET  b  3  5  c

ok

>  SET  a  1  2  b

ok

>  FORWARD  a

b,  c

>  FORWARD  b

c

>  FORWARD  c

nil

>  BACKWARD  a

nil

>  BACKWARD  b

a

>  BACKWARD  c

a,  b

The output of these commands is a search for all forward or backward references related to this entry. The output presents the unique keys in lexicographical sorted order.

Understanding a valid state

The current state is valid if and only if every general entry has a corresponding key exist.

Operations affecting the valid state include the removal of forward or backward references. DEL and PURGE are described using the following example.

Suppose there are forward and backward references created.

>  SET  c  7

ok

>  SET  b  3  5  c

ok

>  SET  a  1  2  b

ok

The state of the database above is valid. Consider the order of commands in the following:

>  DEL  c

not  permitted

>  DEL  b

not  permitted

>  DEL  a

ok

>  DEL  c

not  permitted

>  DEL  b

ok

>  DEL  c

ok

If c were to be deleted first, the state of the database would be inconsistent, due to c being referred to by b. c has back references.

Similarly, if b were to be deleted, the state of the database would be inconsistent, due to it being referenced by a. b has back references.

The operation is performed if and only if the resulting state is valid.

If DEL a were to be performed, a has no backward references, therefore the state after removal of a would be valid and can proceed. After this, b no longer has backward reference to a and DEL b can proceed.

The DEL command operates on the current state.

PURGE operation

The PURGE operation is performed if and only if the resulting states of all snapshots and also the current state are valid.

If the key does not exist in the state or the snapshot, the operation can proceed as the state is valid. PURGE does not need to report keys that do not exist.

For example, consider the empty state

>  DEL  g

no  such  key

>  PURGE  g

ok

Snapshots are always created as a valid state, these can be broken with PURGE. Consider a more elaborate scenario where there are 2 snapshots and the current state.

PURGE scenario

>  SET  c  7

ok

>  SET  b  3  5  c

ok

>  SET  a  1  2  b

ok

>  SNAPSHOT

saved  as  snapshot  1

>  DEL  a

ok

>  SNAPSHOT

saved  as  snapshot  2

>  SET  b  1  2  3

ok

>  PURGE  b

not  permitted

>  PURGE  a

ok

PURGE b removes all keys b only if each state remains valid. Consider from the scenario:

• if removal of b from current state. b’s entry is simple, no backward references, thus the state is valid.

• if removal of b from snapshot 2. b’s entry is simple, no backward references, thus the state is valid.

• if removal of b from snapshot 1, b’s entry has a backward reference a.  a cannot be resolved after removal and the state would become invalid.

Therefore, PURGE b will not modify any state.

PURGE a removes all keys a only if each state remains valid. Consider from the scenario:

if removal of a from current state. a does not exist, it is ignored.

if removal of a from snapshot 2. a does not exist, it is ignored.

• if removal of a from snapshot 1, a has no backward references. It can be removed and the state remains valid.

PURGE a will remove keys a from all snapshots and current state if it exists.

A note about removal of keys from an index

A general entry can remove a value, being a key, from it’s list of values.  If the key exists, and is removed, then the state remains valid.

PLUCK, POP are such operations. It is also possible to use SET to remove a value from an entry by redefining its values.

Care should be taken to update the backward and forward references such that subsequent operations following PLUCK or POP are consistent with the expected behaviour.

Implementation details

Write a program in C that implements YmirDB as shown in the examples. You can assume that our test cases will contain only valid input commands and not cause any integer overflows.

You are guaranteed not to have NULL returned from malloc() or realloc() in this context.

Keys are case sensitive and do not contain spaces.  Keys are alphanumeric and must begin with an alphabetical character [a-z][A-Z]. The key length is no greater than 15 characters.

Commands are case insensitive.

Entry values are indexed from 1.

Snapshots are indexed from 1 and are unique for the lifetime of the program.

Keys, entries and snapshots are to be displayed in the order from most recently added to least recently added.

A snapshot’s keys and values should not have allocated memory associated with other snapshots. When listing snapshots, each snapshot should be separated by a newline character.

Output for commands forward or backward references present the sorted lexicographical order by key and unique keys only.

Test cases will not include self referencing or reference cycles.

Your program must be contained in ymirdb.c and ymirdb.h and produce no errors when built and run on Ed C compiler. Reading from standard input and writing to standard output.

Your program output must match the exact output format shown in the examples and on Ed. You are encouraged to submit your assignment while you are working on it, so you can obtain feedback.

The contents of an example header le ymirdb.h are shown below. You may modify, or make your own.

No external code outside the standard C library functions should be required.

#ifndef  YMIRDB_H

#define   YMIRDB_H

#define  MAX_KEY  16

#define  MAX_LINE  1024

#include   

#include   

enum item_type   {

INTEGER= 0 ,

ENTRY= 1

};

typedef struct element element;

typedef struct entry entry;

typedef struct snapshot snapshot;

struct element {

enum item_type  type;

union {

int value;

struct entry *entry;

};

};

struct entry {

char key[MAX_KEY];

char is_simple;

element  *  values;

size_t length;

entry *  next;

entry *  prev;

size_t forward_size;

size_t forward_max;

entry **  forward;     //  this  entry  depends  on   these

size_t backward_size;

size_t backward_max;

entry **  backward;  //  these  entries  depend  on   this

};

struct snapshot {

int id;

entry *  entries;

snapshot *  next;

snapshot *  prev;

};

#endif

In order to obtain full marks, your program must free all of the dynamic memory it allocates. This will be automatically checked using address sanitizer. If your program produces the correct output for a given test case and does not free all memory it allocates, then it will not pass the given test case.

Commands

Your program should implement the following commands, look at the examples to see how they work.

If a  does not exist in the current state, output: no such key

If a  does not exist in the database, output: no such snapshot

• If an does not exist in an entry, output: index out of range

• If an cannot be removed, output: not permitted

BYE       clear  database  and  exit

HELP     display  this  help  message

LIST  KEYS

LIST  ENTRIES

LIST  SNAPSHOTS

displays displays displays

all  keys  in  current  state

all  entries  in  current  state

all  snapshots  in  the  database

GET       DEL       PURGE  

displays  entry  values

deletes  entry  from  current  state

deletes  entry  from  current  state  and  snapshots

SET           PUSH         APPEND    

sets  entry  values

pushes  values  to  the  front

appends  values  to  the  back

PICK      PLUCK     POP  

displays  value  at  index

displays  and  removes  value  at  index  displays  and  removes  the  front  value

DROP  

ROLLBACK   CHECKOUT  

SNAPSHOT

deletes  snapshot

restores  to  snapshot  and  deletes  newer  snapshots replaces  current  state  with  a  copy  of  snapshot     saves  the  current  state  as  a  snapshot

MIN  <key>

displays minimum value

MAX  <key>

displays maximum value

SUM  <key>

displays sum of values

LEN  <key>

displays number of values

REV  <key>

reverses order of values   (simple entry only)

UNIQ  <key>

removes repeated adjacent values   (simple entry only)

SORT  <key>

sorts values in ascending order   (simple entry only)

FORWARD    lists  all  the  forward  references  of  this  key     BACKWARD    lists  all  the  backward  references  of  this  key

TYPE    displays  if  the  entry  of  this  key  is  simple  or  general

Examples

>  LIST  KEYS

no  keys

>  LIST  ENTRIES

no  entries

>  LIST  SNAPSHOTS

no  snapshots

>  SET  a  1

ok

>  GET  a

[1]

>  POP  a

1

>  GET  a

[]

>  POP  a

nil

>  PUSH  a  2  1

ok

>  GET  a

[1  2]

>  APPEND  a  3  4

ok

>  GET  a

[1  2  3  4]

>  DEL  a

ok

>  DEL  a

no  such  key

>  BYE

bye

>  SET  a  1

ok

>  SET  b  2  3

ok

>  LIST  KEYS

b

a

>  LIST  ENTRIES

b   [2  3]

a   [1]

>  LIST  SNAPSHOTS

no  snapshots

>  PICK  a  0

index  out  of  range

>  PICK  b  1

2

>  GET  b

[2  3]

>  PLUCK  b  2

3

>  GET  b

[2]

>  DEL  b

ok

>  GET  b

no  such  key

>  PURGE  b

ok

>  BYE

bye

>  DROP  1

no  such  snapshot

>  ROLLBACK  1

no  such  snapshot

>  SET  a  1  2

ok

>  SET  b  3  4

ok

>  LIST  ENTRIES

b   [3  4]

a   [1  2]

>  SNAPSHOT

saved  as  snapshot  1

>  SET  c  5   6

ok

>  LIST  ENTRIES

c   [5   6]

b   [3  4]

a   [1  2]

>  SNAPSHOT

saved  as  snapshot  2

>  PURGE  b

ok

>  ROLLBACK  1

ok

>  CHECKOUT  2

no  such  snapshot

>  LIST  ENTRIES

a   [1  2]

>  LIST  SNAPSHOTS

1

>  BYE

bye

>  SET  a  1  4  2  3  4  2

ok

>  SNAPSHOT

saved  as  snapshot  1

>  MIN  a

1

>  MAX  a

4

>  SUM  a

16

>  LEN  a

6

>  REV  a

ok

>  GET  a

[2  4  3  2  4  1]

>  SORT  a

ok

>  GET  a

[1  2  2  3  4  4]

>  UNIQ  a

ok

>  GET  a

[1  2  3  4]

>  CHECKOUT  1

ok

>  LIST  SNAPSHOTS

1

>  LIST  ENTRIES

a   [1  4  2  3  4  2]

>  BYE

bye