Limit počtu řádků (10000) a doby provádění SQL dotazu (3 vteřiny) v pískovišti,
authorFrantišek Kučera <franta-hg@frantovo.cz>
Wed Feb 08 13:15:23 2012 +0100 (2012-02-08)
changeset 783b4abb1ec5a3
parent 77 fdeb54809e23
child 79 aa8c8f51b0cc
Limit počtu řádků (10000) a doby provádění SQL dotazu (3 vteřiny) v pískovišti,
nedokonalá ochrana proti DoS útoku (kartézský součin, náročný dotaz).
java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.java
java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.xml
     1.1 --- a/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.java	Wed Feb 08 12:44:51 2012 +0100
     1.2 +++ b/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.java	Wed Feb 08 13:15:23 2012 +0100
     1.3 @@ -14,118 +14,149 @@
     1.4  
     1.5  /**
     1.6   * Pro spouštění uživatelových příkazů.
     1.7 + *
     1.8   * @author fiki
     1.9   */
    1.10  public class PiskovisteDAO extends VyukaSuperDAO {
    1.11  
    1.12 -    private enum VLASTNOSTI {
    1.13 +	/** maximální doba trvání SQL dotazu – vteřiny */
    1.14 +	private static final int LIMIT_ČASU = 3;
    1.15 +	/** maximální počet řádků */
    1.16 +	private static final int LIMIT_POČTU = 10000;
    1.17  
    1.18 -        VYCHOZI_CESTA
    1.19 -    }
    1.20 -    TipyDAO tipy = new TipyDAO();
    1.21 -    HistorieDAO historie = new HistorieDAO();
    1.22 +	private enum VLASTNOSTI {
    1.23  
    1.24 -    public VysledekSQL vykonejSQL(String sql, Uzivatel uzivatel) {
    1.25 -        VysledekSQL v = new VysledekSQL();
    1.26 -        if (historie.ulozPrikaz(sql, uzivatel)) {
    1.27 +		VYCHOZI_CESTA,
    1.28 +		LIMIT_ČASU
    1.29 +	}
    1.30 +	TipyDAO tipy = new TipyDAO();
    1.31 +	HistorieDAO historie = new HistorieDAO();
    1.32  
    1.33 -            Connection db = getSpojeni(DATABAZE.PISKOVISTE);
    1.34 -            if (db == null) {
    1.35 -                v.getHlasky().add(new Hlaska("Došlo k chybě spojení.", Typ.Chyba));
    1.36 -            } else {
    1.37 -                PreparedStatement ps = null;
    1.38 -                ResultSet rs = null;
    1.39 -                try {
    1.40 -                    /**
    1.41 -                     * Uživatelskému SQL příkazu předřadíme výchozí cestu (search_path).
    1.42 -                     * Protože uživatelé si ji mohou měnit a kvůli recyklaci databázových zdrojů
    1.43 -                     * by jeden uživatel mohl ovlivnit jiného.
    1.44 -                     */
    1.45 -                    if (getVlastnost(VLASTNOSTI.VYCHOZI_CESTA) != null) {
    1.46 -                        sql = orizni(getVlastnost(VLASTNOSTI.VYCHOZI_CESTA)) + sql;
    1.47 -                    }
    1.48 +	public VysledekSQL vykonejSQL(String sql, Uzivatel uzivatel) {
    1.49 +		VysledekSQL v = new VysledekSQL();
    1.50 +		if (historie.ulozPrikaz(sql, uzivatel)) {
    1.51  
    1.52 -                    long casPred = System.currentTimeMillis();
    1.53 -                    ps = db.prepareStatement(sql);
    1.54 -                    boolean isRS = ps.execute();
    1.55 +			Connection db = getSpojeni(DATABAZE.PISKOVISTE);
    1.56 +			if (db == null) {
    1.57 +				v.getHlasky().add(new Hlaska("Došlo k chybě spojení.", Typ.Chyba));
    1.58 +			} else {
    1.59 +				PreparedStatement ps = null;
    1.60 +				ResultSet rs = null;
    1.61 +				try {
    1.62 +					/**
    1.63 +					 * Uživatelskému SQL příkazu předřadíme výchozí cestu (search_path).
    1.64 +					 * Protože uživatelé si ji mohou měnit a kvůli recyklaci databázových zdrojů
    1.65 +					 * by jeden uživatel mohl ovlivnit jiného.
    1.66 +					 */
    1.67 +					if (getVlastnost(VLASTNOSTI.VYCHOZI_CESTA) != null) {
    1.68 +						sql = orizni(getVlastnost(VLASTNOSTI.VYCHOZI_CESTA)) + sql;
    1.69 +					}
    1.70 +					
    1.71 +					/**
    1.72 +					 * TODO:
    1.73 +					 * použít ps.setQueryTimeout(LIMIT_ČASU);
    1.74 +					 * až ho bude podporovat JDBC ovladač,
    1.75 +					 * viz níže.
    1.76 +					 * Uživatel ale stejně může zadat:
    1.77 +					 * SET statement_timeout 0;
    1.78 +					 * do svého SQL dotazu.
    1.79 +					 */
    1.80 +					if (getVlastnost(VLASTNOSTI.LIMIT_ČASU) != null) {
    1.81 +						sql = orizni(getVlastnost(VLASTNOSTI.LIMIT_ČASU)) + sql;
    1.82 +					}
    1.83  
    1.84 -                    if (isRS) {
    1.85 -                        rs = ps.getResultSet();
    1.86 -                        v.getTabulky().add(zpracujVysledek(rs));
    1.87 -                    }
    1.88 +					long casPred = System.currentTimeMillis();
    1.89 +					ps = db.prepareStatement(sql);
    1.90 +					/**
    1.91 +					 * Limit času bohužel není podporován JDBC ovladačem.
    1.92 +					 * Alespoň ne v postgresql-9.1-901.jdbc4.jar
    1.93 +					 * http://jdbc.postgresql.org/todo.html
    1.94 +					 * 
    1.95 +					 * TODO:
    1.96 +					 * ps.setQueryTimeout(LIMIT_ČASU);
    1.97 +					 */
    1.98 +					ps.setMaxRows(LIMIT_POČTU);
    1.99 +					boolean isRS = ps.execute();
   1.100  
   1.101 -                    /**
   1.102 -                     * Ošetříme případ, kdy uživatel zadá SQL příkaz, který nevrací výsledkovou sadu.
   1.103 -                     * Typicky nastavení výchozího schématu: SET search_path = '…';
   1.104 -                     * Poznámka: jeden „SET search_path TO "…"“ se obvykle předřazuje uživatelskému SQL (viz PiskovisteDAO.xml).
   1.105 -                     */                    
   1.106 -                    while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
   1.107 -                        rs = ps.getResultSet();
   1.108 -                        if (rs == null) {
   1.109 -                            /** Jedná se o „update count“. */
   1.110 -                        } else {
   1.111 -                            v.getTabulky().add(zpracujVysledek(rs));
   1.112 -                        }
   1.113 -                    }
   1.114 -                    long dobaProvadeni = System.currentTimeMillis() - casPred;
   1.115 +					if (isRS) {
   1.116 +						rs = ps.getResultSet();
   1.117 +						v.getTabulky().add(zpracujVysledek(rs));
   1.118 +					}
   1.119  
   1.120 -                    /** Varování */
   1.121 -                    if (v.getHlasky().size() < 1 && v.getTabulky().size() < 1) {
   1.122 -                        v.getHlasky().add(new Hlaska("SQL příkaz proběhl, ale nevrátil žádná data.", Typ.Varovani));
   1.123 -                    }
   1.124 +					/**
   1.125 +					 * Ošetříme případ, kdy uživatel zadá SQL příkaz, který nevrací výsledkovou
   1.126 +					 * sadu.
   1.127 +					 * Typicky nastavení výchozího schématu: SET search_path = '…';
   1.128 +					 * Poznámka: jeden „SET search_path TO "…"“ se obvykle předřazuje uživatelskému
   1.129 +					 * SQL (viz PiskovisteDAO.xml).
   1.130 +					 */
   1.131 +					while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
   1.132 +						rs = ps.getResultSet();
   1.133 +						if (rs == null) {
   1.134 +							/** Jedná se o „update count“. */
   1.135 +						} else {
   1.136 +							v.getTabulky().add(zpracujVysledek(rs));
   1.137 +						}
   1.138 +					}
   1.139 +					long dobaProvadeni = System.currentTimeMillis() - casPred;
   1.140  
   1.141 -                    /** Varování */
   1.142 -                    int pocitadloTabulek = 1;
   1.143 -                    for (Tabulka t : v.getTabulky()) {
   1.144 -                        if (t.getHodnoty().size() < 1) {
   1.145 -                            v.getHlasky().add(new Hlaska("Tabulka " + pocitadloTabulek + "  je prázdná.", Typ.Varovani));
   1.146 -                        }
   1.147 -                        pocitadloTabulek++;
   1.148 -                    }
   1.149 +					/** Varování */
   1.150 +					if (v.getHlasky().size() < 1 && v.getTabulky().size() < 1) {
   1.151 +						v.getHlasky().add(new Hlaska("SQL příkaz proběhl, ale nevrátil žádná data.", Typ.Varovani));
   1.152 +					}
   1.153  
   1.154 -                    v.getHlasky().add(new Hlaska("SQL příkaz byl proveden úspěšně, během " + dobaProvadeni + " ms.", Typ.OK));
   1.155 +					/** Varování */
   1.156 +					int pocitadloTabulek = 1;
   1.157 +					for (Tabulka t : v.getTabulky()) {
   1.158 +						if (t.getHodnoty().size() < 1) {
   1.159 +							v.getHlasky().add(new Hlaska("Tabulka " + pocitadloTabulek + "  je prázdná.", Typ.Varovani));
   1.160 +						}
   1.161 +						pocitadloTabulek++;
   1.162 +					}
   1.163  
   1.164 -                } catch (SQLException e) {
   1.165 -                    log.log(Level.SEVERE, "SQL chyba při vykonávání uživatelského dotazu.", e);
   1.166 -                    v.getHlasky().add(new Hlaska("Chybné SQL: " + e.getMessage(), Typ.Chyba));
   1.167 -                } catch (Exception e) {
   1.168 -                    log.log(Level.SEVERE, "Chyba při vykonávání uživatelského dotazu.", e);
   1.169 -                    v.getHlasky().add(new Hlaska("Došlo k chybě dotazu.", Typ.Chyba));
   1.170 -                } finally {
   1.171 -                    zavri(db, ps, rs);
   1.172 -                }
   1.173 -            }
   1.174 +					v.getHlasky().add(new Hlaska("SQL příkaz byl proveden úspěšně, během " + dobaProvadeni + " ms.", Typ.OK));
   1.175  
   1.176 -            /** Tip pro uživatele */
   1.177 -            String tip = tipy.getTip();
   1.178 -            if (tip != null) {
   1.179 -                v.getHlasky().add(new Hlaska(tip, Typ.Tip, false));
   1.180 -            }
   1.181 +				} catch (SQLException e) {
   1.182 +					log.log(Level.SEVERE, "SQL chyba při vykonávání uživatelského dotazu.", e);
   1.183 +					v.getHlasky().add(new Hlaska("Chybné SQL: " + e.getMessage(), Typ.Chyba));
   1.184 +				} catch (Exception e) {
   1.185 +					log.log(Level.SEVERE, "Chyba při vykonávání uživatelského dotazu.", e);
   1.186 +					v.getHlasky().add(new Hlaska("Došlo k chybě dotazu.", Typ.Chyba));
   1.187 +				} finally {
   1.188 +					zavri(db, ps, rs);
   1.189 +				}
   1.190 +			}
   1.191  
   1.192 -        } else {
   1.193 -            v.getHlasky().add(new Hlaska("Došlo k chybě historie.", Typ.Chyba));
   1.194 -        }
   1.195 -        return v;
   1.196 -    }
   1.197 +			/** Tip pro uživatele */
   1.198 +			String tip = tipy.getTip();
   1.199 +			if (tip != null) {
   1.200 +				v.getHlasky().add(new Hlaska(tip, Typ.Tip, false));
   1.201 +			}
   1.202  
   1.203 -    private Tabulka zpracujVysledek(ResultSet rs) throws SQLException {
   1.204 -        Tabulka t = new Tabulka();
   1.205 +		} else {
   1.206 +			v.getHlasky().add(new Hlaska("Došlo k chybě historie.", Typ.Chyba));
   1.207 +		}
   1.208 +		return v;
   1.209 +	}
   1.210  
   1.211 -        int pocetSloupecku = rs.getMetaData().getColumnCount();
   1.212 -        String[] zahlavi = new String[pocetSloupecku];
   1.213 -        t.setZahlavi(zahlavi);
   1.214 -        for (int i = 0; i < pocetSloupecku; i++) {
   1.215 -            zahlavi[i] = rs.getMetaData().getColumnName(i + 1);
   1.216 -        }
   1.217 +	private Tabulka zpracujVysledek(ResultSet rs) throws SQLException {
   1.218 +		Tabulka t = new Tabulka();
   1.219  
   1.220 -        while (rs.next()) {
   1.221 -            Object[] hodnoty = new Object[pocetSloupecku];
   1.222 -            for (int i = 0; i < pocetSloupecku; i++) {
   1.223 -                hodnoty[i] = rs.getObject(i + 1);
   1.224 -            }
   1.225 -            t.getHodnoty().add(hodnoty);
   1.226 -        }
   1.227 +		int pocetSloupecku = rs.getMetaData().getColumnCount();
   1.228 +		String[] zahlavi = new String[pocetSloupecku];
   1.229 +		t.setZahlavi(zahlavi);
   1.230 +		for (int i = 0; i < pocetSloupecku; i++) {
   1.231 +			zahlavi[i] = rs.getMetaData().getColumnName(i + 1);
   1.232 +		}
   1.233  
   1.234 -        return t;
   1.235 -    }
   1.236 +		while (rs.next()) {
   1.237 +			Object[] hodnoty = new Object[pocetSloupecku];
   1.238 +			for (int i = 0; i < pocetSloupecku; i++) {
   1.239 +				hodnoty[i] = rs.getObject(i + 1);
   1.240 +			}
   1.241 +			t.getHodnoty().add(hodnoty);
   1.242 +		}
   1.243 +
   1.244 +		return t;
   1.245 +	}
   1.246  }
     2.1 --- a/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.xml	Wed Feb 08 12:44:51 2012 +0100
     2.2 +++ b/java/sql-vyuka/src/java/cz/frantovo/sql/vyuka/dao/PiskovisteDAO.xml	Wed Feb 08 13:15:23 2012 +0100
     2.3 @@ -2,12 +2,23 @@
     2.4  <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
     2.5  <properties>    
     2.6      <!--
     2.7 -    PostgreSQL proměnná „search_path“ – nastavíme ji před každým uživatelským SQL dotazem,
     2.8 -    aby se uživatelé vzájemně neovlivňovali.
     2.9 +		PostgreSQL proměnná „search_path“ – nastavíme ji před každým uživatelským SQL dotazem,
    2.10 +		aby se uživatelé vzájemně neovlivňovali.
    2.11      -->
    2.12 -    <entry key="VYCHOZI_CESTA">
    2.13 +	<entry key="VYCHOZI_CESTA">
    2.14          <![CDATA[
    2.15          SET search_path TO "$user",public;
    2.16          ]]>
    2.17 -    </entry>
    2.18 +	</entry>
    2.19 +	<!--
    2.20 +		Limit (vteřiny) pro vykonání jednoho SQL příkazu.
    2.21 +		TODO:
    2.22 +			použít ps.setQueryTimeout(LIMIT_ČASU);
    2.23 +			až ho bude podporovat JDBC ovladač.
    2.24 +	-->
    2.25 +	<entry key="LIMIT_ČASU">
    2.26 +        <![CDATA[
    2.27 +        SET statement_timeout TO 3;
    2.28 +        ]]>
    2.29 +	</entry>
    2.30  </properties>
    2.31 \ No newline at end of file