Back

Control File

2021-03-24

Table of Contents
  1. Create
  2. Write
  3. List
  4. Read
  5. Delete
  6. Append
  7. Full Code

A control file is a small file with meta information about databases but at work the file has grown to be much more than that. It contains things that don't have a place to call home. Mailing lists, sequence numbers, locks and various other simple variables need to be available across the entire system and so we have a dumping ground that we can store anything in.

I like this idea so I took to implementing it under linux instead of universe. Linux may already have this idea and it very well could be environment variables but I wanted something a little bit more explicit. Currently if I need any locks or if I need to keep track of sequences, I write out to a file and later read it in. As you can imagine, the number of files I have 1-3 lines is quite a bit and it seems wasteful.

So I wrote a very simple key value store using sqlite and bash. I used bash because I wanted to simply call a linux shell script to write and read from the database. This is great for single values but even multiple values can be stuck into this database, we would just need to pick a delimiter.

The control file script is gonig to need a few different functions, it will need a create function to set up the initial database, it'll need the ability to list the current keys and values. Finally it will need to be able to write and read from the control file.

Create

bash
#!/usr/bin/bash

control_file=/home/username/control_file.db

create() {
    echo "Creating kv"
    sqlite3 "$control_file" "create table if not exists kv (key TEXT PRIMARY KEY, value TEXT);"
}

"$@"

The first thing we do is define where we want our control file to live.

Next we define our create function and it's a very simple sql query. We are going to create a table with 2 text fields and we will have the key be the primary key. This also makes it so the key is unique.

If the table already exists, we simply do nothing and continue on our merry way.

The final line in our program is how we trigger functions from the command line.

bash
❯ cf create
Creating kv

This will create a brand new database.

Write

bash
...
write() {
    sqlite3 "$control_file" "insert or ignore into kv (key, value) values ('$1', '$2')" 
    sqlite3 "$control_file" "update kv set value = '$2' where key = '$1'" 
}
...

The write is the most complicated part of our simple control file program. We need to be able to either insert a new value or update an existing one. Sqlite requires us to use 2 statements to get this done. We could use INSERT OR REPLACE but this causes things to get shifted around which I didn't like.

We first start with inserting into our table the key and value we pass into this function. If it already exists then sqlite will ignore this statement.

Next we will update the value that exists in our database with the key that we passed in.

This ultimately means when we insert brand new keys into our control file, we will execute sql statements.

bash
$ cf write test 123

We should now have a key, test and a value, 123 in our control file.

List

bash
...
list() {
    sqlite3 "$control_file" << END
.headers on
.mode column
select * from kv order by key; 
END
}
...

Our list command needs some options enabled so we can get a pretty printed table. We also use a sort order here so that we can get the table printed sorted by the key.

bash
❯ cf list
key         value
----------  ----------
test        123

Read

bash
...
read() {
    r=$(sqlite3 "$control_file" "select value from kv where key = '$1'")
    echo -n "$r"
}
...

Here all we need to do is print out the value at the passed in key. We need to get the value and then when we print it to the screen, we want to make sure there's no trailing new line characters.

bash
❯ cf read test
123

If we try to read a key that doesn't exist, we will get nothing back. This is something we'll need to check in whatever programs use the control file.

Delete

bash
delete() {
    sqlite3 "$control_file" "delete from kv where key = '$1'"
}

Now we have a way of deleting keys from our control-file!

bash
❯ cf delete test

Append

Sometimes we may need to store multiple values in one key as that makes the most sense. For instance if we need to keep a list and we need to append to it, we could read in the value from the database and write it back out with a delimiter and the next value but it would be better if our script could do it for us.

bash
append() {
    r=$(read $1)
    write "$1" "$r|$2"
}

The append function reads in the value, recreates the string with the delimiter and the new value and then writes it back out. Here we're using the pipe symbol as our delimiter but we could use anything.

bash
❯ cf write test 1
❯ cf append test 2
❯ cf read test
1|2

Voila now we can keep a list in our control file.

! There we have it, a simple key value store script that we can now place in the bin directory and use it from anywhere in linux.

Full Code

bash
#!/usr/bin/bash

control_file=/home/nivethan/control_file.db

where() {
     echo "$control_file"
}

create() {
    echo "Creating kv"
    sqlite3 "$control_file" "create table if not exists kv (key TEXT PRIMARY KEY, value TEXT);"
}

drop() {
    echo "Dropping kv"
    #sqlite3 $control_file "drop table kv;"
}

list() {
    sqlite3 "$control_file" << END
.headers on
.mode column
select * from kv order by key; 
END
}

write() {
    sqlite3 "$control_file" "insert or ignore into kv (key, value) values ('$1', '$2')" 
    sqlite3 "$control_file" "update kv set value = '$2' where key = '$1'" 
}

read() {
    r=$(sqlite3 "$control_file" "select value from kv where key = '$1'")
    echo -n "$r"
}

delete() {
    sqlite3 "$control_file" "delete from kv where key = '$1'"
}

append() {
    r=$(read $1)
    write "$1" "$r|$2"
}

"$@"