MySQLQueryObject.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2024 Link.it srl (https://link.it).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openspcoop2.utils.sql;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import org.openspcoop2.utils.TipiDatabase;
import org.openspcoop2.utils.date.DateUtils;
/**
* Classe dove vengono forniti utility per la conversione di comandi SQL per il database mysql
*
*
* @author Poli Andrea (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class MySQLQueryObject extends SQLQueryObjectCore{
public MySQLQueryObject(TipiDatabase tipoDatabase) {
super(tipoDatabase);
}
/**
* Ritorna una costante di tipo 'timestamp'
*
* @param date Costante
*/
@Override
public String getSelectTimestampConstantField(Date date) throws SQLQueryObjectException{
SimpleDateFormat sqlDateformat = DateUtils.getDefaultDateTimeFormatter("yyyy-MM-dd HH:mm:ss.SSS");
return "STR_TO_DATE('"+sqlDateformat.format(date)+"', '%Y-%m-%d %H:%i:%s.%f')";
}
@Override
public String getUnixTimestampConversion(String column){
return "(UNIX_TIMESTAMP("+column+")*1000)";
}
@Override
public String getDiffUnixTimestamp(String columnMax,String columnMin){
return "( "+getUnixTimestampConversion(columnMax)+" - "+getUnixTimestampConversion(columnMin)+" )";
}
/**
* Aggiunge un field count alla select, con un alias, del campo <var>fieldAvg</var> (di tipo Timestamp)
* es: SELECT avg(fieldAvg) as alias FROM ....
*
* @param field Nome del Field
* @param alias Alias
*/
@Override
public ISQLQueryObject addSelectAvgTimestampField(String field,String alias) throws SQLQueryObjectException{
if(field==null)
throw new SQLQueryObjectException(SQLQueryObjectCore.FIELD_DEVE_ESSERE_DIVERSO_NULL);
// Trasformo in UNIX_TIMESTAMP
String fieldSQL = "avg("+this.getUnixTimestampConversion(field)+")";
if(alias != null){
/**fieldSQL = fieldSQL + " as "+alias;*/
fieldSQL = fieldSQL + this.getDefaultAliasFieldKeyword()+alias;
}
this.engineAddSelectField(null,fieldSQL,null,false,true);
this.fieldNames.add(alias);
return this;
}
/**
* Aggiunge un field count alla select, con un alias, del campo <var>field</var> (di tipo Timestamp)
* es: SELECT max(field) as alias FROM ....
*
* @param field Nome del Field
* @param alias Alias
*/
@Override
public ISQLQueryObject addSelectMaxTimestampField(String field,String alias) throws SQLQueryObjectException{
if(field==null)
throw new SQLQueryObjectException(SQLQueryObjectCore.FIELD_DEVE_ESSERE_DIVERSO_NULL);
// Trasformo in UNIX_TIMESTAMP
String fieldSQL = "max("+this.getUnixTimestampConversion(field)+")";
if(alias != null){
/**fieldSQL = fieldSQL + " as "+alias;*/
fieldSQL = fieldSQL + this.getDefaultAliasFieldKeyword()+alias;
}
this.engineAddSelectField(null,fieldSQL,null,false,true);
this.fieldNames.add(alias);
return this;
}
/**
* Aggiunge un field count alla select, con un alias, del campo <var>field</var> (di tipo Timestamp)
* es: SELECT min(field) as alias FROM ....
*
* @param field Nome del Field
* @param alias
*/
@Override
public ISQLQueryObject addSelectMinTimestampField(String field,String alias) throws SQLQueryObjectException{
if(field==null)
throw new SQLQueryObjectException(SQLQueryObjectCore.FIELD_DEVE_ESSERE_DIVERSO_NULL);
// Trasformo in UNIX_TIMESTAMP
String fieldSQL = "min("+this.getUnixTimestampConversion(field)+")";
if(alias != null){
/**fieldSQL = fieldSQL + " as "+alias;*/
fieldSQL = fieldSQL + this.getDefaultAliasFieldKeyword()+alias;
}
this.engineAddSelectField(null,fieldSQL,null,false,true);
this.fieldNames.add(alias);
return this;
}
/**
* Aggiunge un field count alla select, con un alias, del campo <var>field</var> (di tipo Timestamp)
* es: SELECT sum(field) as alias FROM ....
*
* @param field Nome del Field
* @param alias
*/
@Override
public ISQLQueryObject addSelectSumTimestampField(String field,String alias) throws SQLQueryObjectException{
if(field==null)
throw new SQLQueryObjectException(SQLQueryObjectCore.FIELD_DEVE_ESSERE_DIVERSO_NULL);
// Trasformo in UNIX_TIMESTAMP
String fieldSQL = "sum("+this.getUnixTimestampConversion(field)+")";
if(alias != null){
/**fieldSQL = fieldSQL + " as "+alias;*/
fieldSQL = fieldSQL + this.getDefaultAliasFieldKeyword()+alias;
}
this.engineAddSelectField(null,fieldSQL,null,false,true);
this.fieldNames.add(alias);
return this;
}
/**
* Aggiunge una tabella di ricerca del from
* es: SELECT * from ( subSelect )
*
* @param subSelect subSelect
*/
@Override
public ISQLQueryObject addFromTable(ISQLQueryObject subSelect) throws SQLQueryObjectException{
StringBuilder bf = new StringBuilder();
bf.append(" ( ");
bf.append(subSelect.createSQLQuery());
bf.append(SQLQueryObjectCore.AS_SUBQUERY_SUFFIX+getSerial()+" ");
this.addFromTable(bf.toString());
return this;
}
@Override
protected EscapeSQLConfiguration getEscapeSQLConfiguration(){
EscapeSQLConfiguration config = new EscapeSQLConfiguration();
config.addCharacter('_');
config.addCharacter('%');
config.addCharacter('|');
config.setUseEscapeClausole(true);
config.setEscape('|');
// special
config.addCharacterWithOtherEscapeChar('\\','\\');
config.addCharacterWithOtherEscapeChar('\'','\'');
return config;
}
private SQLQueryObjectException newSQLQueryObjectExceptionDayFormatEnum(DayFormatEnum dayFormat) {
return new SQLQueryObjectException("DayFormatEnum '"+dayFormat+"' unknown");
}
@Override
public String getExtractDayFormatFromTimestampFieldPrefix(DayFormatEnum dayFormat) throws SQLQueryObjectException {
if(dayFormat==null) {
throw new SQLQueryObjectException("dayFormat undefined");
}
switch (dayFormat) {
case FULL_DAY_NAME:
return "DAYNAME(";
case SHORT_DAY_NAME:
return "DATE_FORMAT(";
case DAY_OF_YEAR:
return "DAYOFYEAR(";
case DAY_OF_WEEK:
return "DAYOFWEEK(";
}
throw newSQLQueryObjectExceptionDayFormatEnum(dayFormat);
}
@Override
public String getExtractDayFormatFromTimestampFieldSuffix(DayFormatEnum dayFormat) throws SQLQueryObjectException {
if(dayFormat==null) {
throw new SQLQueryObjectException("dayFormat undefined");
}
switch (dayFormat) {
case SHORT_DAY_NAME:
return ", '%a')";
case FULL_DAY_NAME:
case DAY_OF_YEAR:
case DAY_OF_WEEK:
return ")";
}
throw newSQLQueryObjectExceptionDayFormatEnum(dayFormat);
}
/**
* Crea una SQL Query con i dati dell'oggetto
*
* @return SQL Query
*/
@Override
public String createSQLQuery() throws SQLQueryObjectException{
return this.createSQLQuery(false);
}
private String createSQLQuery(boolean union) throws SQLQueryObjectException{
this.precheckBuildQuery();
StringBuilder bf = new StringBuilder();
bf.append("SELECT ");
// forzatura di indici
Iterator<String> itForceIndex = this.forceIndexTableNames.iterator();
while(itForceIndex.hasNext()){
bf.append(" "+itForceIndex.next()+" ");
}
if(this.isSelectDistinct())
bf.append(" DISTINCT ");
// select field
if(this.fields.isEmpty()){
bf.append("*");
}else{
Iterator<String> it = this.fields.iterator();
boolean first = true;
while(it.hasNext()){
if(!first)
bf.append(",");
else
first = false;
bf.append(it.next());
}
}
bf.append(getSQL(false,false,false,union));
/**if( this.offset>=0 || this.limit>=0)
// System.out.println("SQL ["+bf.toString()+"]");*/
return bf.toString();
}
/**
* Crea una SQL per una operazione di Delete con i dati dell'oggetto
*
* @return SQL per una operazione di Delete
*/
@Override
public String createSQLDeleteEngine() throws SQLQueryObjectException{
StringBuilder bf = new StringBuilder();
bf.append("DELETE ");
bf.append(getSQL(true,false,false,false));
return bf.toString();
}
/**
* @return SQL
* @throws SQLQueryObjectException
*/
private String getSQL(boolean delete,boolean update,boolean conditions,boolean union) throws SQLQueryObjectException {
StringBuilder bf = new StringBuilder();
if(this.selectForUpdate){
this.checkSelectForUpdate(update, delete, union);
}
if(!update && !conditions){
// From
bf.append(SQLQueryObjectCore.FROM_SEPARATOR);
// Table dove effettuare la ricerca 'FromTable'
if(this.tables.isEmpty()){
throw new SQLQueryObjectException(SQLQueryObjectCore.TABELLA_RICERCA_FROM_NON_DEFINITA);
}else{
if(delete && this.tables.size()>2)
throw new SQLQueryObjectException("Non e' possibile effettuare una delete con piu' di una tabella alla volta");
Iterator<String> it = this.tables.iterator();
boolean first = true;
while(it.hasNext()){
if(!first)
bf.append(",");
else
first = false;
bf.append(it.next());
}
}
}
// Condizioni di Where
if(!this.conditions.isEmpty()){
if(!conditions)
bf.append(" WHERE ");
if(this.notBeforeConditions){
bf.append("NOT (");
}
for(int i=0; i<this.conditions.size(); i++){
if(i>0){
if(this.andLogicOperator){
bf.append(SQLQueryObjectCore.AND_SEPARATOR);
}else{
bf.append(SQLQueryObjectCore.OR_SEPARATOR);
}
}
bf.append(this.conditions.get(i));
}
if(this.notBeforeConditions){
bf.append(")");
}
}
// Condizione GroupBy
if((!this.getGroupByConditions().isEmpty()) && (!delete) && (!update) && (!conditions)){
bf.append(SQLQueryObjectCore.GROUP_BY_SEPARATOR);
Iterator<String> it = this.getGroupByConditions().iterator();
boolean first = true;
while(it.hasNext()){
if(!first)
bf.append(",");
else
first = false;
bf.append(it.next());
}
}
// Condizione OrderBy
/** if(union==false){ La condizione di OrderBy DEVE essere generata. In SQLServer e MySQL viene generata durante la condizione di LIMIT/OFFSET*/
if((!this.orderBy.isEmpty()) && (!delete) && (!update) && (!conditions)){
bf.append(SQLQueryObjectCore.ORDER_BY_SEPARATOR);
Iterator<String> it = this.orderBy.iterator();
boolean first = true;
while(it.hasNext()){
String column = it.next();
if(!first)
bf.append(",");
else
first = false;
bf.append(column);
boolean sortTypeAsc = this.sortTypeAsc;
if(this.orderBySortType.containsKey(column)){
sortTypeAsc = this.orderBySortType.get(column);
}
if(sortTypeAsc){
bf.append(SQLQueryObjectCore.ASC_SEPARATOR);
}else{
bf.append(SQLQueryObjectCore.DESC_SEPARATOR);
}
}
}
/** }*/
// Limit e Offset
/**if(this.limit>0 || this.offset>0){*/
// Rilascio vincolo di order by in caso di limit impostato.
// Il vincolo rimane per l'offset, per gestire le select annidate di qualche implementazioni come Oracle,SQLServer ...
if(this.offset>=0 &&
(this.orderBy.isEmpty())
){
throw new SQLQueryObjectException(SQLQueryObjectCore.CONDIZIONI_ORDER_BY_RICHESTE);
}
// Limit
if((this.limit>0) && (!delete) && (!update) && (!conditions)){
bf.append(SQLQueryObjectCore.LIMIT_SEPARATOR);
bf.append(this.limit);
}
// Offset
if((this.offset>=0) && (!delete) && (!update) && (!conditions)){
if(this.limit<=0){
bf.append(SQLQueryObjectCore.LIMIT_SEPARATOR);
/**bf.append(ISQLQueryObject.LIMIT_DEFAULT_VALUE);*/
// Ci metto un valore MOLTO ALTO in modo da essere implementato come per gli altri database quasi come se non ci fosse il limit
bf.append(Integer.MAX_VALUE);
}
bf.append(" OFFSET ");
bf.append(this.offset);
}
// ForUpdate
if(!conditions &&
(this.selectForUpdate)
){
bf.append(" FOR UPDATE ");
}
return bf.toString();
}
/**
* Crea la stringa SQL per l'unione dei parametri presi in sqlQueryObject
* Il booleano 'unionAll' indica se i vari parametri devono essere uniti con 'UNION' o con 'UNION ALL'
*
* In caso nell'oggetto viene impostato il LIMIT o OFFSET, questo viene utilizzato per effettuare LIMIT e/o OFFSET dell'unione
*
* In caso l'ORDER by viene impostato nell'oggetto, questo viene utilizzato per effettuare ORDER BY dell'unione
*
*
* Sintassi MySQL:
*
* select * FROM (
* ( SELECT fields... FROM tabella WHERE conditions )
* UNION [ALL]
* ( SELECT fields... FROM tabella WHERE conditions )
* ) as subquery
*
* ORDER BY gdo,tipo_fruitore,fruitore,tipo_erogatore,erogatore,tipo_servizio,servizio DESC
* LIMIT 1000
* OFFSET 0;
*
*
* @param unionAll
* @param sqlQueryObject
* @return stringa SQL per l'unione dei parametri presi in sqlQueryObject
* @throws SQLQueryObjectException
*/
@Override
public String createSQLUnion(boolean unionAll,ISQLQueryObject... sqlQueryObject) throws SQLQueryObjectException{
// Controllo parametro su cui effettuare la UNION
this.checkUnionField(false,sqlQueryObject);
if(this.selectForUpdate){
this.checkSelectForUpdate(false, false, true);
}
StringBuilder bf = new StringBuilder();
bf.append("SELECT ");
// forzatura di indici
Iterator<String> itForceIndex = this.forceIndexTableNames.iterator();
while(itForceIndex.hasNext()){
bf.append(" "+itForceIndex.next()+" ");
}
// Non ha senso, la union fa gia la distinct, a meno di usare la unionAll ma in quel caso non si vuole la distinct
/** if(this.isSelectDistinct())
// bf.append(" DISTINCT ");*/
// select field
if(this.fields.isEmpty()){
bf.append("*");
}else{
Iterator<String> it = this.fields.iterator();
boolean first = true;
while(it.hasNext()){
if(!first)
bf.append(",");
else
first = false;
bf.append(it.next());
}
}
bf.append(SQLQueryObjectCore.FROM_SEPARATOR_APERTURA);
for(int i=0; i<sqlQueryObject.length; i++){
if(((MySQLQueryObject)sqlQueryObject[i]).selectForUpdate){
try{
((MySQLQueryObject)sqlQueryObject[i]).checkSelectForUpdate(false, false, true);
}catch(Exception e){
throw new SQLQueryObjectException("Parametro SqlQueryObject["+i+"] non valido: "+e.getMessage());
}
}
if(i>0){
bf.append(" UNION ");
if(unionAll){
bf.append(" ALL ");
}
}
bf.append("( ");
bf.append(((MySQLQueryObject)sqlQueryObject[i]).createSQLQuery(true));
bf.append(") ");
}
bf.append(SQLQueryObjectCore.AS_SUBQUERY_SUFFIX+getSerial()+" ");
// Condizione GroupBy
if((!this.getGroupByConditions().isEmpty())){
bf.append(SQLQueryObjectCore.GROUP_BY_SEPARATOR);
Iterator<String> it = this.getGroupByConditions().iterator();
boolean first = true;
while(it.hasNext()){
if(!first)
bf.append(",");
else
first = false;
bf.append(it.next());
}
}
// Condizione OrderBy
if(!this.orderBy.isEmpty()){
bf.append(SQLQueryObjectCore.ORDER_BY_SEPARATOR);
Iterator<String> it = this.orderBy.iterator();
boolean first = true;
while(it.hasNext()){
String column = it.next();
if(!first)
bf.append(",");
else
first = false;
bf.append(column);
boolean sortTypeAsc = this.sortTypeAsc;
if(this.orderBySortType.containsKey(column)){
sortTypeAsc = this.orderBySortType.get(column);
}
if(sortTypeAsc){
bf.append(SQLQueryObjectCore.ASC_SEPARATOR);
}else{
bf.append(SQLQueryObjectCore.DESC_SEPARATOR);
}
}
}
// Limit e Offset
/**if(this.limit>0 || this.offset>0){*/
// Rilascio vincolo di order by in caso di limit impostato.
// Il vincolo rimane per l'offset, per gestire le select annidate di qualche implementazioni come Oracle,SQLServer ...
if(this.offset>=0 &&
(this.orderBy.isEmpty())
){
throw new SQLQueryObjectException(SQLQueryObjectCore.CONDIZIONI_ORDER_BY_RICHESTE);
}
// Limit
if(this.limit>0){
bf.append(SQLQueryObjectCore.LIMIT_SEPARATOR);
bf.append(this.limit);
}
// Offset
if(this.offset>=0){
if(this.limit<=0){
bf.append(SQLQueryObjectCore.LIMIT_SEPARATOR);
/**bf.append(ISQLQueryObject.LIMIT_DEFAULT_VALUE);*/
// Ci metto un valore MOLTO ALTO in modo da essere implementato come per gli altri database quasi come se non ci fosse il limit
bf.append(Integer.MAX_VALUE);
}
bf.append(" OFFSET ");
bf.append(this.offset);
}
return bf.toString();
}
/**
* Crea la stringa SQL per l'unione dei parametri presi in sqlQueryObject (viene effettuato il count)
* Il booleano 'unionAll' indica se i vari parametri devono essere uniti con 'UNION' o con 'UNION ALL'
*
* Sintassi MySQL:
*
* select count(*) as aliasCount FROM (
*
* ( SELECT fields... FROM tabella WHERE conditions )
* UNION [ALL]
* ( SELECT fields... FROM tabella WHERE conditions )
*
* ) as subquery
*
* @param unionAll
* @param sqlQueryObject
* @return stringa SQL per l'unione dei parametri presi in sqlQueryObject (viene effettuato il count)
* @throws SQLQueryObjectException
*/
@Override
public String createSQLUnionCount(boolean unionAll,String aliasCount,ISQLQueryObject... sqlQueryObject) throws SQLQueryObjectException{
// Controllo parametro su cui effettuare la UNION
this.checkUnionField(true,sqlQueryObject);
if(aliasCount==null){
throw new SQLQueryObjectException("Alias per il count non definito");
}
StringBuilder bf = new StringBuilder();
bf.append("SELECT count(*) "+this.getDefaultAliasFieldKeyword()+" ");
bf.append(aliasCount);
bf.append(SQLQueryObjectCore.FROM_SEPARATOR_APERTURA);
bf.append( this.createSQLUnion(unionAll, sqlQueryObject) );
bf.append(SQLQueryObjectCore.AS_SUBQUERY_SUFFIX+getSerial()+" ");
return bf.toString();
}
/**
* Crea una SQL per una operazione di Update con i dati dell'oggetto
*
* @return SQL per una operazione di Update
*/
@Override
public String createSQLUpdateEngine() throws SQLQueryObjectException{
StringBuilder bf = new StringBuilder();
bf.append("UPDATE ");
bf.append(this.updateTable);
bf.append(" SET ");
for(int i=0; i<this.updateFieldsName.size(); i++){
if(i>0)
bf.append(" , ");
bf.append(this.updateFieldsName.get(i));
bf.append(" = ");
bf.append(this.updateFieldsValue.get(i));
}
bf.append(getSQL(false,true,false,false));
return bf.toString();
}
/* ---------------- WHERE CONDITIONS ------------------ */
/**
* Crea le condizioni presenti senza anteporre il comando (INSERT,CREATE,UPDATE), le tabelle interessate e il 'WHERE'
*
* @return SQL Query
*/
@Override
public String createSQLConditionsEngine() throws SQLQueryObjectException{
StringBuilder bf = new StringBuilder();
bf.append(getSQL(false,false,true,false));
return bf.toString();
}
}