DigiKam to PHP Photo Album - denormalize

Denormalization

Denormoralizing the data streamlines the queries necessary to deliver the albums and photos on the web page. In this process, I insert the data into a MySql database then create "views" (actually, the denormalized data in tables) with the data that I'll be using on the web pages.

Insert into MySql

I first use a bash script to dump the sqllite data into MySql. This script is a bit of a hack, but it works well enough for my purposes at the moment. Get it here.

This script uses a script that I found on the internet to do most of the conversion from sqlite3 to mysql. The script is sqlite3_to_mysql.pl.

The approach taken by this script is a little excessive - it begins by dropping tables, then re-importing them from the "migrated" sqlite3 database.

#!/bin/bash

cd /myata/Photos_tools
echo "Dumping sqlite data"
sqlite3 ../digikam4.db .dump > digikam-dump.txt
echo "Converting sqlite data to mysql format"
cat digikam-dump.txt | ./sqlite3-to-mysql.py > into_mysql
echo "Dropping tables"
grep "CREATE TABLE" into_mysql |
	sed 's/^CREATE TABLE \(.*\)$/DROP TABLE IF EXISTS \1;/' |
	mysql --batch -u<userid> -p<password> digikam4
echo "Importing Data"
  ( grep -v hash_index into_mysql |
	  grep -v image_name_index |
	  grep -v uuid_index |
	  sed -e '{N; s/,\n.*UNIQUE.*);/);/;P;D }' |
	  sed '0,/TRIGGER/s/CREATE TRIGGER/\/*/1' ; echo "*/"
	) | mysql --batch -u<userid> -p<password> digikam4 

Create Views

Once I have the data in MySql, I use PHP to create the "views" that I need to deliver the pages in my PhotoGalleries. Get this script here: updateMysqlViews. This script uses my "album library" code to communicate with the database.

<?php
  include "../album_library.php";

	/**
	 * This SQL sniplit is used in a number of places, so it was easier to contain it centrally
	 * I generate views for both non-private and private images
	 */
function getPublicSubQuery( $privateFlag ) {
			if( $privateFlag == 1 ) {
			$sql = "
    (
      SELECT i.id
      FROM Images i
    )";
		} else {
			$sql = "
    (
      SELECT i.id
      FROM Images i
      INNER JOIN ImageTags it
        ON it.imageid = i.id
      INNER JOIN Tags t
        ON t.id = it.tagid
        AND t.name = 'Public'
    )";
		}

		return $sql;
	}

	function updateAlbumsView($db, $privateFlag) {
		$tableName = "AlbumsView";
		if( $privateFlag == 1 ) {
			$tableName .= "_private";
		}

		$sql = "CREATE TABLE {$tableName}_temp AS
		SELECT
			a.id as album_id, a.relativePath as album_path, a.caption as album_caption, a.collection, a.icon, a.date as album_date,
			i.id as image_id, i.name as image_name,
			icon_album.relativePath as image_path,
			ittl.comment as image_title,
			ic.comment as image_caption,
			ii.creationDate, i.modificationDate,
			md.make, md.model, md.lens, md.aperture, md.focalLength, md.exposureTime, md.sensitivity, md.meteringMode,
			pos.latitudeNumber, pos.longitudeNumber
		FROM Albums a
		INNER JOIN Images i
			ON i.id = a.icon
		INNER JOIN Albums icon_album
			ON icon_album.id = i.album
		LEFT OUTER JOIN ImageComments ittl
			ON ittl.imageid = i.id
			AND ittl.type = 3 
		LEFT OUTER JOIN ImageComments ic
			ON ic.imageid = i.id
			AND ic.type = 1
		LEFT OUTER JOIN ImageInformation ii
			ON ii.imageid = i.id
		LEFT OUTER JOIN ImageMetadata md
			ON md.imageid = i.id
		LEFT OUTER JOIN ImagePositions pos
			ON pos.imageid = i.id
		WHERE i.id IN " . getPublicSubQuery($privateFlag);

		$db->executeSql( $sql, null );

		$sql = "DROP TABLE {$tableName}";
		$db->executeSql( $sql, null );

		$sql = "RENAME TABLE {$tableName}_temp TO {$tableName}";
		$db->executeSql( $sql, null );
	}
	
	function updateImagesView($db, $privateFlag) {
		$tableName = "ImagesView";
		if( $privateFlag == 1 ) {
			$tableName .= "_private";
		}
		$sql = "CREATE TABLE {$tableName}_temp AS
		SELECT
			a.id as album_id, a.relativePath as album_path, a.caption as album_caption, a.collection, a.date as album_date,
			i.id as image_id, i.name as image_name,
			a.relativePath as image_path,
			ittl.comment as image_title,
			ic.comment as image_caption,
			ii.creationDate, i.modificationDate,
			md.make, md.model, md.lens, md.aperture, md.focalLength, md.exposureTime, md.sensitivity, md.meteringMode,
			pos.latitudeNumber, pos.longitudeNumber
		FROM Albums a
		INNER JOIN Images i
			ON i.album = a.id
		LEFT OUTER JOIN ImageComments ittl
			ON ittl.imageid = i.id
			AND ittl.type = 3 
		LEFT OUTER JOIN ImageComments ic
			ON ic.imageid = i.id
			AND ic.type = 1
		LEFT OUTER JOIN ImageInformation ii
			ON ii.imageid = i.id
		LEFT OUTER JOIN ImageMetadata md
			ON md.imageid = i.id
		LEFT OUTER JOIN ImagePositions pos
			ON pos.imageid = i.id
		WHERE upper(i.name) like '%JPG'
			AND i.id IN " . getPublicSubQuery($privateFlag);

		$db->executeSql($sql, null);

		$sql = "DROP TABLE {$tableName}";
		$db->executeSql($sql, null);

		$sql = "RENAME TABLE ${tableName}_temp TO ${tableName}";
		$db->executeSql($sql, null);

		$tableName = "ImageTagsView";
		if( $privateFlag == 1 ) {
			$tableName .= "_private";
		}
    $sql = "CREATE TABLE {$tableName}_temp AS
		SELECT i.album as album_id, i.id as image_id, t.id as tag_id, t.name as tag_name
		FROM Images i
		INNER JOIN ImageTags it
      ON it.imageid = i.id
    INNER JOIN Tags t
      ON t.id = it.tagid
      AND t.pid not in (
				SELECT id FROM Tags WHERE name IN ( '_Digikam_Internal_Tags_', 'Visibility' )
			)
		WHERE upper(i.name) like '%JPG'
			AND i.id IN " . getPublicSubQuery($privateFlag);

		$db->executeSql($sql, null);

		$sql = "DROP TABLE $tableName";
		$db->executeSql($sql, null);

		$sql = "RENAME TABLE {$tableName}_temp TO $tableName";
		$db->executeSql($sql, null);

	}
	
	$db = new DatabaseConnection();
	#create album and image views for both public and private views of the data
	for( $i = 0 ; $i <= 1 ; $i++ ) {
		updateAlbumsView($db, $i);
		updateImagesView($db, $i);
	}

?>