2.2.13 Cycle 13 - Introducing a sql database

Design

Objectives

Now that you can send messages between nodes, it's time to introduce a way of storing these messages and the best way in which I can think of that to be done is by creating a local database such that the node can store and load data onto that database and use it as it's memory. The benefit of introducing a database for this reason also means that the references that were introduced in Cycle 11 - Remembering Nodes can be moved to the database for faster and better storage.

The way I'm going to go to do this is through the use of docker, which is a service for generating containerised "virtual machines" that *should* run identically no matter the hardware. This means all the program has to do is tell docker to generate a postgreSQL database image and as long as the user has docker installed, it should then be able to be connect and use this just like it would any other database.

PostgreSQL was chosen because V - the language the node is written in - supports it and I have used it in other projects before so should be able to work with it easily.

Usability Features

  • Docker container and database should be controlled automatically by the node software by default to minimise the amount of interactions users have to have with the software.

  • Contain the ability to change database settings to use a dedicated, custom, database server to make grouping nodes together easier for the user.

Key Variables

Variable Name
Use
init_psql_docker

This is a constant that defines the shell command that is run by the software and creates a docker container that runs the postgreSQL database.

start_psql_docker

This is a constant that runs a shell command that runs to start a pre-made docker container.

stop_psql_docker

This is a constant that runs a shell command to stop a currently running docker container

Pseudocode

The two primary files to be created in this cycle are for the generation of the docker container which runs the database and the initialisation of the database tables. The other additions that need to be made are logically the same as they were with the JSON file storage method so I will not include psuedocode for them but will show the code after they are completed in the outcome section of this cycle.

Docker Container Handling

This code will allow the node software to send direct bash commands to the Operating System and through that create, start and stop a docker container which houses the postgreSQL database used by the software.

// external imports
IMPORT operating_systemtem as	// os stands for operating system
IMPORT time

// config constants
image_name = "monochain-postgresql"
db_name = "postgres"
db_password = "password"

// shell commands
init_psql_docker = "docker run --name $image_name -p 5432:5432 -e POSTGRES_PASSWORD=$db_password -d postgres"
start_psql_docker = "docker start $image_name"
stop_psql_docker = "docker stop $image_name"

// returns whether or not the command executed properly
FUNCTION sh(cmd):
	//sh stands for shell as in a shell commadn
	  OUTPUT "[shell] > $cmd"
	  executed = os.execute(cmd)
	  OUTPUT "[shell] - $executed.output"
	
	  // 0 means the command executed properly
	  RETURN (executed.exit_code == 0)
END FUNCTION

FUNCTION launch():
  	OUTPUT "[Database] Launching database..."
  	// try to start the container
  	started = sh(start_psql_docker)

  	IF (!started):
		OUTPUT "[Database] No database found, or other error, trying to initialise a new database..."
		initialised = sh(init_psql_docker)

		IF (!initialised):
			OUTPUT "\n\n[Database] Could not start or initialise database container, docker is probably not running."
			EXIT
		END IF

		OUTPUT "[Database] Database initialised successfully."
  	END IF
  
	time.sleep(3 SECONDS)
  	OUTPUT "[Database] Database running."
END FUNCTION

FUNCTION stop(){
	OUTPUT "[Database] Stopping database..."
	stopped = sh(stop_psql_docker)

	IF (!stopped):
		OUTPUT "[Database] Failed to stop database, may be due to container not existing, please check docker yourself..."
	ELSE
		OUTPUT "[Database] Stopped database."
	END IF
END FUNCTION

Database initliasation

All that needs to be done in this section is defining what the tables will look like in the database and creating functions to create those tables.

// external imports - postgreSQL is the database type
IMPORT postgres as pg

// config constants
host = "localhost"
port = 5432

config = pg.Config{
	host: host
	port: port
	user: "postgres"
	password: db_password
	dbname: db_name
}


OBJECT Http_Reference:
	id        	[primary; sql: serial; sql_type: 'SERIAL']	// just for the db
	domain		[default: '']	// domain of node
	key        	// key that is attached to node
	last_connected 	[default: 'CURRENT_TIMESTAMP'; sql_type: 'TIMESTAMP']	// when the reference was last used
END OBJECT

OBJECT Message_Table:
	id        	[primary; sql: serial; sql_type: 'SERIAL']	// just for the db
	timestamp  	[default: 'CURRENT_TIMESTAMP'; sql_type: 'TIMESTAMP']	// when the message was sent
	sender     	// key of the sender
	receiver   	// key of the receiver
	contents   	// contents of the message
	signature  	// the signature of the message 
END OBJECT

OBJECT DatabaseConnection:
	connection

	FUNCTION init_tables(this):
		OUTPUT "[Database] Creating http reference table..."
		
		SQL this.connection:
			CREATE table Http_Reference
		END SQL
		
		OUTPUT "[Database] Creating message table..."
		
		SQL this.connection {
			CREATE table Message_Table
		END SQL
		
		OUTPUT "[Database] Tables created.\n"
	END FUNCTION
END OBJECT

// interfacing with the tables using the pg module
FUNCTION connect(over_ride_config, supplied_config):
	config_to_use = config

	IF (over_ride_config):
		config_to_use = supplied_config
	END IF

	connection = {}
	
	TRY:
		connection = pg.connect(config_to_use)
	CATCH:
		OUTPUT "[Database] Could not connect to database, docker container probably not running.\n[Database] Raw error: $err\n[Database] There is a chance this was due to trying to connect before the database was ready, if so restarting the program should fix it."
		EXIT
	END TRY

	db = DatabaseConnection{connection: connection}

	OUTPUT "[Database] Connected to database. ($host:$port)"
	
	OUTPUT "[Database] Creating tables..."
	db.init_tables()
	RETURN db
END FUNCTION

Development

Although all the code shown in this cycle does not mention web-sockets, it is worth mentioning that whilst during the development of this cycle I was primarily working on switching from using json files to a database, I was also starting to work on the web-socket functionality that will be shown in the next cycle, hence the code for this cycle is available here but exploring outside the database module will show evidence of code that is referenced in the next cycle.

On that note, all of the following code is available in separate files within the 'database' file, stored at /packages/node/src/modules/database/ with the specific file names commented at the top of the code below.

Outcome

Creating and handling the docker container

This code allows the node software to send direct bash commands to the Operating System and through that create, start and stop a docker container which houses the postgreSQL database used by the software.

// found at /database/containerHandling.v
module database

// external imports
import os
import time

// config constants
pub const image_name = "monochain-postgresql"
pub const db_name = "postgres"
pub const db_password = "password"

// shell commands
const init_psql_docker = "docker run --name $image_name -p 5432:5432 -e POSTGRES_PASSWORD=$db_password -d postgres"
const start_psql_docker = "docker start $image_name"
const stop_psql_docker = "docker stop $image_name"

// returns whether or not the command executed properly
fn sh(cmd string) bool {
  println("[shell] > $cmd")
  executed := os.execute(cmd)
  print("[shell] - $executed.output")

  // 0 means the command executed properly
  return executed.exit_code == 0 
}

pub fn launch(){
  	println("[Database] Launching database...")
  	// try to start the container
  	started := sh(start_psql_docker)

  	if !started {
		println("[Database] No database found, or other error, trying to initialise a new database...")
		initialised := sh(init_psql_docker)

		if !initialised {
			eprintln("\n\n[Database] Could not start or initialise database container, docker is probably not running.")
			exit(300)
		}

		println("[Database] Database initialised successfully.")
  	}
	time.sleep(3 * time.second)
  	println("[Database] Database running.")
}

pub fn stop(){
	println("[Database] Stopping database...")
	stopped := sh(stop_psql_docker)

	if !stopped {
		eprintln("[Database] Failed to stop database, may be due to container not existing, please check docker yourself...")
	} else {
		println("[Database] Stopped database.")
	}
}

Challenges

The main challenge this cycle was to figure out the best way of setting up a database whilst balancing how many additional things the user would have to download with how much setup and usage they would need to do.

After various testing and trialing of different methods that resulted in the use of a docker container to host the database as the setup and management of docker can be handled by the node software and simply requires the user to download docker, although I later realised that the user would also need to install postgreSQL and libpq in order to have all the header files required to run the node. (This can be avoided by compiling directly to binary with all the required files but in most cases this isn't a great idea as most users will need to recompile the software to fit their systems)

This then means that three new requirements are needed to run the software:

  • Docker

  • PostgreSQL

  • libpq (or libpq-dev for linux)

However a benefit to moving to docker is that in the future the entire software could be moved into a docker container, allowing the software to simply run within two docker containers, one for the software and the other for the database, which would prevent users from having to download anything except docker directly to their computer and instead all other downloads would be handled within docker without any user interference.

Testing

All of the tests for this cycle were automated using a basic script I wrote in order to be able to test the whole module quickly and easily.

Tests

Test
Instructions
What I expect
What actually happens
Passed?

1

Use the Shell function to echo a message

The message to be echo'ed from the terminal

As Expected

2

Launch the database.

The database to launch and no crashes to occur.

As Expected

3

Stop the database.

The database to stop and no crashes to occur.

As Expected

4

Launch and then connect to the database.

The database to launch then accept the program's connection without any crashes.

As Expected

5

Get the last 5 messages from the database.

An array of messages to be returned in the expected type.

As Expected

6

Create a node reference in the database.

The reference to be created in the database.

As Expected

7

Create a node reference and then check if the software is aware of that node.

The "aware_of" check should return true for the reference test that already exists.

As Expected

8

Collect all references from the database.

Should return an array of node references in the expected type.

As Expected

Last updated