Groundwork for automatic database initialization and schema upgrades.
authorMahlon E. Smith <mahlon@laika.com>
Wed, 14 Sep 2011 16:49:28 -0700
changeset 4 5701b7859a31
parent 3 97f767832c52
child 5 7718f04c8cd1
Groundwork for automatic database initialization and schema upgrades.
.hgignore
Makefile
database.c
db.c
db.h
main.c
sql/1.sql
util.c
volta.h
--- a/.hgignore	Tue Sep 13 22:16:11 2011 -0700
+++ b/.hgignore	Wed Sep 14 16:49:28 2011 -0700
@@ -1,4 +1,5 @@
 ^volta$
+^volta.db$
 ^parser_graph.*
 .*debug
 .*.o
--- a/Makefile	Tue Sep 13 22:16:11 2011 -0700
+++ b/Makefile	Wed Sep 14 16:49:28 2011 -0700
@@ -1,10 +1,10 @@
 
 CFLAGS       = -O2
-CFLAGS_DEBUG = -Wall -L /usr/lib -L /opt/local/lib -DDEBUG -DPROG='"volta (debugmode)"'
+CFLAGS_DEBUG = -Wall -L /opt/local/lib -I /opt/local/include -DDEBUG -DPROG='"volta (debugmode)"'
 LIBS         = -lsqlite3
 LIBS_DEBUG   = -lprofiler  # requires proftools
 #OBJS         = $(patsubst %.c,%.o,$(wildcard *.c)) parser.o
-OBJS         = accept_loop.o database.o main.o parser.o util.o
+OBJS         = accept_loop.o db.o main.o parser.o util.o
 
 ########################################################################
 ### P R O D U C T I O N
@@ -52,7 +52,7 @@
 .PHONY : clean cleanall
 
 cleanall: clean
-	rm -f parser.c
+	rm -f parser.c volta.db
 
 clean:
 	-rm -f volta volta_debug* parser_graph.* *.o *.prof*
--- a/database.c	Tue Sep 13 22:16:11 2011 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/* vim: set noet nosta sw=4 ts=4 ft=c : */
-/*
-Copyright (c) 2011, Mahlon E. Smith <mahlon@martini.nu>
-All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-
-    * Redistributions in binary form must reproduce the above copyright
-      notice, this list of conditions and the following disclaimer in the
-      documentation and/or other materials provided with the distribution.
-
-    * Neither the name of Mahlon E. Smith nor the names of his
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
-EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#include "volta.h"
-
-
-/*
- * Parse command line options, perform actions, and enter accept loop.
- *
- */
-int
-db_initialize( void )
-{
-	debug( 1, LOC, "init! init! init!\n" );
-
-	struct sqlite3 *db;
-	struct sqlite3_stmt *stmt;
-
-	if ( sqlite3_open( "testing.db", &db ) != SQLITE_OK ) {
-		debug( 1, LOC, "Error when initializing database: %s\n", sqlite3_errmsg(db) );
-		return( sqlite3_errcode(db) );
-	}
-
-	if ( sqlite3_prepare( db, "create table foo(bar int);", -1, &stmt, NULL ) != SQLITE_OK ) {
-		debug( 1, LOC, "Error preparing statement: %s\n", sqlite3_errmsg(db) );
-		return( sqlite3_errcode(db) );
-	}
-
-	if ( sqlite3_step( stmt ) != SQLITE_DONE ) {
-		debug( 1, LOC, "Error executing statement: %s\n", sqlite3_errmsg(db) );
-		return( sqlite3_errcode(db) );
-	}
-
-	sqlite3_finalize( stmt );
-	sqlite3_close( db );
-
-	debug( 1, LOC, "okay! okay! okay!\n" );
-	return( SQLITE_OK );
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/db.c	Wed Sep 14 16:49:28 2011 -0700
@@ -0,0 +1,146 @@
+/* vim: set noet nosta sw=4 ts=4 ft=c : */
+/*
+Copyright (c) 2011, Mahlon E. Smith <mahlon@martini.nu>
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the name of Mahlon E. Smith nor the names of his
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "volta.h"
+#include "db.h"
+
+const unsigned short int DB_VERSION = 1;
+
+
+/*
+ * Connect to the database specified in the 'v' global struct,
+ * and populate v.db with a handle to it.
+ *
+ * If required, automatically handle initializing/upgrading a DB.
+ *
+ */
+int
+db_attach( void )
+{
+	if ( v.db != NULL )          return( SQLITE_OK );    /* already attached */
+	if ( strlen(v.dbname) == 0 ) return( SQLITE_ERROR ); /* name not set */
+
+	debug( 2, LOC, "Attaching to database '%s'\n", v.dbname );
+	if ( sqlite3_open( v.dbname, &v.db ) != SQLITE_OK ) {
+		debug( 1, LOC, "Error when attaching to database: %s\n", sqlite3_errmsg(v.db) );
+		return( sqlite3_errcode(v.db) );
+	}
+
+	/* check DB version */
+	unsigned short int version = db_version();
+	if ( version != DB_VERSION ) {
+		debug( 1, LOC, "Database version mismatch: expected %hu, got %d.\n",
+				DB_VERSION, version );
+
+		/* We're in need of a DB initialization, or just behind.
+		 * Attempt to "stair step" upgrade to the current version. */
+		if ( version < DB_VERSION ) {
+			return( db_upgrade(version) );
+		}
+
+		/* Something else is wack. */
+		else {
+			return( SQLITE_ERROR );
+		}
+	}
+
+	return( SQLITE_OK );
+}
+
+
+/*
+ * Perform automatic stair-step upgrades of DB versions
+ * to get up to the current version expected from code.
+ *
+ */
+int
+db_upgrade( unsigned short int current_version )
+{
+	unsigned short int i = 0;
+	char user_pragma[30];
+	char sql_file[30];
+	char *upgrade_sql = NULL;
+
+	for ( i = current_version + 1; i <= DB_VERSION; i++ ) {
+		if ( i == 1 ) {
+			debug( 1, LOC, "Initializing new database.\n" );
+		}
+		else {
+			debug( 1, LOC, "Upgrading database version from %hu to %hu\n", current_version, i );
+		}
+
+		sprintf( sql_file, "sql/%d.sql", i );
+		upgrade_sql = slurp_file( sql_file );
+		if ( upgrade_sql == NULL ) return( SQLITE_ERROR );
+
+		/* If there is SQL to execute, do so and then reset for more */
+		if ( sqlite3_exec( v.db, upgrade_sql, NULL, NULL, NULL ) != SQLITE_OK ) {
+			debug( 1, LOC, "Error upgrading database: %s\n", sqlite3_errmsg(v.db) );
+			return( sqlite3_errcode(v.db) );
+		}
+		free( upgrade_sql );
+		upgrade_sql = NULL;
+
+		/* update version metadata in DB if update was successful */
+		current_version = i;
+		sprintf( user_pragma, "PRAGMA user_version = %hu;", current_version );
+		if ( sqlite3_exec( v.db, user_pragma, NULL, NULL, NULL ) != SQLITE_OK ) {
+			debug( 1, LOC, "Error upgrading database: %s\n", sqlite3_errmsg(v.db) );
+			return( sqlite3_errcode(v.db) );
+		}
+	}
+
+	return( SQLITE_OK );
+}
+
+
+/*
+ * Fetch and return the database's current version. or -1 on error.
+ * The database should already be attached before calling this function.
+ *
+ */
+short int
+db_version( void )
+{
+	struct sqlite3_stmt *stmt;
+	int version = -1;
+
+	if ( sqlite3_prepare_v2( v.db, "PRAGMA user_version", -1, &stmt, NULL ) != SQLITE_OK ) {
+		debug( 1, LOC, "Error finding DB version: %s\n", sqlite3_errmsg(v.db) );
+		return( -1 );
+	}
+
+	if ( sqlite3_step( stmt ) == SQLITE_ROW )
+		version = sqlite3_column_int( stmt, 0 );
+
+	sqlite3_finalize( stmt );
+	return version;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/db.h	Wed Sep 14 16:49:28 2011 -0700
@@ -0,0 +1,49 @@
+/* vim: set noet nosta sw=4 ts=4 ft=c : */
+/*
+Copyright (c) 2011, Mahlon E. Smith <mahlon@martini.nu>
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the name of Mahlon E. Smith nor the names of his
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef _DB_H
+#define _DB_H
+
+#include <sqlite3.h>
+
+extern const unsigned short int DB_VERSION;
+
+/*
+ *
+ * Function prototypes
+ *
+ */
+
+int db_attach( void );
+int db_upgrade( unsigned short int current_version );
+short int db_version( void );
+
+#endif
+
--- a/main.c	Tue Sep 13 22:16:11 2011 -0700
+++ b/main.c	Wed Sep 14 16:49:28 2011 -0700
@@ -31,16 +31,15 @@
 /*
  * TODO
  *
- * empty struct not necessary?
  * inet_pton( AF_INET, *char src, dest )
  * an option to run the DB out of memory?
- * PRAGMA user_version = 1;
  *
  */
 
 #include "volta.h"
-unsigned short int debugmode;
+#include "db.h"
 
+struct v_globals v;
 
 /*
  * Parse command line options, perform actions, and enter accept loop.
@@ -49,41 +48,38 @@
 int
 main( int argc, char *argv[] )
 {
-	/* opt action flags */
-	struct {
-		unsigned char init;
-		unsigned char dump;
-	} actions = {0};
-
 #ifdef DEBUG
 	/* debugmode set at compile time,
 	 * default to display everything */
-	debugmode = 99;
+	v.debugmode = 99;
 #else
-	debugmode = 0;
+	v.debugmode = 0;
 #endif
 
+	/* default database file name */
+	v.db = NULL;
+	strcpy( v.dbname, "volta.db" );
+
 	/* get_opt vars */
 	int opt = 0;
 	opterr  = 0;
 
 	/* parse options */
-	while ( (opt = getopt( argc, argv, "a:d:hv" )) != -1 ) {
+	while ( (opt = getopt( argc, argv, "d:f:hv" )) != -1 ) {
 		switch ( opt ) {
 
-			/* action */
-			case 'a':
-				if ( strcmp( optarg, "init" ) == 0 ) actions.init++;
-				if ( strcmp( optarg, "dump" ) == 0 ) actions.dump++;
+			/* database filename */
+			case 'f':
+				strncpy( v.dbname, optarg, sizeof(v.dbname) );
 				break;
 
 			/* debug */
 			case 'd':
 				if ( optarg[2] == '-' ) {
 					argc++; argv -= 1;
-					debugmode = 1;
+					v.debugmode = 1;
 				}
-				sscanf( optarg, "%hu", &debugmode );
+				sscanf( optarg, "%hu", &v.debugmode );
 				break;
 
 			/* help */
@@ -100,7 +96,7 @@
 			case '?':
 				switch( optopt ) {
 					case 'd': /* no debug argument, default to level 1 */
-						debugmode = 1;
+						v.debugmode = 1;
 						break;
 					default:
 						usage( argv[0] );
@@ -114,15 +110,8 @@
 	argc -= optind;
 	argv += optind;
 
-	/* perform any requested actions */
-	if ( actions.init ) {
-		db_initialize();
-		return( 0 );
-	}
-	if ( actions.dump ) {
-		debug( 1, LOC, "dump.\n" );
-		return( 0 );
-	}
+	/* get the initial database handle or bomb immediately. */
+	if ( db_attach() != SQLITE_OK ) exit( 1 );
 
 	/* enter stdin parsing loop */
 	return accept_loop();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sql/1.sql	Wed Sep 14 16:49:28 2011 -0700
@@ -0,0 +1,6 @@
+BEGIN;
+
+DROP TABLE IF EXISTS init;
+CREATE TABLE init ( bar INT );
+
+COMMIT;   
--- a/util.c	Tue Sep 13 22:16:11 2011 -0700
+++ b/util.c	Wed Sep 14 16:49:28 2011 -0700
@@ -37,13 +37,11 @@
 void
 usage( char *prg )
 {
-	printf( "%s [-vh] [-d <level>] [-a <init>]\n", prg );
+	printf( "%s [-vh] [-f <filename>] [-d <level>]\n", prg );
 	printf( "    -v Display version\n" );
 	printf( "    -d <level> Show debug information on stderr\n" );
 	printf( "    -h Usage (you're lookin' at it)\n" );
-	printf( "    -a Perform an action, being one of:\n" );
-	printf( "         init: Initialize a new, empty database\n" );
-	printf( "         dump: DUMP IT\n" );
+	printf( "    -f <filename> Specify the database to use (default is 'volta.db' in the cwd.)\n");
 	printf( "\n" );
 	return;
 }
@@ -62,7 +60,7 @@
 void
 debug( int level, char *file, int line, const char *fmt, ... )
 {
-	if ( debugmode < level ) return;
+	if ( v.debugmode < level ) return;
 
 	char timestamp[20];
 	time_t t = time( NULL );
@@ -114,9 +112,48 @@
 	}
 	else {
 		line = line_realloc;
-		memcpy( line + offset, buf, strlen(buf) );
+		memcpy( line + offset, buf, LINE_BUFSIZE - 1 );
 	}
 
 	return( line );
 }
 
+
+/*
+ * Read an entire file into memory, returning a pointer the contents.
+ * Returns NULL on error.
+ *
+ */
+char *
+slurp_file( char *file )
+{
+	FILE *fh       = NULL;
+	char *contents = NULL;
+	struct stat sb;
+
+	if ( stat( file, &sb ) != 0 ) {
+		debug( 1, LOC, "Unable to stat() file '%s': %s\n",
+				file, strerror(errno) );
+		return( NULL );
+	}
+
+	if ( (contents = malloc( sb.st_size + 1 )) == NULL ) {
+		debug( 1, LOC, "Unable to allocate memory for file '%s': %s\n",
+				file, strerror(errno) );
+		return( NULL );
+	}
+
+	if ( (fh = fopen( file, "r" )) == NULL ) {
+		debug( 1, LOC, "Could not open file for reading '%s': %s\n",
+				file, strerror(errno) );
+		return( NULL );
+	}
+
+	fread( contents, sizeof(char), sb.st_size, fh );
+	fclose( fh );
+
+	return( contents );
+}
+
+
+
--- a/volta.h	Tue Sep 13 22:16:11 2011 -0700
+++ b/volta.h	Wed Sep 14 16:49:28 2011 -0700
@@ -43,14 +43,13 @@
 #include <string.h>
 #include <unistd.h>
 #include <time.h>
+#include <sys/stat.h>
 
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-#include <sqlite3.h>
-
 #ifdef DEBUG
 #include <google/profiler.h>
 #endif
@@ -60,8 +59,14 @@
 
 /* Aid debugging */
 #define LOC __FILE__, __LINE__
-/* Global debug toggle */
-extern unsigned short int debugmode;
+
+/* a global struct for easy access to common vars */
+struct v_globals {
+	unsigned short int debugmode; /* debug level */
+	char dbname[128];             /* path to database file */
+	struct sqlite3 *db;           /* database handle */
+};
+extern struct v_globals v;        /* defined in main.c */
 
 /* The parsed attributes from the request line, as given to us by squid.
  * URL <SP> client_ip "/" fqdn <SP> user <SP> method [<SP> kvpairs]<NL> */
@@ -75,9 +80,6 @@
 	char   *kvpairs;
 } request;
 
-/* FIXME: An "empty" request struct used for re-assignment */
-static const struct request reset_request;
-
 /*
  *
  * Function prototypes
@@ -86,10 +88,9 @@
 
 void usage( char *prg );
 void debug( int level, char *file, int line, const char *fmt, ... );
+char *slurp_file( char *file );
 char *extend_line( char *line, const char *buf );
 
-int db_initialize( void );
-
 int accept_loop( void );
 struct request *parse( char *p );