← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 9259: Impl a simple sharding mechanism for analytics tables

 

------------------------------------------------------------
revno: 9259
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Tue 2012-12-11 02:38:43 +0100
message:
  Impl a simple sharding mechanism for analytics tables
added:
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/ShardUtils.java
  dhis-2/dhis-services/dhis-service-analytics/src/test/
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/
  dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/ShardUtilsTest.java
modified:
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableManager.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java
  dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableManager.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableManager.java	2012-12-10 20:41:29 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/AnalyticsTableManager.java	2012-12-11 01:38:43 +0000
@@ -27,18 +27,49 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.Future;
 
 public interface AnalyticsTableManager
 {
-    void createTable();
-
-    Future<?> createIndexesAsync( List<String> columns );
-    
-    void swapTable();
-    
-    void populateTable();    
+    public static final String TABLE_NAME = "analytics";
+    public static final String TABLE_TEMP_SUFFIX = "_temp";
+    public static final String TABLE_NAME_TEMP = TABLE_NAME + TABLE_TEMP_SUFFIX;
+    
+    /**
+     * Attempts to drop and then create analytics table.
+     * 
+     * @param tableName the table name.
+     */
+    void createTable( String tableName );
+    
+    /**
+     * Creates single indexes on the given columns of the analytics table with
+     * the given name.
+     * 
+     * @param tableName the name of the table to create indexes on.
+     * @param columns the columns to create single indexes for.
+     */
+    Future<?> createIndexesAsync( String tableName, List<String> columns );
+    
+    /**
+     * Attempts to drop analytics table, then rename temporary table to analytics
+     * table.
+     * 
+     * @param tableName the name of the analytics table.
+     */
+    void swapTable( String tableName );
+    
+    /**
+     * Copies and denormalizes rows from data value table into analytics table.
+     * The data range is based on the start date of the data value row.
+     * 
+     * @param tableName the name of the analytics table.
+     * @param startDate the start date for the data value row start date
+     * @param endDate the end date for the data value row end date
+     */
+    Future<?> populateTableAsync( String tableName, Date startDate, Date endDate );    
 
     /**
      * Returns a list of string arrays in where the first index holds the database
@@ -51,4 +82,28 @@
      * Returns a list of database column names.
      */
     List<String> getDimensionColumnNames();
+
+    /**
+     * Retrieves the start date of the period of the earliest data value row.
+     */
+    Date getEarliestData();
+    
+    /**
+     * Retrieves the end date of the period of the latest data value row.
+     */
+    Date getLatestData();
+    
+    /**
+     * Checks whether the given table has no rows, if so drops the table.
+     * 
+     * @param tableName the name of the table to prune.
+     */
+    void pruneTable( String tableName );
+    
+    /**
+     * Drops the given table.
+     * 
+     * @param tableName the name of the table to drop.
+     */
+    void dropTable( String tableName );
 }

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java	2012-12-10 20:41:29 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/DefaultAnalyticsTableService.java	2012-12-11 01:38:43 +0000
@@ -28,6 +28,7 @@
  */
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.Future;
 
@@ -35,6 +36,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.analytics.AnalyticsTableManager;
 import org.hisp.dhis.analytics.AnalyticsTableService;
+import org.hisp.dhis.period.Period;
 import org.hisp.dhis.system.util.Clock;
 import org.hisp.dhis.system.util.ConcurrentUtils;
 import org.hisp.dhis.system.util.PaginatedList;
@@ -63,17 +65,29 @@
     {
         Clock clock = new Clock().startClock().logTime( "Starting update..." );
         
-        tableManager.createTable();
-        clock.logTime( "Created analytics table" );
-        
-        tableManager.populateTable();
-        clock.logTime( "Populated analytics table" );
-        
-        createIndexes();
-        clock.logTime( "Created all indexes, update done" );
-        
-        tableManager.swapTable();
-        clock.logTime( "Swapped analytics table" );
+        final Date earliest = tableManager.getEarliestData();
+        final Date latest = tableManager.getLatestData();
+        final List<String> tables = ShardUtils.getTempTableNames( earliest, latest );        
+        clock.logTime( "Checked data timespan" );
+        
+        //dropTables( tables ); //remove
+        
+        createTables( tables );
+        clock.logTime( "Created analytics tables" );
+        
+        populateTables( tables );
+        clock.logTime( "Populated analytics tables" );
+
+        pruneTables( tables );
+        clock.logTime( "Pruned analytics tables" );
+        
+        createIndexes( tables );
+        clock.logTime( "Created all indexes" );
+        
+        swapTables( tables );
+        clock.logTime( "Swapped analytics tables" );
+        
+        clock.logTime( "Analytics tables update done" );
         
         return null;
     }
@@ -82,21 +96,77 @@
     // Supportive methods
     // -------------------------------------------------------------------------
   
-    private void createIndexes()
+    private void createTables( List<String> tables )
+    {
+        for ( String table : tables )
+        {
+            tableManager.createTable( table );
+        }
+    }
+    
+    private void populateTables( List<String> tables )
+    {
+        int pageSize = Math.max( ( SystemUtils.getCpuCores() - 1 ), 1 );
+        
+        List<List<String>> tablePages = new PaginatedList<String>( tables ).setPageSize( pageSize ).getPages();
+        
+        for ( List<String> tablePage : tablePages )
+        {
+            List<Future<?>> futures = new ArrayList<Future<?>>();
+            
+            for ( String table : tablePage )
+            {
+                Period period = ShardUtils.getPeriod( table );
+                
+                futures.add( tableManager.populateTableAsync( table, period.getStartDate(), period.getEndDate() ) );
+            }
+            
+            ConcurrentUtils.waitForCompletion( futures );
+        }
+    }
+    
+    public void pruneTables( List<String> tables )
+    {
+        for ( String table : tables )
+        {
+            tableManager.pruneTable( table );
+        }
+    }
+    
+    private void createIndexes( List<String> tables )
     {
         int pages = Math.max( ( SystemUtils.getCpuCores() - 1 ), 1 );
         
         log.info( "No of pages: " + pages );
         
-        List<Future<?>> futures = new ArrayList<Future<?>>();
-
-        List<List<String>> columnPages = new PaginatedList<String>( tableManager.getDimensionColumnNames() ).setNumberOfPages( pages ).getPages();
-        
-        for ( List<String> columnPage : columnPages )
-        {
-            futures.add( tableManager.createIndexesAsync( columnPage ) );
-        }
-        
-        ConcurrentUtils.waitForCompletion( futures );        
+        for ( String table : tables )
+        {
+            List<Future<?>> futures = new ArrayList<Future<?>>();
+    
+            List<List<String>> columnPages = new PaginatedList<String>( tableManager.getDimensionColumnNames() ).setNumberOfPages( pages ).getPages();
+            
+            for ( List<String> columnPage : columnPages )
+            {
+                futures.add( tableManager.createIndexesAsync( table, columnPage ) );
+            }
+            
+            ConcurrentUtils.waitForCompletion( futures );
+        }
+    }
+    
+    private void swapTables( List<String> tables )
+    {
+        for ( String table : tables )
+        {
+            tableManager.swapTable( table );
+        }
+    }
+    
+    protected void dropTables( List<String> tables )
+    {
+        for ( String table : tables )
+        {
+            tableManager.dropTable( table );
+        }
     }
 }

=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java	2012-12-10 20:41:29 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/JdbcAnalyticsTableManager.java	2012-12-11 01:38:43 +0000
@@ -30,6 +30,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.concurrent.Future;
 
@@ -41,6 +42,7 @@
 import org.hisp.dhis.organisationunit.OrganisationUnitLevel;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
 import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.system.util.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.BadSqlGrammarException;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -62,12 +64,10 @@
     implements AnalyticsTableManager
 {
     private static final Log log = LogFactory.getLog( JdbcAnalyticsTableManager.class );
-    
+
     public static final String PREFIX_ORGUNITGROUPSET = "ougs_";
     public static final String PREFIX_ORGUNITLEVEL = "idlevel";
     public static final String PREFIX_INDEX = "index_";
-    public static final String TABLE_NAME = "analytics";
-    public static final String TABLE_NAME_TEMP = "analytics_temp";
     
     @Autowired
     private OrganisationUnitService organisationUnitService;
@@ -84,14 +84,14 @@
   
     //TODO shard on data quarter based on start date
     //TODO average aggregation operator data, pre-aggregate in time dimension, not in org unit dimension
-        
-    public void createTable()
+    
+    public void createTable( String tableName )
     {
-        final String sqlDrop = "drop table " + TABLE_NAME_TEMP;
+        final String sqlDrop = "drop table " + tableName;
         
         executeSilently( sqlDrop );
         
-        String sqlCreate = "create table " + TABLE_NAME_TEMP + " (";
+        String sqlCreate = "create table " + tableName + " (";
         
         for ( String[] col : getDimensionColumns() )
         {
@@ -106,16 +106,17 @@
     }
     
     @Async
-    public Future<?> createIndexesAsync( List<String> columns )
+    public Future<?> createIndexesAsync( String tableName, List<String> columns )
     {
         for ( String column : columns )
         {        
-            final String sql = "create index " + PREFIX_INDEX +
-                column + " on " + TABLE_NAME_TEMP + " (" + column + ")";
+            final String index = PREFIX_INDEX + column + "_" + tableName;
+            
+            final String sql = "create index " + index + " on " + tableName + " (" + column + ")";
                 
             executeSilently( sql );
             
-            log.info( "Created index on column: " + column );
+            log.info( "Created index: " + index );
         }
         
         log.info( "Indexes created" );
@@ -123,27 +124,35 @@
         return null;
     }
 
-    public void swapTable()
+    public void swapTable( String tableName )
     {
-        final String sqlDrop = "drop table " + TABLE_NAME;
+        final String realTable = tableName.replaceFirst( TABLE_TEMP_SUFFIX, "" );
+        
+        final String sqlDrop = "drop table " + realTable;
         
         executeSilently( sqlDrop );
         
-        final String sqlAlter = "alter table " + TABLE_NAME_TEMP + " rename to " + TABLE_NAME;
-        
-        jdbcTemplate.execute( sqlAlter );
-    }
-    
-    public void populateTable()
-    {
-        populateTable( "cast(dv.value as double precision)" , "int" );
-        
-        populateTable( "1 as value" , "bool" );
-    }
-    
-    private void populateTable( String valueExpression, String valueType )
-    {
-        String insert = "insert into " + TABLE_NAME_TEMP + " (";
+        final String sqlAlter = "alter table " + tableName + " rename to " + realTable;
+        
+        executeSilently( sqlAlter );
+    }
+    
+    @Async
+    public Future<?> populateTableAsync( String tableName, Date startDate, Date endDate )
+    {
+        populateTable( tableName, startDate, endDate, "cast(dv.value as double precision)", "int" );
+        
+        populateTable( tableName, startDate, endDate, "1 as value" , "bool" );
+        
+        return null;
+    }
+    
+    private void populateTable( String tableName, Date startDate, Date endDate, String valueExpression, String valueType )
+    {
+        final String start = DateUtils.getMediumDateString( startDate );
+        final String end = DateUtils.getMediumDateString( endDate );
+        
+        String insert = "insert into " + tableName + " (";
         
         for ( String[] col : getDimensionColumns() )
         {
@@ -168,7 +177,9 @@
             "left join _period_no_disaggregation_structure ps on dv.periodid=ps.periodid " +
             "left join dataelement de on dv.dataelementid=de.dataelementid " +
             "left join period pe on dv.periodid=pe.periodid " +
-            "where de.valuetype='" + valueType + "' and pe.startdate >= '2011-10-01'";
+            "where de.valuetype='" + valueType + "' " +
+            "and pe.startdate >= '" + start + "' " +
+            "and pe.startdate <= '" + end + "'";
 
         final String sql = insert + select;
         
@@ -229,6 +240,48 @@
         return columnNames;
     }
 
+    public Date getEarliestData()
+    {
+        final String sql = "select min(pe.startdate) from datavalue dv " +
+            "join period pe on dv.periodid=pe.periodid";
+        
+        return jdbcTemplate.queryForObject( sql, Date.class );
+    }
+
+    public Date getLatestData()
+    {
+        final String sql = "select max(pe.startdate) from datavalue dv " +
+            "join period pe on dv.periodid=pe.periodid";
+        
+        return jdbcTemplate.queryForObject( sql, Date.class );
+    }
+    
+    public void pruneTable( String tableName )
+    {
+        final String sqlCount = "select count(*) from " + tableName;
+        
+        log.info( "Count SQL: " + sqlCount );
+        
+        final boolean empty = jdbcTemplate.queryForInt( sqlCount ) == 0;
+        
+        if ( empty )
+        {
+            final String sqlDrop = "drop table " + tableName;
+            
+            executeSilently( sqlDrop );
+            
+            log.info( "Drop SQL: " + sqlDrop );
+        }
+    }
+    
+    public void dropTable( String tableName )
+    {
+        final String realTable = tableName.replaceFirst( TABLE_TEMP_SUFFIX, "" );
+        
+        executeSilently( "drop table " + tableName );
+        executeSilently( "drop table " + realTable );
+    }
+    
     // -------------------------------------------------------------------------
     // Supportive methods
     // -------------------------------------------------------------------------

=== added file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/ShardUtils.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/ShardUtils.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/table/ShardUtils.java	2012-12-11 01:38:43 +0000
@@ -0,0 +1,96 @@
+package org.hisp.dhis.analytics.table;
+
+/*
+ * Copyright (c) 2004-2012, University of Oslo
+ * 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 the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER OR 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.
+ */
+
+import static org.hisp.dhis.analytics.AnalyticsTableManager.TABLE_NAME;
+import static org.hisp.dhis.analytics.AnalyticsTableManager.TABLE_NAME_TEMP;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.period.QuarterlyPeriodType;
+
+public class ShardUtils
+{
+    private static final QuarterlyPeriodType QUARTERLY = new QuarterlyPeriodType();
+    
+    private static final String SEP = "_";
+
+    public static List<String> getTempTableNames( Date earliest, Date latest )
+    {   
+        if ( earliest == null || latest == null || earliest.after( latest ) )
+        {
+            throw new IllegalArgumentException( "Earliest or latest date invalid: " + earliest + ", " + latest );
+        }
+        
+        List<String> tables = new ArrayList<String>();
+        
+        Period period = QUARTERLY.createPeriod( earliest );
+        
+        while ( period != null && period.getStartDate().before( latest ) )
+        {
+            String table = TABLE_NAME_TEMP + SEP + period.getIsoDate();
+            
+            tables.add( table );
+            
+            period = QUARTERLY.getNextPeriod( period );
+        }
+        
+        return tables;
+    }
+    
+    public static String getTable( String isoPeriod )
+    {
+        Period period = PeriodType.getPeriodFromIsoString( isoPeriod );
+        
+        if ( period == null )
+        {
+            throw new IllegalArgumentException( "Illegal ISO period: " +  isoPeriod );
+        }
+        
+        Period quarter = QUARTERLY.createPeriod( period.getStartDate() );
+        
+        return TABLE_NAME + SEP + quarter.getIsoDate();
+    }
+    
+    public static Period getPeriod( String tableName )
+    {
+        if ( tableName == null || tableName.indexOf( SEP ) == -1 )
+        {
+            throw new IllegalArgumentException( "Illegal table name: " + tableName );
+        }
+        
+        String[] split = tableName.split( SEP );
+        String isoPeriod = split[split.length - 1];
+        
+        return PeriodType.getPeriodFromIsoString( isoPeriod );
+    }
+}

=== added directory 'dhis-2/dhis-services/dhis-service-analytics/src/test'
=== added directory 'dhis-2/dhis-services/dhis-service-analytics/src/test/java'
=== added directory 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org'
=== added directory 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp'
=== added directory 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis'
=== added directory 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics'
=== added directory 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table'
=== added file 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/ShardUtilsTest.java'
--- dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/ShardUtilsTest.java	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/table/ShardUtilsTest.java	2012-12-11 01:38:43 +0000
@@ -0,0 +1,82 @@
+package org.hisp.dhis.analytics.table;
+
+/*
+ * Copyright (c) 2004-2012, University of Oslo
+ * 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 the HISP project nor the names of its contributors may
+ *   be used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER OR 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.
+ */
+
+import static org.hisp.dhis.analytics.AnalyticsTableManager.TABLE_NAME;
+import static org.hisp.dhis.analytics.AnalyticsTableManager.TABLE_NAME_TEMP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.List;
+
+import org.hisp.dhis.period.Cal;
+import org.hisp.dhis.period.Period;
+import org.hisp.dhis.period.QuarterlyPeriodType;
+import org.junit.Test;
+
+public class ShardUtilsTest
+{
+    @Test
+    public void testGetTableNames()
+    {
+        Cal cal = new Cal();
+        Date earliest = cal.set( 2000, 5, 4 ).time();
+        Date latest = cal.set( 2001, 2, 10 ).time();
+        
+        List<String> tables = ShardUtils.getTempTableNames( earliest, latest );
+        
+        assertEquals( 4, tables.size() );
+        assertTrue( tables.contains( TABLE_NAME_TEMP + "_2000Q2" ) );
+        assertTrue( tables.contains( TABLE_NAME_TEMP + "_2000Q3" ) );
+        assertTrue( tables.contains( TABLE_NAME_TEMP + "_2000Q4" ) );
+        assertTrue( tables.contains( TABLE_NAME_TEMP + "_2001Q1" ) );
+    }
+    
+    @Test
+    public void testGetTable()
+    {
+        assertEquals( TABLE_NAME + "_2000Q4", ShardUtils.getTable( "200011" ) );
+        assertEquals( TABLE_NAME + "_2000Q1", ShardUtils.getTable( "2000W02" ) );
+        assertEquals( TABLE_NAME + "_2000Q2", ShardUtils.getTable( "2000Q2" ) );
+        assertEquals( TABLE_NAME + "_2000Q3", ShardUtils.getTable( "2000S2" ) );
+        assertEquals( TABLE_NAME + "_2000Q1", ShardUtils.getTable( "2000" ) );
+    }
+    
+    @Test
+    public void testGetPeriod()
+    {
+        Cal cal = new Cal();
+        
+        Period q2 = new QuarterlyPeriodType().createPeriod( cal.set( 2000, 4, 1 ).time() );
+        Period q4 = new QuarterlyPeriodType().createPeriod( cal.set( 2000, 10, 1 ).time() );
+        
+        assertEquals( q2, ShardUtils.getPeriod( TABLE_NAME_TEMP + "_2000Q2" ) );
+        assertEquals( q4, ShardUtils.getPeriod( TABLE_NAME_TEMP + "_2000Q4" ) );
+    }
+}