blob: fcf05acdc25b98d575897ad4bf181e3c275ded6c [file] [log] [blame]
/*
* 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.advanced;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
import org.apache.empire.commons.ObjectUtils;
import org.apache.empire.data.DataType;
import org.apache.empire.data.list.DataListEntry;
import org.apache.empire.db.DBCommand;
import org.apache.empire.db.DBContext;
import org.apache.empire.db.DBRecord;
import org.apache.empire.db.DBSQLScript;
import org.apache.empire.db.DBTableColumn;
import org.apache.empire.db.generic.TDatabase;
import org.apache.empire.db.generic.TTable;
import org.apache.empire.db.validation.DBModelChecker;
import org.apache.empire.db.validation.DBModelErrorLogger;
import org.apache.empire.dbms.postgresql.DBMSHandlerPostgreSQL;
import org.apache.empire.samples.db.advanced.records.BrandRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <PRE>
* This file contains the definition of the data model in Java.
* The SampleDB data model consists of three tables and a foreign key relation.
* The tables are defined as nested classes here, but you may put them in separate files if you want to.
*
* PLEASE NOTE THE NAMING CONVENTION:
* Since all tables, views and columns are declared as "final" constants they are all in upper case.
* We recommend using a prefix of T_ for tables and C_ for columns in order to keep them together
* when listed in your IDE's code completion.
* There is no need to stick to this convention but it makes life just another little bit easier.
*
* You may declare other database tables or views in the same way.
* </PRE>
*/
public class CarSalesDB extends TDatabase<CarSalesDB>
{
// Logger
private static final Logger log = LoggerFactory.getLogger(CarSalesDB.class);
/**
* EngineType enum
*/
public enum EngineType
{
P("Petrol"),
D("Diese"),
H("Hybrid"),
E("Electric");
private final String title;
private EngineType(String title)
{
this.title = title;
}
@Override
public String toString()
{
return title;
}
}
/**
* EngineType enum
*/
public enum DealershipType
{
B("Brand"),
I("Independent"),
F("Franchise"),
U("Used cars"),
G("Small garage");
private final String title;
private DealershipType(String title)
{
this.title = title;
}
@Override
public String toString()
{
return title;
}
}
/**
* This class represents the Brand table.
*/
public static class Brand extends TTable<CarSalesDB>
{
public final DBTableColumn WMI;
public final DBTableColumn NAME;
public final DBTableColumn COUNTRY;
public final DBTableColumn UPDATE_TIMESTAMP;
public Brand(CarSalesDB db)
{
super("BRAND", db);
// ID
WMI = addColumn("WMI", DataType.VARCHAR, 3, true); // World Manufacturer code
NAME = addColumn("NAME", DataType.VARCHAR, 80, true);
COUNTRY = addColumn("COUNTRY", DataType.VARCHAR, 80, false);
UPDATE_TIMESTAMP= addColumn("UPDATE_TIMESTAMP", DataType.TIMESTAMP, 0, true);
// Primary Key (automatically set due to AUTOINC column)
setPrimaryKey(WMI);
}
}
/**
* This class represents the Model table.
*/
public static class Model extends TTable<CarSalesDB>
{
public final DBTableColumn ID;
public final DBTableColumn NAME;
public final DBTableColumn CONFIG_NAME;
public final DBTableColumn WMI;
public final DBTableColumn TRIM;
public final DBTableColumn ENGINE_TYPE;
public final DBTableColumn ENGINE_POWER;
public final DBTableColumn BASE_PRICE;
public final DBTableColumn UPDATE_TIMESTAMP;
public Model(CarSalesDB db)
{
super("MODEL", db);
// ID
ID = addColumn("ID", DataType.AUTOINC, 0, true, "MODEL_ID_SEQUENCE"); // Optional Sequence name for some DBMS (e.g. Oracle)
NAME = addColumn("NAME", DataType.VARCHAR, 20, true);
CONFIG_NAME = addColumn("CONFIGURATION", DataType.VARCHAR, 40, true);
WMI = addColumn("WMI", DataType.VARCHAR, 3, true);
TRIM = addColumn("TRIM", DataType.VARCHAR, 20, true);
ENGINE_TYPE = addColumn("ENGINE_TYPE", DataType.CHAR, 1, true, EngineType.class);
ENGINE_POWER = addColumn("ENGINE_POWER", DataType.DECIMAL, 4.0, true);
BASE_PRICE = addColumn("BASE_PRICE", DataType.DECIMAL, 8.2, false);
UPDATE_TIMESTAMP= addColumn("UPDATE_TIMESTAMP", DataType.TIMESTAMP, 0, true);
// Primary Key (automatically set due to AUTOINC column)
// setPrimaryKey(ID);
}
}
/**
* This class represents the Dealer table.
*/
public static class Dealer extends TTable<CarSalesDB>
{
public final DBTableColumn ID;
public final DBTableColumn COMPANY_NAME;
public final DBTableColumn STREET;
public final DBTableColumn CITY;
public final DBTableColumn YEAR_FOUNDED;
public final DBTableColumn UPDATE_TIMESTAMP;
public Dealer(CarSalesDB db)
{
super("DEALER", db);
// ID
ID = addColumn("ID", DataType.AUTOINC, 0, true, "DEALER_ID_SEQUENCE"); // Optional Sequence name for some DBMS (e.g. Oracle)
COMPANY_NAME = addColumn("COMPANY_NAME", DataType.VARCHAR, 40, true);
STREET = addColumn("ADDRESS", DataType.VARCHAR, 40, false);
CITY = addColumn("CITY", DataType.VARCHAR, 20, true);
YEAR_FOUNDED = addColumn("YEAR_FOUNDED", DataType.DECIMAL, 4.0, false);
UPDATE_TIMESTAMP= addColumn("UPDATE_TIMESTAMP", DataType.TIMESTAMP, 0, true);
// Primary Key (automatically set due to AUTOINC column)
// setPrimaryKey(ID);
}
}
/**
* This class represents the Dealer table.
*/
public static class DealerBrands extends TTable<CarSalesDB>
{
public final DBTableColumn DEALER_ID;
public final DBTableColumn BRAND_WMI;
public final DBTableColumn TYPE;
public DealerBrands(CarSalesDB db)
{
super("DEALER_BRANDS", db);
// ID
DEALER_ID = addForgeinKey(db.DEALER, "DEALER_ID", true);
BRAND_WMI = addForgeinKey(db.BRAND, "BRAND_WMI", true);
TYPE = addColumn("TYPE", DataType.CHAR, 1, true, DealershipType.class);
// Primary Key (automatically set due to AUTOINC column)
// setPrimaryKey(ID);
}
}
/**
* This class represents the Sales table.
*/
public static class Sales extends TTable<CarSalesDB>
{
public final DBTableColumn MODEL_ID;
public final DBTableColumn YEAR;
public final DBTableColumn MONTH;
public final DBTableColumn CAR_COLOR;
public final DBTableColumn PRICE;
public Sales(CarSalesDB db)
{
super("SALES", db);
// ID
MODEL_ID = addColumn("MODEL_ID", DataType.INTEGER, 0, true);
YEAR = addColumn("YEAR", DataType.DECIMAL, 4.0, true);
MONTH = addColumn("MONTH", DataType.DECIMAL, 2.0, true);
CAR_COLOR = addColumn("CAR_COLOR", DataType.VARCHAR, 20, false);
PRICE = addColumn("PRICE", DataType.DECIMAL, 8.2, true);
// No primary key!
}
}
// Declare all Tables and Views here
public final Brand BRAND = new Brand(this);
public final Model MODEL = new Model(this);
public final Dealer DEALER = new Dealer(this);
public final DealerBrands DEALER_BRANDS = new DealerBrands(this);
public final Sales SALES = new Sales(this);
private boolean wasCreated;
/**
* Constructor of the SampleDB data model
*
* Put all foreign key relations here.
*/
public CarSalesDB()
{
// Define Foreign-Key Relations
addRelation( MODEL.WMI.referenceOn( BRAND.WMI ));
addRelation( SALES.MODEL_ID.referenceOn( MODEL.ID ));
}
public boolean wasCreated()
{
return wasCreated;
}
@Override
public void open(DBContext context)
{
// Enable prepared statements
setPreparedStatementsEnabled(true);
// Check exists
if (checkExists(context))
{ // attach to driver
super.open(context);
// remember
wasCreated = false;
// yes, it exists, then check the model
checkDataModel(context);
}
else
{ // PostgreSQL does not support DDL in transaction
if(getDbms() instanceof DBMSHandlerPostgreSQL)
setAutoCommit(context, true);
// create the database
createDatabase(context);
// PostgreSQL does not support DDL in transaction
if(getDbms() instanceof DBMSHandlerPostgreSQL)
setAutoCommit(context, false);
// attach to driver
super.open(context);
// populate
wasCreated = true;
// Commit
context.commit();
}
}
private void createDatabase(DBContext context)
{
// create DDL for Database Definition
DBSQLScript script = new DBSQLScript(context);
getCreateDDLScript(script);
// Show DDL Statement
log.info(script.toString());
// Execute Script
script.executeAll(false);
}
private void checkDataModel(DBContext context)
{ try {
DBModelChecker modelChecker = context.getDbms().createModelChecker(this);
// Check data model
log.info("Checking DataModel for {} using {}", getClass().getSimpleName(), modelChecker.getClass().getSimpleName());
// dbo schema
DBModelErrorLogger logger = new DBModelErrorLogger();
modelChecker.checkModel(this, context.getConnection(), logger);
// show result
log.info("Data model check done. Found {} errors and {} warnings.", logger.getErrorCount(), logger.getWarnCount());
} catch(Exception e) {
log.error("FATAL error when checking data model. Probably not properly implemented by DBMSHandler!");
}
}
private void setAutoCommit(DBContext context, boolean enable)
{ try {
context.getConnection().setAutoCommit(enable);
} catch (SQLException e) {
log.error("Unable to set AutoCommit on Connection", e);
}
}
public void populate(SampleAdvContext context)
{
BrandRecord brandVW = new BrandRecord(context); brandVW .insert("WVW", "VW", "Germany");
BrandRecord brandFord = new BrandRecord(context); brandFord .insert("1F", "Ford", "USA");
BrandRecord brandTesla = new BrandRecord(context); brandTesla.insert("5YJ", "Tesla", "USA");
BrandRecord brandToy = new BrandRecord(context); brandToy .insert("JT", "Toyota", "Japan");
DBRecord model = new DBRecord(context, MODEL);
// VW
model.create().set(MODEL.WMI, brandVW).set(MODEL.NAME, "Golf").set(MODEL.CONFIG_NAME, "Golf Style 1,5 l TSI").set(MODEL.TRIM, "Style").set(MODEL.ENGINE_TYPE, EngineType.P).set(MODEL.ENGINE_POWER, 130).set(MODEL.BASE_PRICE,30970).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandVW).set(MODEL.NAME, "Golf").set(MODEL.CONFIG_NAME, "Golf R-Line 2,0 l TSI 4MOTION").set(MODEL.TRIM, "R-Line").set(MODEL.ENGINE_TYPE, EngineType.P).set(MODEL.ENGINE_POWER, 190).set(MODEL.BASE_PRICE,38650).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandVW).set(MODEL.NAME, "Tiguan").set(MODEL.CONFIG_NAME, "Tiguan Life 1,5 l TSI").set(MODEL.TRIM, "Life").set(MODEL.ENGINE_TYPE, EngineType.P).set(MODEL.ENGINE_POWER, 150).set(MODEL.BASE_PRICE,32545).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandVW).set(MODEL.NAME, "Tiguan").set(MODEL.CONFIG_NAME, "Tiguan Elegance 2,0 l TDI SCR").set(MODEL.TRIM, "Elegance").set(MODEL.ENGINE_TYPE, EngineType.D).set(MODEL.ENGINE_POWER, 150).set(MODEL.BASE_PRICE,40845).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandVW).set(MODEL.NAME, "Tiguan").set(MODEL.CONFIG_NAME, "Tiguan R-Line 1,4 l eHybrid").set(MODEL.TRIM, "R-Line").set(MODEL.ENGINE_TYPE, EngineType.H).set(MODEL.ENGINE_POWER, 150).set(MODEL.BASE_PRICE,48090).update();generateRandomSales(model);
// Tesla
model.create().set(MODEL.WMI, brandTesla).set(MODEL.NAME, "Model 3").set(MODEL.CONFIG_NAME, "Model 3 LR").set(MODEL.TRIM, "Long Range").set(MODEL.ENGINE_TYPE, EngineType.E).set(MODEL.ENGINE_POWER, 261).set(MODEL.BASE_PRICE,45940).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandTesla).set(MODEL.NAME, "Model 3").set(MODEL.CONFIG_NAME, "Model 3 Performance").set(MODEL.TRIM, "Performance").set(MODEL.ENGINE_TYPE, EngineType.E).set(MODEL.ENGINE_POWER, 487).set(MODEL.BASE_PRICE,53940).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandTesla).set(MODEL.NAME, "Model Y").set(MODEL.CONFIG_NAME, "Model Y LR").set(MODEL.TRIM, "Long Range").set(MODEL.ENGINE_TYPE, EngineType.E).set(MODEL.ENGINE_POWER, 345).set(MODEL.BASE_PRICE,53940).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandTesla).set(MODEL.NAME, "Model Y").set(MODEL.CONFIG_NAME, "Model Y Performance").set(MODEL.TRIM, "Performance").set(MODEL.ENGINE_TYPE, EngineType.E).set(MODEL.ENGINE_POWER, 450).set(MODEL.BASE_PRICE,58940).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandTesla).set(MODEL.NAME, "Model S").set(MODEL.CONFIG_NAME, "Model S Plaid").set(MODEL.TRIM, "Plaid").set(MODEL.ENGINE_TYPE, EngineType.E).set(MODEL.ENGINE_POWER, 1020).set(MODEL.BASE_PRICE,126990).update(); // no sales
// Ford
model.create().set(MODEL.WMI, brandFord).set(MODEL.NAME, "Mustang").set(MODEL.CONFIG_NAME, "Mustang GT 5,0 l Ti-VCT V8").set(MODEL.TRIM, "GT").set(MODEL.ENGINE_TYPE, EngineType.P).set(MODEL.ENGINE_POWER, 449).set(MODEL.BASE_PRICE,54300).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandFord).set(MODEL.NAME, "Mustang").set(MODEL.CONFIG_NAME, "Mustang Mach1 5,0 l Ti-VCT V8").set(MODEL.TRIM, "Mach1").set(MODEL.ENGINE_TYPE, EngineType.P).set(MODEL.ENGINE_POWER, 460).set(MODEL.BASE_PRICE,62800).update();generateRandomSales(model);
// Toyota
model.create().set(MODEL.WMI, brandToy).set(MODEL.NAME, "Prius").set(MODEL.CONFIG_NAME, "Prius Hybrid 1,8-l-VVT-i").set(MODEL.TRIM, "Basis").set(MODEL.ENGINE_TYPE, EngineType.H).set(MODEL.ENGINE_POWER, 122).set(MODEL.BASE_PRICE,38000).update();generateRandomSales(model);
model.create().set(MODEL.WMI, brandToy).set(MODEL.NAME, "Supra").set(MODEL.CONFIG_NAME, "GR Supra Pure 2,0 l Twin-Scroll Turbo").set(MODEL.TRIM, "Pure").set(MODEL.ENGINE_TYPE, EngineType.P).set(MODEL.ENGINE_POWER, 258).set(MODEL.BASE_PRICE,49290).update();generateRandomSales(model);
}
private void generateRandomSales(DBRecord model)
{
int baseYear = LocalDate.now().getYear()-3;
BigDecimal price = model.getDecimal(MODEL.BASE_PRICE);
if (ObjectUtils.isEmpty(price))
return;
DBRecord sale = new DBRecord(model.getContext(), SALES);
for (int i = (int)(Math.random()*99)+5; i>0; i--)
{
int year = (int)(Math.random()*3)+baseYear;
int month = (int)(Math.random()*12)+1;
BigDecimal variation = new BigDecimal((Math.random()*200) - 100.0);
variation = variation.setScale(2, RoundingMode.HALF_UP);
sale.create()
.set(SALES.MODEL_ID, model.getId())
.set(SALES.YEAR, year)
.set(SALES.MONTH, month)
.set(SALES.PRICE, price.add(variation))
.update();
}
}
public static class QueryResult
{
private String brand;
private String model;
private BigDecimal basePrice;
private int salesCount;
private BigDecimal avgSalesPrice;
private BigDecimal priceDifference;
public QueryResult(String brand, String model, BigDecimal basePrice
, int salesCount, BigDecimal avgSalesPrice, BigDecimal priceDifference)
{
this.brand = brand;
this.model = model;
this.basePrice = basePrice;
this.salesCount = salesCount;
this.avgSalesPrice = avgSalesPrice;
this.priceDifference = priceDifference;
}
}
public void queryDemo(DBContext context)
{
/*
DBCommand cmd = this.createCommand()
.select(BRAND.NAME.as("BRAND"), MODEL.NAME.as("MODEL"), MODEL.BASE_PRICE.avg(), SALES.MODEL_ID.count(), SALES.PRICE.avg())
.select(SALES.PRICE.avg().minus(MODEL.BASE_PRICE.avg()).round(2).as("DIFFERENCE"))
.join(MODEL.WMI, BRAND.ID)
.joinLeft(MODEL.ID, SALES.MODEL_ID, SALES.YEAR.is(2021))
.where(MODEL.ENGINE_TYPE.in(EngineType.H, EngineType.E)) // Hybrid and Electric
.where(MODEL.BASE_PRICE.isGreaterThan(30000))
.groupBy(BRAND.NAME, MODEL.NAME)
.having(SALES.MODEL_ID.count().isGreaterThan(10))
.orderBy(BRAND.NAME.desc(), MODEL.NAME.asc());
*/
DBCommand cmd = context.createCommand()
.selectQualified(BRAND.NAME, MODEL.CONFIG_NAME)
.select (MODEL.BASE_PRICE)
.select (SALES.MODEL_ID.count(), SALES.PRICE.avg())
.select (SALES.PRICE.avg().minus(MODEL.BASE_PRICE.avg()).round(2))
.join (MODEL.WMI, BRAND.WMI)
.joinLeft(MODEL.ID, SALES.MODEL_ID, SALES.YEAR.is(2021)) // only year 2021
.where (MODEL.ENGINE_TYPE.in(EngineType.P, EngineType.H, EngineType.E)) // Petrol, Hybrid, Electric
.where (MODEL.BASE_PRICE.isGreaterThan(30000))
.groupBy (BRAND.NAME, MODEL.CONFIG_NAME, MODEL.BASE_PRICE)
.having (SALES.MODEL_ID.count().isGreaterThan(5))
.orderBy (BRAND.NAME.desc(), MODEL.CONFIG_NAME.asc());
/*
List<DataListEntry> list = context.getUtils().queryDataList(cmd);
for (DataListEntry dle : list)
{
System.out.println(dle.toString());
}
*/
DataListEntry entry = context.getUtils().queryDataEntry(cmd);
for (int i=0; i<entry.getFieldCount(); i++)
log.info("col {} -> {}", entry.getColumn(i).getName(), entry.getColumn(i).getBeanPropertyName());
List<QueryResult> list = context.getUtils().queryBeanList(cmd, QueryResult.class, null);
log.info("queryBeanList returnes {} items", list.size());
}
public void updateDemo(DBContext context)
{
DBCommand cmd = context.createCommand()
.set (MODEL.BASE_PRICE.to(55000)) // set the price-tag
.join (MODEL.WMI, BRAND.WMI)
.where(BRAND.NAME.is("Tesla"))
.where(MODEL.NAME.is("Model 3").and(MODEL.TRIM.is("Performance")));
/*
* Clone test
DBCommand cl1 = cmd.clone();
cl1.set(MODEL.BASE_PRICE.to(66000)); // set the price-tag
cl1.where(BRAND.NAME.is("Foo"));
log.info("cmd= {} params={}", cmd.getUpdate(), cmd.getParamValues());
log.info("cmd= {} params={}", cl1.getUpdate(), cl1.getParamValues());
*/
// and off you go...
context.executeUpdate(cmd);
}
}