/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.empire.samples.db;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.time.LocalDate;
import java.util.List;

import org.apache.empire.commons.StringUtils;
import org.apache.empire.data.Record;
import org.apache.empire.data.bean.BeanResult;
import org.apache.empire.data.list.DataListEntry;
import org.apache.empire.db.DBColumnExpr;
import org.apache.empire.db.DBCommand;
import org.apache.empire.db.DBContext;
import org.apache.empire.db.DBQuery;
import org.apache.empire.db.DBReader;
import org.apache.empire.db.DBRecord;
import org.apache.empire.db.DBRecordBean;
import org.apache.empire.db.DBRowSet.PartialMode;
import org.apache.empire.db.context.DBContextStatic;
import org.apache.empire.dbms.DBMSHandler;
import org.apache.empire.dbms.postgresql.DBMSHandlerPostgreSQL;
import org.apache.empire.samples.db.SampleDB.Gender;
import org.apache.empire.samples.db.beans.Department;
import org.apache.empire.samples.db.beans.Employee;
import org.apache.empire.samples.db.beans.EmployeeQuery;
import org.apache.empire.samples.db.beans.Payment;
import org.apache.empire.xml.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

public class SampleApp 
{
    // Logger
    private static final Logger log = LoggerFactory.getLogger(SampleApp.class);

    private static SampleConfig config = new SampleConfig();
    
	private SampleDB db = new SampleDB();

	private DBContext context = null;

	private enum QueryType
    {
	    Reader,
        BeanList,
	    XmlDocument
	}

    /**
     * <PRE>
     * This is the entry point of the Empire-DB Sample Application
     * Please check the config.xml configuration file for Database and Connection settings.
     * </PRE>
     * @param args arguments
     */
    public static void main(String[] args)
    {
        // Init Configuration
        config.init((args.length > 0 ? args[0] : "config.xml" ));
        // create the app
        SampleApp app = new SampleApp();
        try
        {   // Run
            log.info("Running DB Sample...");
            app.run();
            // Done
            log.info("DB Sample finished successfully.");
        } catch (Exception e) {
            // Error
            log.error("Running SampleApp failed with Exception" + e.toString(), e);
            if (app.context!=null)
                app.context.rollback();
        } finally {
            if (app.context!=null)
                app.context.discard();
        }
    }

    /**
     * This method runs all the example code
     */
    public void run()
    {
        // SECTION 1: Get a JDBC Connection
        log.info("Step 1: getJDBCConnection()");

        Connection conn = getJDBCConnection();

        // SECTION 2: Choose a DBMSHandler
        log.info("Step 2: getDatabaseProvider()");
        DBMSHandler dbms = getDBMSHandler(config.getDatabaseProvider(), conn);

        // SECTION 3: Create a Context
        context = new DBContextStatic(dbms, conn, true)
            // set optional context features
            .setPreparedStatementsEnabled(false)
            .setRollbackHandlingEnabled(false);

        // SECTION 4: Open Database 
        log.info("Step 3: Open database (and create if not existing)");
        // Open the database (and create if not existing)
        db.open(context);

        // SECTION 5 AND 6: Populate Database and modify Data
        DBCommand cmd = context.createCommand();
        cmd.select(db.EMPLOYEES.count());
        if (context.getUtils().querySingleInt(cmd)==0)
        {   // Employess table is empty. Populate now
            populateAndModify();
        }

        // SECTION 7: Option 1: Query Records and print tab-separated
        log.info("Step 8 Option 1: queryRecords() / Tab-Output");
        queryExample(QueryType.Reader); // Tab-Output

        // SECTION 7: Option 2: Query Records as a list of java beans
        log.info("Step 8 Option 2: queryRecords() / Bean-List-Output");
        queryExample(QueryType.BeanList); // Bean-List-Output

        // SECTION 7: Option 3: Query Records as XML
        log.info("Step 8 Option 3: queryRecords() / XML-Output");
        queryExample(QueryType.XmlDocument); // XML-Output

        // SECTION 8: Use DataList query
        queryDataList();

        // SECTION 9: Use RecordList query
        queryRecordList();

        // SECTION 10: Use Bean Result to query beans
        queryBeans();
        

        /*
        int idEmp = testTransactionCreate(idDevDep);
        testTransactionUpdate(idEmp);
        testTransactionDelete(idEmp);
        */
        
        // Finally, commit any changes
        context.commit();        
    }
	
	/**
     * <PRE>
	 * Opens and returns a JDBC-Connection.
	 * JDBC url, user and password for the connection are obtained from the SampleConfig bean
	 * Please use the config.xml file to change connection params.
     * </PRE>
	 */
	private Connection getJDBCConnection()
    {
		// Establish a new database connection
		Connection conn = null;
		log.info("Connecting to Database'" + config.getJdbcURL() + "' / User=" + config.getJdbcUser());
		try
        {
            // Connect to the database
			Class.forName(config.getJdbcClass()).newInstance();
			conn = DriverManager.getConnection(config.getJdbcURL(), config.getJdbcUser(), config.getJdbcPwd());
			log.info("Connected successfully");
			// set the AutoCommit to false for this connection. 
			// commit must be called explicitly! 
			conn.setAutoCommit(false);
			log.info("AutoCommit has been set to " + conn.getAutoCommit());

		} catch (Exception e)
        {
			log.error("Failed to connect directly to '" + config.getJdbcURL() + "' / User=" + config.getJdbcUser());
			log.error(e.toString());
			throw new RuntimeException(e);
		}
		return conn;
	}

	/**
	 * Creates an Empire-db DatabaseDriver for the given provider and applies dbms specific configuration 
	 */
    private DBMSHandler getDBMSHandler(String provider, Connection conn)
    {
        try
        {   // Get Driver Class Name
            String dbmsHandlerClass = config.getDbmsHandlerClass();
            if (StringUtils.isEmpty(dbmsHandlerClass))
                throw new RuntimeException("Configuration error: Element 'dbmsHandlerClass' not found in node 'properties-"+provider+"'");

            // Create dbms
            DBMSHandler dbms = (DBMSHandler) Class.forName(dbmsHandlerClass).newInstance();

            // Configure dbms
            config.readProperties(dbms, "properties-"+provider, "dbmsHandlerProperties");

            // Special cases
            if (dbms instanceof DBMSHandlerPostgreSQL)
            {   // Create the reverse function that is needed by this sample
                ((DBMSHandlerPostgreSQL)dbms).getDDLGenerator().setCreateReverseFunction(true);
            }

            // done
            return dbms;
            
        } catch (Exception e)
        {   // catch any checked exception and forward it
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    
    @SuppressWarnings("unused")
    private void populateAndModify()
    {
        clearDatabase();
        
        log.info("Step 5: insertDepartment() & insertEmployee()");
        long idDevDep = insertDepartment("Development", "ITTK");
        long idSalDep = insertDepartment("Sales", "ITTK");
        // Insert Employees
        long idEmp1 = insertEmployee(idDevDep, "Peter", "Sharp",  Gender.M, 25000);
        long idEmp2 = insertEmployee(idDevDep, "Fred",  "Bloggs", Gender.M, 0);
        long idEmp3 = insertEmployee(idSalDep, "Emma",  "White",  Gender.F, 19500);
        long idEmp4 = insertEmployee(idSalDep, "John",  "Doe",    Gender.M, 18800);
        long idEmp5 = insertEmployee(idDevDep, "Sarah", "Smith",  Gender.F, 44000);

        // commit
        context.commit();
        
        // SECTION 6: Modify some data
        log.info("Step 6: updateEmployee()");
        updateEmployee(idEmp1, "+49-7531-457160", true);
        updateEmployee(idEmp2, "+49-5555-505050", false);
        // Partial Record
        updatePartialRecord(idEmp3, "+49-040-125486");
        // Update Joined Records (Make Fred Bloggs head of department and set salary)
        updateJoinedRecords(idEmp2, 100000);
    }
    
	/**
     * <PRE>
	 * Empties all Tables.
     * </PRE>
	 */
	private void clearDatabase()
    {
		DBCommand cmd = context.createCommand();
        // Delete all Payments (no constraints)
        context.executeDelete(db.PAYMENTS, cmd);
		// Delete all Employees (no constraints)
		context.executeDelete(db.EMPLOYEES, cmd);
		// Delete all Departments (no constraints)
		context.executeDelete(db.DEPARTMENTS, cmd);
        // commit
        context.commit();
	}

	/**
     * <PRE>
	 * Insert a Department into the Departments table.
     * </PRE>
	 */
	private long insertDepartment(String departmentName, String businessUnit)
    {
        SampleDB.Departments DEP = db.DEPARTMENTS;
		// Insert a Department
		DBRecord rec = new DBRecord(context, DEP);
		rec.create()
		   .set(DEP.NAME, departmentName)
		   .set(DEP.BUSINESS_UNIT, businessUnit)
		   .update();
		// Return Department ID
        return rec.getIdentity();
	}

	/**
     * <PRE>
	 * Inserts an Employee into the Employees table.
     * </PRE>
	 */
	private long insertEmployee(long departmentId, String firstName, String lastName, Gender gender, int salary)
    {
        SampleDB.Employees EMP = db.EMPLOYEES;
		// Insert an Employee
		DBRecord rec = new DBRecord(context, EMP);
		rec.create(null)
           .set(EMP.DEPARTMENT_ID, departmentId)
		   .set(EMP.FIRST_NAME, firstName)
		   .set(EMP.LAST_NAME, lastName)
		   .set(EMP.GENDER, gender)
		   .set(EMP.SALARY, salary)
	       .update();
		// insert payments
		if (salary>0)
		    insertPayments(rec);
		// Return Employee ID
		return rec.getIdentity();
	}

    /**
     * <PRE>
     * Inserts an Payments for a particular Employee
     * </PRE>
     */
    private void insertPayments(DBRecord employee)
    {
        if (employee.isNull(db.EMPLOYEES.SALARY))
            return; // No salary
        // monthlySalary
        BigDecimal monthlySalary = employee.getDecimal(db.EMPLOYEES.SALARY).divide(new BigDecimal(12), 2, RoundingMode.HALF_UP);
        // Insert an Employee
        LocalDate date = LocalDate.now();
        date = date.minusDays(date.getDayOfMonth()-1); // first day of this month
        // Add Payment for each month
        SampleDB.Payments PAY = db.PAYMENTS;
        DBRecord rec = new DBRecord(context, PAY);
        int months = (int)(Math.random()*6)+17;
        for (LocalDate month=date.minusMonths(months); !month.isAfter(date); month=month.plusMonths(1))
        {
            BigDecimal variation = new BigDecimal((Math.random()*200) - 100.0);
            variation = variation.setScale(2, RoundingMode.HALF_UP);
            // insert
            rec.create(DBRecord.key(employee.getIdentity(), month.getYear(), month.getMonth()));
            rec.set(PAY.AMOUNT, monthlySalary.add(variation));
            rec.update();
        }
    }

	/**
     * <PRE>
	 * Updates an employee record by setting the phone number.
     * </PRE>
	 */
	private void updateEmployee(long idEmp, String phoneNumber, boolean useRecord)
    {
	      // Update an Employee
	      if (useRecord)
	      {
	          // Use a DBRecord (recommended)
	          DBRecord rec = new DBRecord(context, db.EMPLOYEES);
	          rec.read(idEmp);
	          // Set
	          rec.set(db.EMPLOYEES.PHONE_NUMBER, phoneNumber);
	          rec.update();
	          
	      }
	      else
	      {   // Or use a DBRecordBean:
	          DBRecordBean rec = new DBRecordBean();
	          rec.read(context, db.EMPLOYEES, idEmp);
	          // Set
	          rec.set(db.EMPLOYEES.PHONE_NUMBER, phoneNumber);
	          rec.update(context);
	      }
	}

    /**
     * <PRE>
     * Updates an employee record by setting the phone number.
     * </PRE>
     */
    private void updatePartialRecord(long employeeId, String phoneNumber)
    {
        // Shortcut for convenience
        SampleDB.Employees EMP = db.EMPLOYEES;
        // Update an Employee with partial record
        // this will only load the EMPLOYEE ID and the PHONE_NUMBER
        DBRecord rec = new DBRecord(context, EMP);
        rec.read(Record.key(employeeId), PartialMode.INCLUDE, EMP.SALUTATION, EMP.FIRST_NAME, EMP.LAST_NAME, EMP.PHONE_NUMBER, EMP.EMAIL);
        // Set
        rec.set(db.EMPLOYEES.PHONE_NUMBER, phoneNumber);
        rec.update();
    }

    /**
     * <PRE>
     * Updates an employee record by setting the phone number.
     * </PRE>
     */
    private void updateJoinedRecords(long idEmp, int salary)
    {
        // Shortcuts for convenience
        SampleDB.Employees EMP = db.EMPLOYEES;
        SampleDB.Departments DEP = db.DEPARTMENTS;

        // Create DBQuery from command
        DBCommand cmd = context.createCommand();
        cmd.select(EMP.getColumns());
        cmd.select(DEP.getColumns());
        cmd.join(EMP.DEPARTMENT_ID, DEP.ID);
        DBQuery query = new DBQuery(cmd, EMP.ID);

        // Make employee Head of Department and update salary
        DBRecord rec = new DBRecord(context, query);
        rec.read(idEmp);
        rec.set(EMP.SALARY, salary);
        rec.set(DEP.HEAD, rec.getString(EMP.LAST_NAME));
        rec.update();
    }

	/**
	 * <PRE>
	 * Performs an SQL-Query and prints the result to System.out
	 * 
	 * First a DBCommand object is used to create the following SQL-Query (Oracle-Syntax):
     *     
     *   SELECT t2.EMPLOYEE_ID, t2.LASTNAME || ', ' || t2.FIRSTNAME AS FULL_NAME, t2.GENDER, t2.PHONE_NUMBER, 
     *          substr(t2.PHONE_NUMBER, length(t2.PHONE_NUMBER)-instr(reverse(t2.PHONE_NUMBER), '-')+2) AS PHONE_EXTENSION, 
     *          t1.NAME AS DEPARTMENT, t1.BUSINESS_UNIT
     *   FROM EMPLOYEES t2 INNER JOIN DEPARTMENTS t1 ON t1.DEPARTMENT_ID = t2.ID
     *   WHERE length(t2.LASTNAME)>0
     *   ORDER BY t2.LASTNAME, t2.FIRSTNAME
     * 
	 * For processing the rows there are three options available:
	 * 
	 *   QueryType.Reader:
	 *     Iterates through all rows and prints field values as tabbed text.
	 *      
     *   QueryType.BeanList:
     *     Obtains the query result as a list of JavaBean objects of type SampleBean.
     *     It then iterates through the list of beans and uses bean.toString() for printing.
     *     
     *   QueryType.XmlDocument:
     *     Obtains the query result as an XML-Document and prints the document.
     *     Please note, that the XML not only contains the data but also the field metadata.
     * </PRE>
	 */
	private void queryExample(QueryType queryType)
    {
        int lastYear = LocalDate.now().getYear()-1;
	    
	    // Define shortcuts for tables used - not necessary but convenient
	    SampleDB.Employees   EMP = db.EMPLOYEES;
	    SampleDB.Departments DEP = db.DEPARTMENTS;
        SampleDB.Payments    PAY = db.PAYMENTS;

	    // The following expression concats lastname + ', ' + firstname
        DBColumnExpr EMPLOYEE_NAME = EMP.LAST_NAME.append(", ").append(EMP.FIRST_NAME).as("EMPLOYEE_NAME");
        DBColumnExpr PAYMENTS_LAST_YEAR = PAY.AMOUNT.sum().as("PAYMENTS_LAST_YEAR");
        
        /*
        // Example: Extracts the extension number from the phone field
        // e.g. substr(PHONE_NUMBER, length(PHONE_NUMBER)-instr(reverse(PHONE_NUMBER), '-')+2) AS PHONE_EXTENSION
        // Hint: Since the reverse() function is not supported by HSQLDB there is special treatment for HSQL
        DBColumnExpr PHONE_LAST_DASH;
        if ( db.getDbms() instanceof DBMSHandlerHSql 
        		|| db.getDbms() instanceof DBMSHandlerDerby
        		|| db.getDbms() instanceof DBMSHandlerH2)
             PHONE_LAST_DASH = EMP.PHONE_NUMBER.indexOf("-", EMP.PHONE_NUMBER.indexOf("-").plus(1)).plus(1); // HSQLDB only
        else PHONE_LAST_DASH = EMP.PHONE_NUMBER.length().minus(EMP.PHONE_NUMBER.reverse().indexOf("-")).plus(2);  
        DBColumnExpr PHONE_EXT_NUMBER = EMP.PHONE_NUMBER.substring(PHONE_LAST_DASH).as("PHONE_EXTENSION");
        */

        /*
        // Example: Select the Gender-Enum as String
        // e.g. case t2.GENDER when 'U' then 'Unknown' when 'M' then 'Male' when 'F' then 'Female' end
        DBColumnExpr GENDER_NAME = EMP.GENDER.decode(EMP.GENDER.getOptions()).as("GENDER_NAME");
        */

        // Select Employee and Department columns
        DBCommand cmd = context.createCommand()
           .selectQualified(EMP.ID) // select "EMPLOYEE_ID"
           .select(EMPLOYEE_NAME, EMP.GENDER, EMP.PHONE_NUMBER, EMP.SALARY)
           .selectQualified(DEP.NAME) // "DEPARMENT_NAME"
           .select(DEP.BUSINESS_UNIT) // "BUSINESS_UNIT" 
           // Joins
           .join(EMP.DEPARTMENT_ID, DEP.ID)
           .joinLeft(EMP.ID, PAY.EMPLOYEE_ID, PAY.YEAR.is(lastYear))
           // Where constraints
           .where(EMP.LAST_NAME.length().isGreaterThan(0))  // always true, just for show
           .where(EMP.GENDER.in(Gender.M, Gender.F))        // always true, just for show
           .where(EMP.RETIRED.is(false))                    // always true, just for show
           // Order by
           .orderBy(EMPLOYEE_NAME);

        // Add payment of last year using a SUM aggregation
        cmd.groupBy(cmd.getSelectExpressions());
        cmd.select(PAYMENTS_LAST_YEAR);

        /*
         * Example for limitRows() and skipRows()
         * Uncomment if you wish
         *
        if (db.getDbms().isSupported(DBMSFeature.QUERY_LIMIT_ROWS))
        {	// set maximum number of rows
        	cmd.limitRows(20);
            if (db.getDbms().isSupported(DBMSFeature.QUERY_SKIP_ROWS))
                cmd.skipRows(1);
        }
        */
        
		// Query Records and print output
		DBReader reader = new DBReader(context);
		try
        {
		    // log select statement (but only once)
		    if (queryType==QueryType.Reader)
		        log.info("Running Query: {}", cmd.getSelect());
		    // Open Reader 
			reader.open(cmd);
			// Print output
			System.out.println("---------------------------------");
			switch(queryType)
			{
			    case Reader:
			        // Text-Output by iterating through all records.
	                while (reader.moveNext())
                    {
	                    System.out.println(reader.getText(EMP.ID)
	                            + "\t" + reader.getText(EMPLOYEE_NAME)
	                            + "\t" + reader.getText(EMP.GENDER)
                                + "\t" + reader.getText(EMP.SALARY)
                                + "\t" + reader.getText(PAYMENTS_LAST_YEAR)
	                            + "\t" + reader.getText(DEP.NAME));
	                }
			        break;
                case BeanList:
                    // Text-Output using a list of Java Beans supplied by the DBReader
                    List<EmployeeQuery> beanList = reader.getBeanList(EmployeeQuery.class);
                    // log.info(String.valueOf(beanList.size()) + " SampleBeans returned from Query.");
                    for (EmployeeQuery b : beanList)
                    {
                        System.out.println(b.toString());
                    }
                    break;
                case XmlDocument:
                    // XML Output
                    Document doc = reader.getXmlDocument();
                    // Print XML Document to System.out
                    XMLWriter.debug(doc);
                    break;
			}
            System.out.println("---------------------------------");
		} finally  {
			// Always close Reader!
			reader.close();
		}
	}
	
	private void queryBeans()
	{
	    SampleDB.Employees EMP = db.EMPLOYEES;
	    
	    DBCommand cmd = context.createCommand();
	    cmd.where(EMP.GENDER.is(Gender.M));
	    cmd.orderBy(EMP.LAST_NAME.desc());
	    List<Employee> list = context.getUtils().queryBeanList(cmd, Employee.class, null);
	    for (Employee emp : list)
	    {
	        System.out.println(emp.toString());
	    }
	    
	    // load department
	    Department department = context.getUtils().queryBean(Department.class, db.DEPARTMENTS.NAME.is("Sales"));
	    Payment first = department.getEmployees().get(0).getPayments().get(0);
	    log.info("First payment amount is {}", first.getAmount());

        // Query all males
	    BeanResult<Employee> result = new BeanResult<Employee>(Employee.class, EMP);
        result.getCommand().where(EMP.GENDER.is(Gender.M));
	    result.fetch(context);
	    
	    log.info("Number of male employees is: "+result.size());

	    // And now, the females
	    result.getCommand().where(EMP.GENDER.is(Gender.F));
	    result.fetch(context);
	    
        log.info("Number of female employees is: "+result.size());
	}
	
	private void queryDataList()
	{
        int lastYear = LocalDate.now().getYear()-1;
        
        // Define shortcuts for tables used - not necessary but convenient
        SampleDB.Employees   EMP = db.EMPLOYEES;
        SampleDB.Departments DEP = db.DEPARTMENTS;
        SampleDB.Payments    PAY = db.PAYMENTS;

        // Employee total query
        DBColumnExpr EMP_TOTAL = PAY.AMOUNT.sum().as("EMP_TOTAL");
        DBCommand cmdEmpTotal = context.createCommand()
           .select(PAY.EMPLOYEE_ID, EMP_TOTAL)
           .where (PAY.YEAR.is(lastYear))
           .groupBy(PAY.EMPLOYEE_ID);
        DBQuery Q_EMP_TOTAL = new DBQuery(cmdEmpTotal, "qet");
        
        // Department total query
        DBColumnExpr DEP_TOTAL = PAY.AMOUNT.sum().as("DEP_TOTAL");
        DBCommand cmdDepTotal  = context.createCommand()
           .select(EMP.DEPARTMENT_ID, DEP_TOTAL)
           .join  (PAY.EMPLOYEE_ID, EMP.ID)
           .where (PAY.YEAR.is(lastYear))
           .groupBy(EMP.DEPARTMENT_ID);
        DBQuery Q_DEP_TOTAL = new DBQuery(cmdDepTotal, "qdt");

        // Percentage of department
        DBColumnExpr PCT_OF_DEP_COST = Q_EMP_TOTAL.column(EMP_TOTAL).multiplyWith(100).divideBy(Q_DEP_TOTAL.column(DEP_TOTAL));
        // Create the employee query
        DBCommand cmd = context.createCommand()
           .select(EMP.ID, EMP.FIRST_NAME, EMP.LAST_NAME, DEP.NAME.as("DEPARTMENT"))
           .select(Q_EMP_TOTAL.column(EMP_TOTAL))
           .select(PCT_OF_DEP_COST.as("PCT_OF_DEPARTMENT_COST"))
           // join Employee with Department
           .join(EMP.DEPARTMENT_ID, DEP.ID)
           // Join with Subqueries
           .joinLeft(EMP.ID, Q_EMP_TOTAL.column(PAY.EMPLOYEE_ID))
           .joinLeft(DEP.ID, Q_DEP_TOTAL.column(EMP.DEPARTMENT_ID))
           // Order by
           .orderBy(DEP.NAME.desc())
           .orderBy(EMP.LAST_NAME);
	    
        List<DataListEntry> list = context.getUtils().queryDataList(cmd);
        /* uncomment this to print full list
        for (DataListEntry dle : list)
            System.out.println(dle.toString());
        */    
        for (DataListEntry dle : list)
        {
            long empId = dle.getRecordId(EMP);
            // int depId = dle.getId(DEP);
            String empName = StringUtils.concat(", ", dle.getString(EMP.LAST_NAME), dle.getString(EMP.FIRST_NAME));
            String depName = dle.getString(DEP.NAME);
            boolean hasPayments =!dle.isNull(Q_EMP_TOTAL.column(EMP_TOTAL));
            if (hasPayments)
            {   // report
                BigDecimal empTotal = dle.getDecimal(Q_EMP_TOTAL.column(EMP_TOTAL));
                BigDecimal pctOfDep = dle.getDecimal(PCT_OF_DEP_COST).setScale(1, RoundingMode.HALF_UP);
                log.info("Eployee[{}]: {}\tDepartment: {}\tPayments: {} ({}% of Department)", empId, empName, depName, empTotal, pctOfDep);
            }
            else
                log.info("Eployee[{}]: {}\tDepartment: {}\tPayments: [No data avaiable]", empId, empName, depName);
        }
        
        /*
        cmd.where(EMP.ID.is(list.get(0).getRecordId(EMP)));
        DataListEntry emp1 = context.getUtils().queryDataEntry(cmd);
        System.out.println(emp1.toString());

        cmd.where(EMP.ID.is(list.get(1).getRecordId(EMP)));
        DataListEntry emp2 = context.getUtils().queryDataEntry(cmd);
        System.out.println(emp2.toString());
        */
	}

	private void queryRecordList()
	{
        SampleDB.Departments DEP = db.DEPARTMENTS;
        SampleDB.Employees EMP = db.EMPLOYEES;
        /*
         * Test RecordList
         */
        DBCommand cmd = context.createCommand();
        cmd.join(EMP.DEPARTMENT_ID, DEP.ID);
        cmd.where(DEP.NAME.is("Development"));
        // query now
        List<DBRecordBean> list = context.getUtils().queryRecordList(cmd, EMP, DBRecordBean.class);
        log.info("RecordList query found {} employees in Development department", list.size());
        for (DBRecordBean record : list)
        {
            Object[] key = record.getKey();
            // print info
            String empName = StringUtils.concat(", ", record.getString(EMP.LAST_NAME), record.getString(EMP.FIRST_NAME));
            String phone   = record.getString(EMP.PHONE_NUMBER);
            BigDecimal salary = record.getDecimal(EMP.SALARY);
            log.info("Eployee[{}]: {}\tPhone: {}\tSalary: {}", StringUtils.toString(key), empName, phone, salary);
            // modify salary
            BigDecimal newSalary = new BigDecimal(2000 + ((Math.random()*200) - 100.0));
            record.set(EMP.SALARY, newSalary);
            // check
            if (record.wasModified(EMP.SALARY))
            {   // Salary was modified
                log.info("Salary was modified for {}. New salary is {}", empName, record.getDecimal(EMP.SALARY));
            }
            // udpate the record
            record.update(context);
            
            // convert to bean
            Employee employee = new Employee();
            record.setBeanProperties(employee);
            System.out.println(employee.toString());
        }
	}
}
