- Updated EasyMock to version 3.0 (SQLContainer requirement) svn changeset:20252/svn branch:6.7tags/6.7.0.beta1
@@ -0,0 +1,88 @@ | |||
package com.vaadin.data.util; | |||
import java.lang.ref.ReferenceQueue; | |||
import java.lang.ref.WeakReference; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import com.vaadin.data.util.query.FreeformQuery; | |||
import com.vaadin.data.util.query.QueryDelegate; | |||
import com.vaadin.data.util.query.TableQuery; | |||
/** | |||
* CacheFlushNotifier is a simple static notification mechanism to inform other | |||
* SQLContainers that the contents of their caches may have become stale. | |||
*/ | |||
class CacheFlushNotifier { | |||
/* | |||
* SQLContainer instance reference list and dead reference queue. Used for | |||
* the cache flush notification feature. | |||
*/ | |||
private static List<WeakReference<SQLContainer>> allInstances = new ArrayList<WeakReference<SQLContainer>>(); | |||
private static ReferenceQueue<SQLContainer> deadInstances = new ReferenceQueue<SQLContainer>(); | |||
/** | |||
* Adds the given SQLContainer to the cache flush notification receiver list | |||
* | |||
* @param c | |||
* Container to add | |||
*/ | |||
public static void addInstance(SQLContainer c) { | |||
removeDeadReferences(); | |||
if (c != null) { | |||
allInstances.add(new WeakReference<SQLContainer>(c, deadInstances)); | |||
} | |||
} | |||
/** | |||
* Removes dead references from instance list | |||
*/ | |||
private static void removeDeadReferences() { | |||
java.lang.ref.Reference<? extends SQLContainer> dead = deadInstances | |||
.poll(); | |||
while (dead != null) { | |||
allInstances.remove(dead); | |||
dead = deadInstances.poll(); | |||
} | |||
} | |||
/** | |||
* Iterates through the instances and notifies containers which are | |||
* connected to the same table or are using the same query string. | |||
* | |||
* @param c | |||
* SQLContainer that issued the cache flush notification | |||
*/ | |||
public static void notifyOfCacheFlush(SQLContainer c) { | |||
removeDeadReferences(); | |||
for (WeakReference<SQLContainer> wr : allInstances) { | |||
if (wr.get() != null) { | |||
SQLContainer wrc = wr.get(); | |||
if (wrc == null) { | |||
continue; | |||
} | |||
/* | |||
* If the reference points to the container sending the | |||
* notification, do nothing. | |||
*/ | |||
if (wrc.equals(c)) { | |||
continue; | |||
} | |||
/* Compare QueryDelegate types and tableName/queryString */ | |||
QueryDelegate wrQd = wrc.getQueryDelegate(); | |||
QueryDelegate qd = c.getQueryDelegate(); | |||
if (wrQd instanceof TableQuery | |||
&& qd instanceof TableQuery | |||
&& ((TableQuery) wrQd).getTableName().equals( | |||
((TableQuery) qd).getTableName())) { | |||
wrc.refresh(); | |||
} else if (wrQd instanceof FreeformQuery | |||
&& qd instanceof FreeformQuery | |||
&& ((FreeformQuery) wrQd).getQueryString().equals( | |||
((FreeformQuery) qd).getQueryString())) { | |||
wrc.refresh(); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
package com.vaadin.data.util; | |||
import java.util.LinkedHashMap; | |||
import java.util.Map; | |||
/** | |||
* CacheMap extends LinkedHashMap, adding the possibility to adjust maximum | |||
* number of items. In SQLContainer this is used for RowItem -cache. Cache size | |||
* will be two times the page length parameter of the container. | |||
*/ | |||
class CacheMap<K, V> extends LinkedHashMap<K, V> { | |||
private static final long serialVersionUID = 679999766473555231L; | |||
private int cacheLimit = SQLContainer.CACHE_RATIO | |||
* SQLContainer.DEFAULT_PAGE_LENGTH; | |||
@Override | |||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { | |||
return size() > cacheLimit; | |||
} | |||
void setCacheLimit(int limit) { | |||
cacheLimit = limit > 0 ? limit : SQLContainer.DEFAULT_PAGE_LENGTH; | |||
} | |||
int getCacheLimit() { | |||
return cacheLimit; | |||
} | |||
} |
@@ -0,0 +1,241 @@ | |||
package com.vaadin.data.util; | |||
import java.lang.reflect.Constructor; | |||
import java.sql.Date; | |||
import java.sql.Time; | |||
import java.sql.Timestamp; | |||
import com.vaadin.data.Property; | |||
/** | |||
* ColumnProperty represents the value of one column in a RowItem. In addition | |||
* to the value, ColumnProperty also contains some basic column attributes such | |||
* as nullability status, read-only status and data type. | |||
* | |||
* Note that depending on the QueryDelegate in use this does not necessarily map | |||
* into an actual column in a database table. | |||
*/ | |||
final public class ColumnProperty implements Property { | |||
private static final long serialVersionUID = -3694463129581802457L; | |||
private RowItem owner; | |||
private String propertyId; | |||
private boolean readOnly; | |||
private boolean allowReadOnlyChange = true; | |||
private boolean nullable = true; | |||
private Object value; | |||
private Object changedValue; | |||
private Class<?> type; | |||
private boolean modified; | |||
private boolean versionColumn; | |||
/** | |||
* Prevent instantiation without required parameters. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private ColumnProperty() { | |||
} | |||
public ColumnProperty(String propertyId, boolean readOnly, | |||
boolean allowReadOnlyChange, boolean nullable, Object value, | |||
Class<?> type) { | |||
if (propertyId == null) { | |||
throw new IllegalArgumentException("Properties must be named."); | |||
} | |||
if (type == null) { | |||
throw new IllegalArgumentException("Property type must be set."); | |||
} | |||
this.propertyId = propertyId; | |||
this.type = type; | |||
this.value = value; | |||
this.allowReadOnlyChange = allowReadOnlyChange; | |||
this.nullable = nullable; | |||
this.readOnly = readOnly; | |||
} | |||
public Object getValue() { | |||
if (isModified()) { | |||
return changedValue; | |||
} | |||
return value; | |||
} | |||
public void setValue(Object newValue) throws ReadOnlyException, | |||
ConversionException { | |||
if (newValue == null && !nullable) { | |||
throw new NotNullableException( | |||
"Null values are not allowed for this property."); | |||
} | |||
if (readOnly) { | |||
throw new ReadOnlyException( | |||
"Cannot set value for read-only property."); | |||
} | |||
/* Check if this property is a date property. */ | |||
boolean isDateProperty = Time.class.equals(getType()) | |||
|| Date.class.equals(getType()) | |||
|| Timestamp.class.equals(getType()); | |||
if (newValue != null) { | |||
/* Handle SQL dates, times and Timestamps given as java.util.Date */ | |||
if (isDateProperty) { | |||
/* | |||
* Try to get the millisecond value from the new value of this | |||
* property. Possible type to convert from is java.util.Date. | |||
*/ | |||
long millis = 0; | |||
if (newValue instanceof java.util.Date) { | |||
millis = ((java.util.Date) newValue).getTime(); | |||
/* | |||
* Create the new object based on the millisecond value, | |||
* according to the type of this property. | |||
*/ | |||
if (Time.class.equals(getType())) { | |||
newValue = new Time(millis); | |||
} else if (Date.class.equals(getType())) { | |||
newValue = new Date(millis); | |||
} else if (Timestamp.class.equals(getType())) { | |||
newValue = new Timestamp(millis); | |||
} | |||
} | |||
} | |||
/* | |||
* If the type is not correct, try to generate it through a possibly | |||
* existing String constructor. | |||
*/ | |||
if (!getType().isAssignableFrom(newValue.getClass())) { | |||
try { | |||
final Constructor<?> constr = getType().getConstructor( | |||
new Class[] { String.class }); | |||
newValue = constr.newInstance(new Object[] { newValue | |||
.toString() }); | |||
} catch (Exception e) { | |||
throw new ConversionException(e); | |||
} | |||
} | |||
/* | |||
* If the value to be set is the same that has already been set, do | |||
* not set it again. | |||
*/ | |||
if (newValue.equals(value)) { | |||
return; | |||
} | |||
} | |||
/* Set the new value and notify container of the change. */ | |||
changedValue = newValue; | |||
owner.getContainer().itemChangeNotification(owner); | |||
modified = true; | |||
} | |||
public Class<?> getType() { | |||
return type; | |||
} | |||
public boolean isReadOnly() { | |||
return readOnly; | |||
} | |||
public boolean isReadOnlyChangeAllowed() { | |||
return allowReadOnlyChange; | |||
} | |||
public void setReadOnly(boolean newStatus) { | |||
if (allowReadOnlyChange) { | |||
readOnly = newStatus; | |||
} | |||
} | |||
public String getPropertyId() { | |||
return propertyId; | |||
} | |||
@Override | |||
public String toString() { | |||
Object val = getValue(); | |||
if (val == null) { | |||
return null; | |||
} | |||
return val.toString(); | |||
} | |||
public void setOwner(RowItem owner) { | |||
if (owner == null) { | |||
throw new IllegalArgumentException("Owner can not be set to null."); | |||
} | |||
if (this.owner != null) { | |||
throw new IllegalStateException( | |||
"ColumnProperties can only be bound once."); | |||
} | |||
this.owner = owner; | |||
} | |||
public boolean isModified() { | |||
return modified; | |||
} | |||
public boolean isVersionColumn() { | |||
return versionColumn; | |||
} | |||
public void setVersionColumn(boolean versionColumn) { | |||
this.versionColumn = versionColumn; | |||
} | |||
public boolean isNullable() { | |||
return nullable; | |||
} | |||
/** | |||
* An exception that signals that a <code>null</code> value was passed to | |||
* the <code>setValue</code> method, but the value of this property can not | |||
* be set to <code>null</code>. | |||
*/ | |||
@SuppressWarnings("serial") | |||
public class NotNullableException extends RuntimeException { | |||
/** | |||
* Constructs a new <code>NotNullableException</code> without a detail | |||
* message. | |||
*/ | |||
public NotNullableException() { | |||
} | |||
/** | |||
* Constructs a new <code>NotNullableException</code> with the specified | |||
* detail message. | |||
* | |||
* @param msg | |||
* the detail message | |||
*/ | |||
public NotNullableException(String msg) { | |||
super(msg); | |||
} | |||
/** | |||
* Constructs a new <code>NotNullableException</code> from another | |||
* exception. | |||
* | |||
* @param cause | |||
* The cause of the failure | |||
*/ | |||
public NotNullableException(Throwable cause) { | |||
super(cause); | |||
} | |||
} | |||
public void commit() { | |||
if (isModified()) { | |||
modified = false; | |||
value = changedValue; | |||
} | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
package com.vaadin.data.util; | |||
/** | |||
* An OptimisticLockException is thrown when trying to update or delete a row | |||
* that has been changed since last read from the database. | |||
* | |||
* OptimisticLockException is a runtime exception because optimistic locking is | |||
* turned off by default, and as such will never be thrown in a default | |||
* configuration. In order to turn on optimistic locking, you need to specify | |||
* the version column in your TableQuery instance. | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.TableQuery#setVersionColumn(String) | |||
* | |||
* @author Jonatan Kronqvist / Vaadin Ltd | |||
*/ | |||
public class OptimisticLockException extends RuntimeException { | |||
private final RowId rowId; | |||
public OptimisticLockException(RowId rowId) { | |||
super(); | |||
this.rowId = rowId; | |||
} | |||
public OptimisticLockException(String msg, RowId rowId) { | |||
super(msg); | |||
this.rowId = rowId; | |||
} | |||
public RowId getRowId() { | |||
return rowId; | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
package com.vaadin.data.util; | |||
public class ReadOnlyRowId extends RowId { | |||
private static final long serialVersionUID = -2626764781642012467L; | |||
private final Integer rowNum; | |||
public ReadOnlyRowId(int rowNum) { | |||
super(); | |||
this.rowNum = rowNum; | |||
} | |||
@Override | |||
public int hashCode() { | |||
return rowNum.hashCode(); | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (obj == null || !(obj instanceof ReadOnlyRowId)) { | |||
return false; | |||
} | |||
return rowNum.equals(((ReadOnlyRowId) obj).rowNum); | |||
} | |||
public int getRowNum() { | |||
return rowNum; | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
package com.vaadin.data.util; | |||
import java.io.Serializable; | |||
/** | |||
* The reference class represents a simple [usually foreign key] reference to | |||
* another SQLContainer. Actual foreign key reference in the database is not | |||
* required, but it is recommended to make sure that certain constraints are | |||
* followed. | |||
*/ | |||
@SuppressWarnings("serial") | |||
class Reference implements Serializable { | |||
/** | |||
* The SQLContainer that this reference points to. | |||
*/ | |||
private SQLContainer referencedContainer; | |||
/** | |||
* The column ID/name in the referencing SQLContainer that contains the key | |||
* used for the reference. | |||
*/ | |||
private String referencingColumn; | |||
/** | |||
* The column ID/name in the referenced SQLContainer that contains the key | |||
* used for the reference. | |||
*/ | |||
private String referencedColumn; | |||
/** | |||
* Constructs a new reference to be used within the SQLContainer to | |||
* reference another SQLContainer. | |||
*/ | |||
Reference(SQLContainer referencedContainer, String referencingColumn, | |||
String referencedColumn) { | |||
this.referencedContainer = referencedContainer; | |||
this.referencingColumn = referencingColumn; | |||
this.referencedColumn = referencedColumn; | |||
} | |||
SQLContainer getReferencedContainer() { | |||
return referencedContainer; | |||
} | |||
String getReferencingColumn() { | |||
return referencingColumn; | |||
} | |||
String getReferencedColumn() { | |||
return referencedColumn; | |||
} | |||
} |
@@ -0,0 +1,78 @@ | |||
package com.vaadin.data.util; | |||
import java.io.Serializable; | |||
/** | |||
* RowId represents identifiers of a single database result set row. | |||
* | |||
* The data structure of a RowId is an Object array which contains the values of | |||
* the primary key columns of the identified row. This allows easy equals() | |||
* -comparison of RowItems. | |||
*/ | |||
public class RowId implements Serializable { | |||
private static final long serialVersionUID = -3161778404698901258L; | |||
protected Object[] id; | |||
/** | |||
* Prevent instantiation without required parameters. | |||
*/ | |||
protected RowId() { | |||
} | |||
public RowId(Object[] id) { | |||
if (id == null) { | |||
throw new IllegalArgumentException("id parameter must not be null!"); | |||
} | |||
this.id = id; | |||
} | |||
public Object[] getId() { | |||
return id; | |||
} | |||
@Override | |||
public int hashCode() { | |||
int result = 31; | |||
if (id != null) { | |||
for (Object o : id) { | |||
if (o != null) { | |||
result += o.hashCode(); | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (obj == null || !(obj instanceof RowId)) { | |||
return false; | |||
} | |||
Object[] compId = ((RowId) obj).getId(); | |||
if (id == null && compId == null) { | |||
return true; | |||
} | |||
if (id.length != compId.length) { | |||
return false; | |||
} | |||
for (int i = 0; i < id.length; i++) { | |||
if ((id[i] == null && compId[i] != null) | |||
|| (id[i] != null && !id[i].equals(compId[i]))) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
@Override | |||
public String toString() { | |||
StringBuffer s = new StringBuffer(); | |||
for (int i = 0; i < id.length; i++) { | |||
s.append(id[i]); | |||
if (i < id.length - 1) { | |||
s.append("/"); | |||
} | |||
} | |||
return s.toString(); | |||
} | |||
} |
@@ -0,0 +1,125 @@ | |||
package com.vaadin.data.util; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import com.vaadin.data.Item; | |||
import com.vaadin.data.Property; | |||
/** | |||
* RowItem represents one row of a result set obtained from a QueryDelegate. | |||
* | |||
* Note that depending on the QueryDelegate in use this does not necessarily map | |||
* into an actual row in a database table. | |||
*/ | |||
public final class RowItem implements Item { | |||
private static final long serialVersionUID = -6228966439127951408L; | |||
private SQLContainer container; | |||
private RowId id; | |||
private Collection<ColumnProperty> properties; | |||
/** | |||
* Prevent instantiation without required parameters. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private RowItem() { | |||
} | |||
public RowItem(SQLContainer container, RowId id, | |||
Collection<ColumnProperty> properties) { | |||
if (container == null) { | |||
throw new IllegalArgumentException("Container cannot be null."); | |||
} | |||
if (id == null) { | |||
throw new IllegalArgumentException("Row ID cannot be null."); | |||
} | |||
this.container = container; | |||
this.properties = properties; | |||
/* Set this RowItem as owner to the properties */ | |||
if (properties != null) { | |||
for (ColumnProperty p : properties) { | |||
p.setOwner(this); | |||
} | |||
} | |||
this.id = id; | |||
} | |||
public Property getItemProperty(Object id) { | |||
if (id instanceof String && id != null) { | |||
for (ColumnProperty cp : properties) { | |||
if (id.equals(cp.getPropertyId())) { | |||
return cp; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
public Collection<?> getItemPropertyIds() { | |||
Collection<String> ids = new ArrayList<String>(properties.size()); | |||
for (ColumnProperty cp : properties) { | |||
ids.add(cp.getPropertyId()); | |||
} | |||
return Collections.unmodifiableCollection(ids); | |||
} | |||
/** | |||
* Adding properties is not supported. Properties are generated by | |||
* SQLContainer. | |||
*/ | |||
public boolean addItemProperty(Object id, Property property) | |||
throws UnsupportedOperationException { | |||
throw new UnsupportedOperationException(); | |||
} | |||
/** | |||
* Removing properties is not supported. Properties are generated by | |||
* SQLContainer. | |||
*/ | |||
public boolean removeItemProperty(Object id) | |||
throws UnsupportedOperationException { | |||
throw new UnsupportedOperationException(); | |||
} | |||
public RowId getId() { | |||
return id; | |||
} | |||
public SQLContainer getContainer() { | |||
return container; | |||
} | |||
public boolean isModified() { | |||
if (properties != null) { | |||
for (ColumnProperty p : properties) { | |||
if (p.isModified()) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
@Override | |||
public String toString() { | |||
StringBuffer s = new StringBuffer(); | |||
s.append("ID:"); | |||
s.append(getId().toString()); | |||
for (Object propId : getItemPropertyIds()) { | |||
s.append("|"); | |||
s.append(propId.toString()); | |||
s.append(":"); | |||
s.append(getItemProperty(propId).toString()); | |||
} | |||
return s.toString(); | |||
} | |||
public void commit() { | |||
if (properties != null) { | |||
for (ColumnProperty p : properties) { | |||
p.commit(); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
package com.vaadin.data.util; | |||
public class SQLUtil { | |||
/** | |||
* Escapes different special characters in strings that are passed to SQL. | |||
* Replaces the following: | |||
* | |||
* <list> <li>' is replaced with ''</li> <li>\x00 is removed</li> <li>\ is | |||
* replaced with \\</li> <li>" is replaced with \"</li> <li> | |||
* \x1a is removed</li> </list> | |||
* | |||
* Also note! The escaping done here may or may not be enough to prevent any | |||
* and all SQL injections so it is recommended to check user input before | |||
* giving it to the SQLContainer/TableQuery. | |||
* | |||
* @param constant | |||
* @return \\\'\' | |||
*/ | |||
public static String escapeSQL(String constant) { | |||
if (constant == null) { | |||
return null; | |||
} | |||
String fixedConstant = constant; | |||
fixedConstant = fixedConstant.replaceAll("\\\\x00", ""); | |||
fixedConstant = fixedConstant.replaceAll("\\\\x1a", ""); | |||
fixedConstant = fixedConstant.replaceAll("'", "''"); | |||
fixedConstant = fixedConstant.replaceAll("\\\\", "\\\\\\\\"); | |||
fixedConstant = fixedConstant.replaceAll("\\\"", "\\\\\""); | |||
return fixedConstant; | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
package com.vaadin.data.util; | |||
public class TemporaryRowId extends RowId { | |||
private static final long serialVersionUID = -641983830469018329L; | |||
public TemporaryRowId(Object[] id) { | |||
super(id); | |||
} | |||
@Override | |||
public int hashCode() { | |||
return id.hashCode(); | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (obj == null || !(obj instanceof TemporaryRowId)) { | |||
return false; | |||
} | |||
Object[] compId = ((TemporaryRowId) obj).getId(); | |||
return id.equals(compId); | |||
} | |||
@Override | |||
public String toString() { | |||
return "Temporary row id"; | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
package com.vaadin.data.util.connection; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
import javax.naming.InitialContext; | |||
import javax.naming.NamingException; | |||
import javax.sql.DataSource; | |||
public class J2EEConnectionPool implements JDBCConnectionPool { | |||
private String dataSourceJndiName; | |||
private DataSource dataSource = null; | |||
public J2EEConnectionPool(DataSource dataSource) { | |||
this.dataSource = dataSource; | |||
} | |||
public J2EEConnectionPool(String dataSourceJndiName) { | |||
this.dataSourceJndiName = dataSourceJndiName; | |||
} | |||
public Connection reserveConnection() throws SQLException { | |||
Connection conn = getDataSource().getConnection(); | |||
conn.setAutoCommit(false); | |||
return conn; | |||
} | |||
private DataSource getDataSource() throws SQLException { | |||
if (dataSource == null) { | |||
dataSource = lookupDataSource(); | |||
} | |||
return dataSource; | |||
} | |||
private DataSource lookupDataSource() throws SQLException { | |||
try { | |||
InitialContext ic = new InitialContext(); | |||
return (DataSource) ic.lookup(dataSourceJndiName); | |||
} catch (NamingException e) { | |||
throw new SQLException( | |||
"NamingException - Cannot connect to the database. Cause: " | |||
+ e.getMessage()); | |||
} | |||
} | |||
public void releaseConnection(Connection conn) { | |||
try { | |||
conn.close(); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
public void destroy() { | |||
dataSource = null; | |||
} | |||
} |
@@ -0,0 +1,38 @@ | |||
package com.vaadin.data.util.connection; | |||
import java.io.Serializable; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
/** | |||
* Interface for implementing connection pools to be used with SQLContainer. | |||
*/ | |||
public interface JDBCConnectionPool extends Serializable { | |||
/** | |||
* Retrieves a connection. | |||
* | |||
* @return a usable connection to the database | |||
* @throws SQLException | |||
*/ | |||
public Connection reserveConnection() throws SQLException; | |||
/** | |||
* Releases a connection that was retrieved earlier. | |||
* | |||
* Note that depending on implementation, the transaction possibly open in | |||
* the connection may or may not be rolled back. | |||
* | |||
* @param conn | |||
* Connection to be released | |||
*/ | |||
public void releaseConnection(Connection conn); | |||
/** | |||
* Destroys the connection pool: close() is called an all the connections in | |||
* the pool, whether available or reserved. | |||
* | |||
* This method was added to fix PostgreSQL -related issues with connections | |||
* that were left hanging 'idle'. | |||
*/ | |||
public void destroy(); | |||
} |
@@ -0,0 +1,162 @@ | |||
package com.vaadin.data.util.connection; | |||
import java.io.IOException; | |||
import java.sql.Connection; | |||
import java.sql.DriverManager; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
/** | |||
* Simple implementation of the JDBCConnectionPool interface. Handles loading | |||
* the JDBC driver, setting up the connections and ensuring they are still | |||
* usable upon release. | |||
*/ | |||
@SuppressWarnings("serial") | |||
public class SimpleJDBCConnectionPool implements JDBCConnectionPool { | |||
private int initialConnections = 5; | |||
private int maxConnections = 20; | |||
private String driverName; | |||
private String connectionUri; | |||
private String userName; | |||
private String password; | |||
private transient Set<Connection> availableConnections; | |||
private transient Set<Connection> reservedConnections; | |||
private boolean initialized; | |||
public SimpleJDBCConnectionPool(String driverName, String connectionUri, | |||
String userName, String password) throws SQLException { | |||
if (driverName == null) { | |||
throw new IllegalArgumentException( | |||
"JDBC driver class name must be given."); | |||
} | |||
if (connectionUri == null) { | |||
throw new IllegalArgumentException( | |||
"Database connection URI must be given."); | |||
} | |||
if (userName == null) { | |||
throw new IllegalArgumentException( | |||
"Database username must be given."); | |||
} | |||
if (password == null) { | |||
throw new IllegalArgumentException( | |||
"Database password must be given."); | |||
} | |||
this.driverName = driverName; | |||
this.connectionUri = connectionUri; | |||
this.userName = userName; | |||
this.password = password; | |||
/* Initialize JDBC driver */ | |||
try { | |||
Class.forName(driverName).newInstance(); | |||
} catch (Exception ex) { | |||
throw new RuntimeException("Specified JDBC Driver: " + driverName | |||
+ " - initialization failed.", ex); | |||
} | |||
} | |||
public SimpleJDBCConnectionPool(String driverName, String connectionUri, | |||
String userName, String password, int initialConnections, | |||
int maxConnections) throws SQLException { | |||
this(driverName, connectionUri, userName, password); | |||
this.initialConnections = initialConnections; | |||
this.maxConnections = maxConnections; | |||
} | |||
private void initializeConnections() throws SQLException { | |||
availableConnections = new HashSet<Connection>(initialConnections); | |||
reservedConnections = new HashSet<Connection>(initialConnections); | |||
for (int i = 0; i < initialConnections; i++) { | |||
availableConnections.add(createConnection()); | |||
} | |||
initialized = true; | |||
} | |||
public synchronized Connection reserveConnection() throws SQLException { | |||
if (!initialized) { | |||
initializeConnections(); | |||
} | |||
if (availableConnections.isEmpty()) { | |||
if (reservedConnections.size() < maxConnections) { | |||
availableConnections.add(createConnection()); | |||
} else { | |||
throw new SQLException("Connection limit has been reached."); | |||
} | |||
} | |||
Connection c = availableConnections.iterator().next(); | |||
availableConnections.remove(c); | |||
reservedConnections.add(c); | |||
return c; | |||
} | |||
public synchronized void releaseConnection(Connection conn) { | |||
if (conn == null || !initialized) { | |||
return; | |||
} | |||
/* Try to roll back if necessary */ | |||
try { | |||
if (!conn.getAutoCommit()) { | |||
conn.rollback(); | |||
} | |||
} catch (SQLException e) { | |||
/* Roll back failed, close and discard connection */ | |||
try { | |||
conn.close(); | |||
} catch (SQLException e1) { | |||
/* Nothing needs to be done */ | |||
} | |||
reservedConnections.remove(conn); | |||
return; | |||
} | |||
reservedConnections.remove(conn); | |||
availableConnections.add(conn); | |||
} | |||
private Connection createConnection() throws SQLException { | |||
Connection c = DriverManager.getConnection(connectionUri, userName, | |||
password); | |||
c.setAutoCommit(false); | |||
if (driverName.toLowerCase().contains("mysql")) { | |||
try { | |||
Statement s = c.createStatement(); | |||
s.execute("SET SESSION sql_mode = 'ANSI'"); | |||
s.close(); | |||
} catch (Exception e) { | |||
// Failed to set ansi mode; continue | |||
} | |||
} | |||
return c; | |||
} | |||
public void destroy() { | |||
for (Connection c : availableConnections) { | |||
try { | |||
c.close(); | |||
} catch (SQLException e) { | |||
// No need to do anything | |||
} | |||
} | |||
for (Connection c : reservedConnections) { | |||
try { | |||
c.close(); | |||
} catch (SQLException e) { | |||
// No need to do anything | |||
} | |||
} | |||
} | |||
private void writeObject(java.io.ObjectOutputStream out) throws IOException { | |||
initialized = false; | |||
out.defaultWriteObject(); | |||
} | |||
} |
@@ -0,0 +1,69 @@ | |||
package com.vaadin.data.util.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.Item; | |||
public class Between implements Filter { | |||
private final Object propertyId; | |||
private final Comparable startValue; | |||
private final Comparable endValue; | |||
public Between(Object propertyId, Comparable startValue, Comparable endValue) { | |||
this.propertyId = propertyId; | |||
this.startValue = startValue; | |||
this.endValue = endValue; | |||
} | |||
public Object getPropertyId() { | |||
return propertyId; | |||
} | |||
public Comparable<?> getStartValue() { | |||
return startValue; | |||
} | |||
public Comparable<?> getEndValue() { | |||
return endValue; | |||
} | |||
public boolean passesFilter(Object itemId, Item item) | |||
throws UnsupportedOperationException { | |||
Object value = item.getItemProperty(getPropertyId()).getValue(); | |||
if (value instanceof Comparable) { | |||
Comparable cval = (Comparable) value; | |||
return cval.compareTo(getStartValue()) >= 0 | |||
&& cval.compareTo(getEndValue()) <= 0; | |||
} | |||
return false; | |||
} | |||
public boolean appliesToProperty(Object propertyId) { | |||
return getPropertyId() != null && getPropertyId().equals(propertyId); | |||
} | |||
@Override | |||
public int hashCode() { | |||
return getPropertyId().hashCode() + getStartValue().hashCode() | |||
+ getEndValue().hashCode(); | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
// Only objects of the same class can be equal | |||
if (!getClass().equals(obj.getClass())) { | |||
return false; | |||
} | |||
final Between o = (Between) obj; | |||
// Checks the properties one by one | |||
boolean propertyIdEqual = (null != getPropertyId()) ? getPropertyId() | |||
.equals(o.getPropertyId()) : null == o.getPropertyId(); | |||
boolean startValueEqual = (null != getStartValue()) ? getStartValue() | |||
.equals(o.getStartValue()) : null == o.getStartValue(); | |||
boolean endValueEqual = (null != getEndValue()) ? getEndValue().equals( | |||
o.getEndValue()) : null == o.getEndValue(); | |||
return propertyIdEqual && startValueEqual && endValueEqual; | |||
} | |||
} |
@@ -0,0 +1,78 @@ | |||
package com.vaadin.data.util.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.Item; | |||
public class Like implements Filter { | |||
private final Object propertyId; | |||
private final String value; | |||
private boolean caseSensitive; | |||
public Like(String propertyId, String value) { | |||
this(propertyId, value, true); | |||
} | |||
public Like(String propertyId, String value, boolean caseSensitive) { | |||
this.propertyId = propertyId; | |||
this.value = value; | |||
setCaseSensitive(caseSensitive); | |||
} | |||
public Object getPropertyId() { | |||
return propertyId; | |||
} | |||
public String getValue() { | |||
return value; | |||
} | |||
public void setCaseSensitive(boolean caseSensitive) { | |||
this.caseSensitive = caseSensitive; | |||
} | |||
public boolean isCaseSensitive() { | |||
return caseSensitive; | |||
} | |||
public boolean passesFilter(Object itemId, Item item) | |||
throws UnsupportedOperationException { | |||
if (!item.getItemProperty(getPropertyId()).getType() | |||
.isAssignableFrom(String.class)) { | |||
// We can only handle strings | |||
return false; | |||
} | |||
String colValue = (String) item.getItemProperty(getPropertyId()) | |||
.getValue(); | |||
String pattern = getValue().replace("%", ".*"); | |||
if (isCaseSensitive()) { | |||
return colValue.matches(pattern); | |||
} | |||
return colValue.toUpperCase().matches(pattern.toUpperCase()); | |||
} | |||
public boolean appliesToProperty(Object propertyId) { | |||
return getPropertyId() != null && getPropertyId().equals(propertyId); | |||
} | |||
@Override | |||
public int hashCode() { | |||
return getPropertyId().hashCode() + getValue().hashCode(); | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
// Only objects of the same class can be equal | |||
if (!getClass().equals(obj.getClass())) { | |||
return false; | |||
} | |||
final Like o = (Like) obj; | |||
// Checks the properties one by one | |||
boolean propertyIdEqual = (null != getPropertyId()) ? getPropertyId() | |||
.equals(o.getPropertyId()) : null == o.getPropertyId(); | |||
boolean valueEqual = (null != getValue()) ? getValue().equals( | |||
o.getValue()) : null == o.getValue(); | |||
return propertyIdEqual && valueEqual; | |||
} | |||
} |
@@ -0,0 +1,450 @@ | |||
package com.vaadin.data.util.query; | |||
import java.io.IOException; | |||
import java.sql.Connection; | |||
import java.sql.PreparedStatement; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.SQLContainer; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
import com.vaadin.data.util.query.generator.filter.QueryBuilder; | |||
@SuppressWarnings("serial") | |||
public class FreeformQuery implements QueryDelegate { | |||
FreeformQueryDelegate delegate = null; | |||
private String queryString; | |||
private List<String> primaryKeyColumns; | |||
private JDBCConnectionPool connectionPool; | |||
private transient Connection activeConnection = null; | |||
/** | |||
* Prevent no-parameters instantiation of FreeformQuery | |||
*/ | |||
@SuppressWarnings("unused") | |||
private FreeformQuery() { | |||
} | |||
/** | |||
* Creates a new freeform query delegate to be used with the | |||
* {@link SQLContainer}. | |||
* | |||
* @param queryString | |||
* The actual query to perform. | |||
* @param primaryKeyColumns | |||
* The primary key columns. Read-only mode is forced if this | |||
* parameter is null or empty. | |||
* @param connectionPool | |||
* the JDBCConnectionPool to use to open connections to the SQL | |||
* database. | |||
* @deprecated @see | |||
* {@link FreeformQuery#FreeformQuery(String, JDBCConnectionPool, String...)} | |||
*/ | |||
@Deprecated | |||
public FreeformQuery(String queryString, List<String> primaryKeyColumns, | |||
JDBCConnectionPool connectionPool) { | |||
if (primaryKeyColumns == null) { | |||
primaryKeyColumns = new ArrayList<String>(); | |||
} | |||
if (primaryKeyColumns.contains("")) { | |||
throw new IllegalArgumentException( | |||
"The primary key columns contain an empty string!"); | |||
} else if (queryString == null || "".equals(queryString)) { | |||
throw new IllegalArgumentException( | |||
"The query string may not be empty or null!"); | |||
} else if (connectionPool == null) { | |||
throw new IllegalArgumentException( | |||
"The connectionPool may not be null!"); | |||
} | |||
this.queryString = queryString; | |||
this.primaryKeyColumns = Collections | |||
.unmodifiableList(primaryKeyColumns); | |||
this.connectionPool = connectionPool; | |||
} | |||
/** | |||
* Creates a new freeform query delegate to be used with the | |||
* {@link SQLContainer}. | |||
* | |||
* @param queryString | |||
* The actual query to perform. | |||
* @param connectionPool | |||
* the JDBCConnectionPool to use to open connections to the SQL | |||
* database. | |||
* @param primaryKeyColumns | |||
* The primary key columns. Read-only mode is forced if none are | |||
* provided. (optional) | |||
*/ | |||
public FreeformQuery(String queryString, JDBCConnectionPool connectionPool, | |||
String... primaryKeyColumns) { | |||
this(queryString, Arrays.asList(primaryKeyColumns), connectionPool); | |||
} | |||
/** | |||
* This implementation of getCount() actually fetches all records from the | |||
* database, which might be a performance issue. Override this method with a | |||
* SELECT COUNT(*) ... query if this is too slow for your needs. | |||
* | |||
* {@inheritDoc} | |||
*/ | |||
public int getCount() throws SQLException { | |||
// First try the delegate | |||
int count = countByDelegate(); | |||
if (count < 0) { | |||
// Couldn't use the delegate, use the bad way. | |||
Connection conn = getConnection(); | |||
Statement statement = conn.createStatement( | |||
ResultSet.TYPE_SCROLL_INSENSITIVE, | |||
ResultSet.CONCUR_READ_ONLY); | |||
ResultSet rs = statement.executeQuery(queryString); | |||
if (rs.last()) { | |||
count = rs.getRow(); | |||
} else { | |||
count = 0; | |||
} | |||
rs.close(); | |||
statement.close(); | |||
releaseConnection(conn); | |||
} | |||
return count; | |||
} | |||
@SuppressWarnings("deprecation") | |||
private int countByDelegate() throws SQLException { | |||
int count = -1; | |||
if (delegate == null) { | |||
return count; | |||
} | |||
/* First try using prepared statement */ | |||
if (delegate instanceof FreeformStatementDelegate) { | |||
try { | |||
StatementHelper sh = ((FreeformStatementDelegate) delegate) | |||
.getCountStatement(); | |||
Connection c = getConnection(); | |||
PreparedStatement pstmt = c.prepareStatement(sh | |||
.getQueryString()); | |||
sh.setParameterValuesToStatement(pstmt); | |||
ResultSet rs = pstmt.executeQuery(); | |||
rs.next(); | |||
count = rs.getInt(1); | |||
rs.close(); | |||
pstmt.clearParameters(); | |||
pstmt.close(); | |||
releaseConnection(c); | |||
return count; | |||
} catch (UnsupportedOperationException e) { | |||
// Count statement generation not supported | |||
} | |||
} | |||
/* Try using regular statement */ | |||
try { | |||
String countQuery = delegate.getCountQuery(); | |||
if (countQuery != null) { | |||
Connection conn = getConnection(); | |||
Statement statement = conn.createStatement(); | |||
ResultSet rs = statement.executeQuery(countQuery); | |||
rs.next(); | |||
count = rs.getInt(1); | |||
rs.close(); | |||
statement.close(); | |||
releaseConnection(conn); | |||
return count; | |||
} | |||
} catch (UnsupportedOperationException e) { | |||
// Count query generation not supported | |||
} | |||
return count; | |||
} | |||
private Connection getConnection() throws SQLException { | |||
if (activeConnection != null) { | |||
return activeConnection; | |||
} | |||
return connectionPool.reserveConnection(); | |||
} | |||
/** | |||
* Fetches the results for the query. This implementation always fetches the | |||
* entire record set, ignoring the offset and pagelength parameters. In | |||
* order to support lazy loading of records, you must supply a | |||
* FreeformQueryDelegate that implements the | |||
* FreeformQueryDelegate.getQueryString(int,int) method. | |||
* | |||
* @throws SQLException | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.FreeformQueryDelegate#getQueryString(int, | |||
* int) {@inheritDoc} | |||
*/ | |||
@SuppressWarnings("deprecation") | |||
public ResultSet getResults(int offset, int pagelength) throws SQLException { | |||
if (activeConnection == null) { | |||
throw new SQLException("No active transaction!"); | |||
} | |||
String query = queryString; | |||
if (delegate != null) { | |||
/* First try using prepared statement */ | |||
if (delegate instanceof FreeformStatementDelegate) { | |||
try { | |||
StatementHelper sh = ((FreeformStatementDelegate) delegate) | |||
.getQueryStatement(offset, pagelength); | |||
PreparedStatement pstmt = activeConnection | |||
.prepareStatement(sh.getQueryString()); | |||
sh.setParameterValuesToStatement(pstmt); | |||
return pstmt.executeQuery(); | |||
} catch (UnsupportedOperationException e) { | |||
// Statement generation not supported, continue... | |||
} | |||
} | |||
try { | |||
query = delegate.getQueryString(offset, pagelength); | |||
} catch (UnsupportedOperationException e) { | |||
// This is fine, we'll just use the default queryString. | |||
} | |||
} | |||
Statement statement = activeConnection.createStatement(); | |||
ResultSet rs = statement.executeQuery(query); | |||
return rs; | |||
} | |||
@SuppressWarnings("deprecation") | |||
public boolean implementationRespectsPagingLimits() { | |||
if (delegate == null) { | |||
return false; | |||
} | |||
/* First try using prepared statement */ | |||
if (delegate instanceof FreeformStatementDelegate) { | |||
try { | |||
StatementHelper sh = ((FreeformStatementDelegate) delegate) | |||
.getCountStatement(); | |||
if (sh != null && sh.getQueryString() != null | |||
&& sh.getQueryString().length() > 0) { | |||
return true; | |||
} | |||
} catch (UnsupportedOperationException e) { | |||
// Statement generation not supported, continue... | |||
} | |||
} | |||
try { | |||
String queryString = delegate.getQueryString(0, 50); | |||
return queryString != null && queryString.length() > 0; | |||
} catch (UnsupportedOperationException e) { | |||
return false; | |||
} | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.vaadin.addon.sqlcontainer.query.QueryDelegate#setFilters(java.util | |||
* .List) | |||
*/ | |||
public void setFilters(List<Filter> filters) | |||
throws UnsupportedOperationException { | |||
if (delegate != null) { | |||
delegate.setFilters(filters); | |||
} else if (filters != null) { | |||
throw new UnsupportedOperationException( | |||
"FreeFormQueryDelegate not set!"); | |||
} | |||
} | |||
public void setOrderBy(List<OrderBy> orderBys) | |||
throws UnsupportedOperationException { | |||
if (delegate != null) { | |||
delegate.setOrderBy(orderBys); | |||
} else if (orderBys != null) { | |||
throw new UnsupportedOperationException( | |||
"FreeFormQueryDelegate not set!"); | |||
} | |||
} | |||
public int storeRow(RowItem row) throws SQLException { | |||
if (activeConnection == null) { | |||
throw new IllegalStateException("No transaction is active!"); | |||
} else if (primaryKeyColumns.isEmpty()) { | |||
throw new UnsupportedOperationException( | |||
"Cannot store items fetched with a read-only freeform query!"); | |||
} | |||
if (delegate != null) { | |||
return delegate.storeRow(activeConnection, row); | |||
} else { | |||
throw new UnsupportedOperationException( | |||
"FreeFormQueryDelegate not set!"); | |||
} | |||
} | |||
public boolean removeRow(RowItem row) throws SQLException { | |||
if (activeConnection == null) { | |||
throw new IllegalStateException("No transaction is active!"); | |||
} else if (primaryKeyColumns.isEmpty()) { | |||
throw new UnsupportedOperationException( | |||
"Cannot remove items fetched with a read-only freeform query!"); | |||
} | |||
if (delegate != null) { | |||
return delegate.removeRow(activeConnection, row); | |||
} else { | |||
throw new UnsupportedOperationException( | |||
"FreeFormQueryDelegate not set!"); | |||
} | |||
} | |||
public synchronized void beginTransaction() | |||
throws UnsupportedOperationException, SQLException { | |||
if (activeConnection != null) { | |||
throw new IllegalStateException("A transaction is already active!"); | |||
} | |||
activeConnection = connectionPool.reserveConnection(); | |||
activeConnection.setAutoCommit(false); | |||
} | |||
public synchronized void commit() throws UnsupportedOperationException, | |||
SQLException { | |||
if (activeConnection == null) { | |||
throw new SQLException("No active transaction"); | |||
} | |||
if (!activeConnection.getAutoCommit()) { | |||
activeConnection.commit(); | |||
} | |||
connectionPool.releaseConnection(activeConnection); | |||
activeConnection = null; | |||
} | |||
public synchronized void rollback() throws UnsupportedOperationException, | |||
SQLException { | |||
if (activeConnection == null) { | |||
throw new SQLException("No active transaction"); | |||
} | |||
activeConnection.rollback(); | |||
connectionPool.releaseConnection(activeConnection); | |||
activeConnection = null; | |||
} | |||
public List<String> getPrimaryKeyColumns() { | |||
return primaryKeyColumns; | |||
} | |||
public String getQueryString() { | |||
return queryString; | |||
} | |||
public FreeformQueryDelegate getDelegate() { | |||
return delegate; | |||
} | |||
public void setDelegate(FreeformQueryDelegate delegate) { | |||
this.delegate = delegate; | |||
} | |||
/** | |||
* This implementation of the containsRowWithKey method rewrites existing | |||
* WHERE clauses in the query string. The logic is, however, not very | |||
* complex and some times can do the Wrong Thing<sup>TM</sup>. For the | |||
* situations where this logic is not enough, you can implement the | |||
* getContainsRowQueryString method in FreeformQueryDelegate and this will | |||
* be used instead of the logic. | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.FreeformQueryDelegate#getContainsRowQueryString(Object...) | |||
* | |||
* {@inheritDoc} | |||
*/ | |||
@SuppressWarnings("deprecation") | |||
public boolean containsRowWithKey(Object... keys) throws SQLException { | |||
String query = null; | |||
boolean contains = false; | |||
if (delegate != null) { | |||
if (delegate instanceof FreeformStatementDelegate) { | |||
try { | |||
StatementHelper sh = ((FreeformStatementDelegate) delegate) | |||
.getContainsRowQueryStatement(keys); | |||
Connection c = getConnection(); | |||
PreparedStatement pstmt = c.prepareStatement(sh | |||
.getQueryString()); | |||
sh.setParameterValuesToStatement(pstmt); | |||
ResultSet rs = pstmt.executeQuery(); | |||
contains = rs.next(); | |||
rs.close(); | |||
pstmt.clearParameters(); | |||
pstmt.close(); | |||
releaseConnection(c); | |||
return contains; | |||
} catch (UnsupportedOperationException e) { | |||
// Statement generation not supported, continue... | |||
} | |||
} | |||
try { | |||
query = delegate.getContainsRowQueryString(keys); | |||
} catch (UnsupportedOperationException e) { | |||
query = modifyWhereClause(keys); | |||
} | |||
} else { | |||
query = modifyWhereClause(keys); | |||
} | |||
Connection conn = getConnection(); | |||
try { | |||
Statement statement = conn.createStatement(); | |||
ResultSet rs = statement.executeQuery(query); | |||
contains = rs.next(); | |||
rs.close(); | |||
statement.close(); | |||
} finally { | |||
releaseConnection(conn); | |||
} | |||
return contains; | |||
} | |||
/** | |||
* Releases the connection if it is not part of an active transaction. | |||
* | |||
* @param conn | |||
* the connection to release | |||
*/ | |||
private void releaseConnection(Connection conn) { | |||
if (conn != activeConnection) { | |||
connectionPool.releaseConnection(conn); | |||
} | |||
} | |||
private String modifyWhereClause(Object... keys) { | |||
// Build the where rules for the provided keys | |||
StringBuffer where = new StringBuffer(); | |||
for (int ix = 0; ix < primaryKeyColumns.size(); ix++) { | |||
where.append(QueryBuilder.quote(primaryKeyColumns.get(ix))); | |||
if (keys[ix] == null) { | |||
where.append(" IS NULL"); | |||
} else { | |||
where.append(" = '").append(keys[ix]).append("'"); | |||
} | |||
if (ix < primaryKeyColumns.size() - 1) { | |||
where.append(" AND "); | |||
} | |||
} | |||
// Is there already a WHERE clause in the query string? | |||
int index = queryString.toLowerCase().indexOf("where "); | |||
if (index > -1) { | |||
// Rewrite the where clause | |||
return queryString.substring(0, index) + "WHERE " + where + " AND " | |||
+ queryString.substring(index + 6); | |||
} | |||
// Append a where clause | |||
return queryString + " WHERE " + where; | |||
} | |||
private void writeObject(java.io.ObjectOutputStream out) throws IOException { | |||
try { | |||
rollback(); | |||
} catch (SQLException ignored) { | |||
} | |||
out.defaultWriteObject(); | |||
} | |||
} |
@@ -0,0 +1,115 @@ | |||
package com.vaadin.data.util.query; | |||
import java.io.Serializable; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.RowItem; | |||
public interface FreeformQueryDelegate extends Serializable { | |||
/** | |||
* Should return the SQL query string to be performed. This method is | |||
* responsible for gluing together the select query from the filters and the | |||
* order by conditions if these are supported. | |||
* | |||
* @param offset | |||
* the first record (row) to fetch. | |||
* @param pagelength | |||
* the number of records (rows) to fetch. 0 means all records | |||
* starting from offset. | |||
* @deprecated Implement {@link FreeformStatementDelegate} instead of | |||
* {@link FreeformQueryDelegate} | |||
*/ | |||
@Deprecated | |||
public String getQueryString(int offset, int limit) | |||
throws UnsupportedOperationException; | |||
/** | |||
* Generates and executes a query to determine the current row count from | |||
* the DB. Row count will be fetched using filters that are currently set to | |||
* the QueryDelegate. | |||
* | |||
* @return row count | |||
* @throws SQLException | |||
* @deprecated Implement {@link FreeformStatementDelegate} instead of | |||
* {@link FreeformQueryDelegate} | |||
*/ | |||
@Deprecated | |||
public String getCountQuery() throws UnsupportedOperationException; | |||
/** | |||
* Sets the filters to apply when performing the SQL query. These are | |||
* translated into a WHERE clause. Default filtering mode will be used. | |||
* | |||
* @param filters | |||
* The filters to apply. | |||
* @throws UnsupportedOperationException | |||
* if the implementation doesn't support filtering. | |||
*/ | |||
public void setFilters(List<Filter> filters) | |||
throws UnsupportedOperationException; | |||
/** | |||
* Sets the order in which to retrieve rows from the database. The result | |||
* can be ordered by zero or more columns and each column can be in | |||
* ascending or descending order. These are translated into an ORDER BY | |||
* clause in the SQL query. | |||
* | |||
* @param orderBys | |||
* A list of the OrderBy conditions. | |||
* @throws UnsupportedOperationException | |||
* if the implementation doesn't support ordering. | |||
*/ | |||
public void setOrderBy(List<OrderBy> orderBys) | |||
throws UnsupportedOperationException; | |||
/** | |||
* Stores a row in the database. The implementation of this interface | |||
* decides how to identify whether to store a new row or update an existing | |||
* one. | |||
* | |||
* @param conn | |||
* the JDBC connection to use | |||
* @param row | |||
* RowItem to be stored or updated. | |||
* @throws UnsupportedOperationException | |||
* if the implementation is read only. | |||
* @throws SQLException | |||
*/ | |||
public int storeRow(Connection conn, RowItem row) | |||
throws UnsupportedOperationException, SQLException; | |||
/** | |||
* Removes the given RowItem from the database. | |||
* | |||
* @param conn | |||
* the JDBC connection to use | |||
* @param row | |||
* RowItem to be removed | |||
* @return true on success | |||
* @throws UnsupportedOperationException | |||
* @throws SQLException | |||
*/ | |||
public boolean removeRow(Connection conn, RowItem row) | |||
throws UnsupportedOperationException, SQLException; | |||
/** | |||
* Generates an SQL Query string that allows the user of the FreeformQuery | |||
* class to customize the query string used by the | |||
* FreeformQuery.containsRowWithKeys() method. This is useful for cases when | |||
* the logic in the containsRowWithKeys method is not enough to support more | |||
* complex free form queries. | |||
* | |||
* @param keys | |||
* the values of the primary keys | |||
* @throws UnsupportedOperationException | |||
* to use the default logic in FreeformQuery | |||
* @deprecated Implement {@link FreeformStatementDelegate} instead of | |||
* {@link FreeformQueryDelegate} | |||
*/ | |||
@Deprecated | |||
public String getContainsRowQueryString(Object... keys) | |||
throws UnsupportedOperationException; | |||
} |
@@ -0,0 +1,54 @@ | |||
package com.vaadin.data.util.query; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
/** | |||
* FreeformStatementDelegate is an extension to FreeformQueryDelegate that | |||
* provides definitions for methods that produce StatementHelper objects instead | |||
* of basic query strings. This allows the FreeformQuery query delegate to use | |||
* PreparedStatements instead of regular Statement when accessing the database. | |||
* | |||
* Due to the injection protection and other benefits of prepared statements, it | |||
* is advisable to implement this interface instead of the FreeformQueryDelegate | |||
* whenever possible. | |||
*/ | |||
public interface FreeformStatementDelegate extends FreeformQueryDelegate { | |||
/** | |||
* Should return a new instance of StatementHelper that contains the query | |||
* string and parameter values required to create a PreparedStatement. This | |||
* method is responsible for gluing together the select query from the | |||
* filters and the order by conditions if these are supported. | |||
* | |||
* @param offset | |||
* the first record (row) to fetch. | |||
* @param pagelength | |||
* the number of records (rows) to fetch. 0 means all records | |||
* starting from offset. | |||
*/ | |||
public StatementHelper getQueryStatement(int offset, int limit) | |||
throws UnsupportedOperationException; | |||
/** | |||
* Should return a new instance of StatementHelper that contains the query | |||
* string and parameter values required to create a PreparedStatement that | |||
* will fetch the row count from the DB. Row count should be fetched using | |||
* filters that are currently set to the QueryDelegate. | |||
*/ | |||
public StatementHelper getCountStatement() | |||
throws UnsupportedOperationException; | |||
/** | |||
* Should return a new instance of StatementHelper that contains the query | |||
* string and parameter values required to create a PreparedStatement used | |||
* by the FreeformQuery.containsRowWithKeys() method. This is useful for | |||
* cases when the default logic in said method is not enough to support more | |||
* complex free form queries. | |||
* | |||
* @param keys | |||
* the values of the primary keys | |||
* @throws UnsupportedOperationException | |||
* to use the default logic in FreeformQuery | |||
*/ | |||
public StatementHelper getContainsRowQueryStatement(Object... keys) | |||
throws UnsupportedOperationException; | |||
} |
@@ -0,0 +1,43 @@ | |||
package com.vaadin.data.util.query; | |||
import java.io.Serializable; | |||
/** | |||
* OrderBy represents a sorting rule to be applied to a query made by the | |||
* SQLContainer's QueryDelegate. | |||
* | |||
* The sorting rule is simple and contains only the affected column's name and | |||
* the direction of the sort. | |||
*/ | |||
public class OrderBy implements Serializable { | |||
private String column; | |||
private boolean isAscending; | |||
/** | |||
* Prevent instantiation without required parameters. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private OrderBy() { | |||
} | |||
public OrderBy(String column, boolean isAscending) { | |||
setColumn(column); | |||
setAscending(isAscending); | |||
} | |||
public void setColumn(String column) { | |||
this.column = column; | |||
} | |||
public String getColumn() { | |||
return column; | |||
} | |||
public void setAscending(boolean isAscending) { | |||
this.isAscending = isAscending; | |||
} | |||
public boolean isAscending() { | |||
return isAscending; | |||
} | |||
} |
@@ -0,0 +1,208 @@ | |||
package com.vaadin.data.util.query; | |||
import java.io.Serializable; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.RowId; | |||
import com.vaadin.data.util.RowItem; | |||
public interface QueryDelegate extends Serializable { | |||
/** | |||
* Generates and executes a query to determine the current row count from | |||
* the DB. Row count will be fetched using filters that are currently set to | |||
* the QueryDelegate. | |||
* | |||
* @return row count | |||
* @throws SQLException | |||
*/ | |||
public int getCount() throws SQLException; | |||
/** | |||
* Executes a paged SQL query and returns the ResultSet. The query is | |||
* defined through implementations of this QueryDelegate interface. | |||
* | |||
* @param offset | |||
* the first item of the page to load | |||
* @param pagelength | |||
* the length of the page to load | |||
* @return a ResultSet containing the rows of the page | |||
* @throws SQLException | |||
* if the database access fails. | |||
*/ | |||
public ResultSet getResults(int offset, int pagelength) throws SQLException; | |||
/** | |||
* Allows the SQLContainer implementation to check whether the QueryDelegate | |||
* implementation implements paging in the getResults method. | |||
* | |||
* @see QueryDelegate#getResults(int, int) | |||
* | |||
* @return true if the delegate implements paging | |||
*/ | |||
public boolean implementationRespectsPagingLimits(); | |||
/** | |||
* Sets the filters to apply when performing the SQL query. These are | |||
* translated into a WHERE clause. Default filtering mode will be used. | |||
* | |||
* @param filters | |||
* The filters to apply. | |||
* @throws UnsupportedOperationException | |||
* if the implementation doesn't support filtering. | |||
*/ | |||
public void setFilters(List<Filter> filters) | |||
throws UnsupportedOperationException; | |||
/** | |||
* Sets the order in which to retrieve rows from the database. The result | |||
* can be ordered by zero or more columns and each column can be in | |||
* ascending or descending order. These are translated into an ORDER BY | |||
* clause in the SQL query. | |||
* | |||
* @param orderBys | |||
* A list of the OrderBy conditions. | |||
* @throws UnsupportedOperationException | |||
* if the implementation doesn't support ordering. | |||
*/ | |||
public void setOrderBy(List<OrderBy> orderBys) | |||
throws UnsupportedOperationException; | |||
/** | |||
* Stores a row in the database. The implementation of this interface | |||
* decides how to identify whether to store a new row or update an existing | |||
* one. | |||
* | |||
* @param columnToValueMap | |||
* A map containing the values for all columns to be stored or | |||
* updated. | |||
* @return the number of affected rows in the database table | |||
* @throws UnsupportedOperationException | |||
* if the implementation is read only. | |||
*/ | |||
public int storeRow(RowItem row) throws UnsupportedOperationException, | |||
SQLException; | |||
/** | |||
* Removes the given RowItem from the database. | |||
* | |||
* @param row | |||
* RowItem to be removed | |||
* @return true on success | |||
* @throws UnsupportedOperationException | |||
* @throws SQLException | |||
*/ | |||
public boolean removeRow(RowItem row) throws UnsupportedOperationException, | |||
SQLException; | |||
/** | |||
* Starts a new database transaction. Used when storing multiple changes. | |||
* | |||
* Note that if a transaction is already open, it will be rolled back when a | |||
* new transaction is started. | |||
* | |||
* @throws SQLException | |||
* if the database access fails. | |||
*/ | |||
public void beginTransaction() throws SQLException; | |||
/** | |||
* Commits a transaction. If a transaction is not open nothing should | |||
* happen. | |||
* | |||
* @throws SQLException | |||
* if the database access fails. | |||
*/ | |||
public void commit() throws SQLException; | |||
/** | |||
* Rolls a transaction back. If a transaction is not open nothing should | |||
* happen. | |||
* | |||
* @throws SQLException | |||
* if the database access fails. | |||
*/ | |||
public void rollback() throws SQLException; | |||
/** | |||
* Returns a list of primary key column names. The list is either fetched | |||
* from the database (TableQuery) or given as an argument depending on | |||
* implementation. | |||
* | |||
* @return | |||
*/ | |||
public List<String> getPrimaryKeyColumns(); | |||
/** | |||
* Performs a query to find out whether the SQL table contains a row with | |||
* the given set of primary keys. | |||
* | |||
* @param keys | |||
* the primary keys | |||
* @return true if the SQL table contains a row with the provided keys | |||
* @throws SQLException | |||
*/ | |||
public boolean containsRowWithKey(Object... keys) throws SQLException; | |||
/************************/ | |||
/** ROWID CHANGE EVENT **/ | |||
/************************/ | |||
/** | |||
* An <code>Event</code> object specifying the old and new RowId of an added | |||
* item after the addition has been successfully committed. | |||
*/ | |||
public interface RowIdChangeEvent extends Serializable { | |||
/** | |||
* Gets the old (temporary) RowId of the added row that raised this | |||
* event. | |||
* | |||
* @return old RowId | |||
*/ | |||
public RowId getOldRowId(); | |||
/** | |||
* Gets the new, possibly database assigned RowId of the added row that | |||
* raised this event. | |||
* | |||
* @return new RowId | |||
*/ | |||
public RowId getNewRowId(); | |||
} | |||
/** RowId change listener interface. */ | |||
public interface RowIdChangeListener extends Serializable { | |||
/** | |||
* Lets the listener know that a RowId has been changed. | |||
* | |||
* @param event | |||
*/ | |||
public void rowIdChange(QueryDelegate.RowIdChangeEvent event); | |||
} | |||
/** | |||
* The interface for adding and removing <code>RowIdChangeEvent</code> | |||
* listeners. By implementing this interface a class explicitly announces | |||
* that it will generate a <code>RowIdChangeEvent</code> when it performs a | |||
* database commit that may change the RowId. | |||
*/ | |||
public interface RowIdChangeNotifier extends Serializable { | |||
/** | |||
* Adds a RowIdChangeListener for the object. | |||
* | |||
* @param listener | |||
* listener to be added | |||
*/ | |||
public void addListener(QueryDelegate.RowIdChangeListener listener); | |||
/** | |||
* Removes the specified RowIdChangeListener from the object. | |||
* | |||
* @param listener | |||
* listener to be removed | |||
*/ | |||
public void removeListener(QueryDelegate.RowIdChangeListener listener); | |||
} | |||
} |
@@ -0,0 +1,707 @@ | |||
package com.vaadin.data.util.query; | |||
import java.io.IOException; | |||
import java.sql.Connection; | |||
import java.sql.DatabaseMetaData; | |||
import java.sql.PreparedStatement; | |||
import java.sql.ResultSet; | |||
import java.sql.ResultSetMetaData; | |||
import java.sql.SQLException; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.EventObject; | |||
import java.util.HashMap; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.ColumnProperty; | |||
import com.vaadin.data.util.OptimisticLockException; | |||
import com.vaadin.data.util.RowId; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.SQLUtil; | |||
import com.vaadin.data.util.TemporaryRowId; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.filter.Compare.Equal; | |||
import com.vaadin.data.util.query.generator.DefaultSQLGenerator; | |||
import com.vaadin.data.util.query.generator.MSSQLGenerator; | |||
import com.vaadin.data.util.query.generator.SQLGenerator; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
@SuppressWarnings("serial") | |||
public class TableQuery implements QueryDelegate, | |||
QueryDelegate.RowIdChangeNotifier { | |||
/** Table name, primary key column name(s) and version column name */ | |||
private String tableName; | |||
private List<String> primaryKeyColumns; | |||
private String versionColumn; | |||
/** Currently set Filters and OrderBys */ | |||
private List<Filter> filters; | |||
private List<OrderBy> orderBys; | |||
/** SQLGenerator instance to use for generating queries */ | |||
private SQLGenerator sqlGenerator; | |||
/** Fields related to Connection and Transaction handling */ | |||
private JDBCConnectionPool connectionPool; | |||
private transient Connection activeConnection; | |||
private boolean transactionOpen; | |||
/** Row ID change listeners */ | |||
private LinkedList<RowIdChangeListener> rowIdChangeListeners; | |||
/** Row ID change events, stored until commit() is called */ | |||
private final List<RowIdChangeEvent> bufferedEvents = new ArrayList<RowIdChangeEvent>(); | |||
/** Set to true to output generated SQL Queries to System.out */ | |||
private boolean debug = false; | |||
/** Prevent no-parameters instantiation of TableQuery */ | |||
@SuppressWarnings("unused") | |||
private TableQuery() { | |||
} | |||
/** | |||
* Creates a new TableQuery using the given connection pool, SQL generator | |||
* and table name to fetch the data from. All parameters must be non-null. | |||
* | |||
* @param tableName | |||
* Name of the database table to connect to | |||
* @param connectionPool | |||
* Connection pool for accessing the database | |||
* @param sqlGenerator | |||
* SQL query generator implementation | |||
*/ | |||
public TableQuery(String tableName, JDBCConnectionPool connectionPool, | |||
SQLGenerator sqlGenerator) { | |||
if (tableName == null || tableName.trim().length() < 1 | |||
|| connectionPool == null || sqlGenerator == null) { | |||
throw new IllegalArgumentException( | |||
"All parameters must be non-null and a table name must be given."); | |||
} | |||
this.tableName = tableName; | |||
this.sqlGenerator = sqlGenerator; | |||
this.connectionPool = connectionPool; | |||
fetchMetaData(); | |||
} | |||
/** | |||
* Creates a new TableQuery using the given connection pool and table name | |||
* to fetch the data from. All parameters must be non-null. The default SQL | |||
* generator will be used for queries. | |||
* | |||
* @param tableName | |||
* Name of the database table to connect to | |||
* @param connectionPool | |||
* Connection pool for accessing the database | |||
*/ | |||
public TableQuery(String tableName, JDBCConnectionPool connectionPool) { | |||
this(tableName, connectionPool, new DefaultSQLGenerator()); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#getCount() | |||
*/ | |||
public int getCount() throws SQLException { | |||
debug("Fetching count..."); | |||
StatementHelper sh = sqlGenerator.generateSelectQuery(tableName, | |||
filters, null, 0, 0, "COUNT(*)"); | |||
boolean shouldCloseTransaction = false; | |||
if (!transactionOpen) { | |||
shouldCloseTransaction = true; | |||
beginTransaction(); | |||
} | |||
ResultSet r = executeQuery(sh); | |||
r.next(); | |||
int count = r.getInt(1); | |||
r.getStatement().close(); | |||
r.close(); | |||
if (shouldCloseTransaction) { | |||
commit(); | |||
} | |||
return count; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#getResults(int, | |||
* int) | |||
*/ | |||
public ResultSet getResults(int offset, int pagelength) throws SQLException { | |||
StatementHelper sh; | |||
/* | |||
* If no ordering is explicitly set, results will be ordered by the | |||
* first primary key column. | |||
*/ | |||
if (orderBys == null || orderBys.isEmpty()) { | |||
List<OrderBy> ob = new ArrayList<OrderBy>(); | |||
ob.add(new OrderBy(primaryKeyColumns.get(0), true)); | |||
sh = sqlGenerator.generateSelectQuery(tableName, filters, ob, | |||
offset, pagelength, null); | |||
} else { | |||
sh = sqlGenerator.generateSelectQuery(tableName, filters, orderBys, | |||
offset, pagelength, null); | |||
} | |||
return executeQuery(sh); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.QueryDelegate# | |||
* implementationRespectsPagingLimits() | |||
*/ | |||
public boolean implementationRespectsPagingLimits() { | |||
return true; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.vaadin.addon.sqlcontainer.query.QueryDelegate#storeRow(com.vaadin | |||
* .addon.sqlcontainer.RowItem) | |||
*/ | |||
public int storeRow(RowItem row) throws UnsupportedOperationException, | |||
SQLException { | |||
if (row == null) { | |||
throw new IllegalArgumentException("Row argument must be non-null."); | |||
} | |||
StatementHelper sh; | |||
int result = 0; | |||
if (row.getId() instanceof TemporaryRowId) { | |||
setVersionColumnFlagInProperty(row); | |||
sh = sqlGenerator.generateInsertQuery(tableName, row); | |||
result = executeUpdateReturnKeys(sh, row); | |||
} else { | |||
setVersionColumnFlagInProperty(row); | |||
sh = sqlGenerator.generateUpdateQuery(tableName, row); | |||
result = executeUpdate(sh); | |||
} | |||
if (versionColumn != null && result == 0) { | |||
throw new OptimisticLockException( | |||
"Someone else changed the row that was being updated.", | |||
row.getId()); | |||
} | |||
return result; | |||
} | |||
private void setVersionColumnFlagInProperty(RowItem row) { | |||
ColumnProperty versionProperty = (ColumnProperty) row | |||
.getItemProperty(versionColumn); | |||
if (versionProperty != null) { | |||
versionProperty.setVersionColumn(true); | |||
} | |||
} | |||
/** | |||
* Inserts the given row in the database table immediately. Begins and | |||
* commits the transaction needed. This method was added specifically to | |||
* solve the problem of returning the final RowId immediately on the | |||
* SQLContainer.addItem() call when auto commit mode is enabled in the | |||
* SQLContainer. | |||
* | |||
* @param row | |||
* RowItem to add to the database | |||
* @return Final RowId of the added row | |||
* @throws SQLException | |||
*/ | |||
public RowId storeRowImmediately(RowItem row) throws SQLException { | |||
beginTransaction(); | |||
/* Set version column, if one is provided */ | |||
setVersionColumnFlagInProperty(row); | |||
/* Generate query */ | |||
StatementHelper sh = sqlGenerator.generateInsertQuery(tableName, row); | |||
PreparedStatement pstmt = activeConnection.prepareStatement( | |||
sh.getQueryString(), primaryKeyColumns.toArray(new String[0])); | |||
sh.setParameterValuesToStatement(pstmt); | |||
debug("DB -> " + sh.getQueryString()); | |||
int result = pstmt.executeUpdate(); | |||
if (result > 0) { | |||
/* | |||
* If affected rows exist, we'll get the new RowId, commit the | |||
* transaction and return the new RowId. | |||
*/ | |||
ResultSet generatedKeys = pstmt.getGeneratedKeys(); | |||
RowId newId = getNewRowId(row, generatedKeys); | |||
generatedKeys.close(); | |||
pstmt.clearParameters(); | |||
pstmt.close(); | |||
commit(); | |||
return newId; | |||
} else { | |||
pstmt.clearParameters(); | |||
pstmt.close(); | |||
/* On failure return null */ | |||
return null; | |||
} | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.vaadin.addon.sqlcontainer.query.QueryDelegate#setFilters(java.util | |||
* .List) | |||
*/ | |||
public void setFilters(List<Filter> filters) | |||
throws UnsupportedOperationException { | |||
if (filters == null) { | |||
this.filters = null; | |||
return; | |||
} | |||
this.filters = Collections.unmodifiableList(filters); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.vaadin.addon.sqlcontainer.query.QueryDelegate#setOrderBy(java.util | |||
* .List) | |||
*/ | |||
public void setOrderBy(List<OrderBy> orderBys) | |||
throws UnsupportedOperationException { | |||
if (orderBys == null) { | |||
this.orderBys = null; | |||
return; | |||
} | |||
this.orderBys = Collections.unmodifiableList(orderBys); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#beginTransaction() | |||
*/ | |||
public void beginTransaction() throws UnsupportedOperationException, | |||
SQLException { | |||
if (transactionOpen && activeConnection != null) { | |||
throw new IllegalStateException(); | |||
} | |||
debug("DB -> begin transaction"); | |||
activeConnection = connectionPool.reserveConnection(); | |||
activeConnection.setAutoCommit(false); | |||
transactionOpen = true; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#commit() | |||
*/ | |||
public void commit() throws UnsupportedOperationException, SQLException { | |||
if (transactionOpen && activeConnection != null) { | |||
debug("DB -> commit"); | |||
activeConnection.commit(); | |||
connectionPool.releaseConnection(activeConnection); | |||
} else { | |||
throw new SQLException("No active transaction"); | |||
} | |||
transactionOpen = false; | |||
/* Handle firing row ID change events */ | |||
RowIdChangeEvent[] unFiredEvents = bufferedEvents | |||
.toArray(new RowIdChangeEvent[] {}); | |||
bufferedEvents.clear(); | |||
if (rowIdChangeListeners != null && !rowIdChangeListeners.isEmpty()) { | |||
for (RowIdChangeListener r : rowIdChangeListeners) { | |||
for (RowIdChangeEvent e : unFiredEvents) { | |||
r.rowIdChange(e); | |||
} | |||
} | |||
} | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#rollback() | |||
*/ | |||
public void rollback() throws UnsupportedOperationException, SQLException { | |||
if (transactionOpen && activeConnection != null) { | |||
debug("DB -> rollback"); | |||
activeConnection.rollback(); | |||
connectionPool.releaseConnection(activeConnection); | |||
} else { | |||
throw new SQLException("No active transaction"); | |||
} | |||
transactionOpen = false; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.vaadin.addon.sqlcontainer.query.QueryDelegate#getPrimaryKeyColumns() | |||
*/ | |||
public List<String> getPrimaryKeyColumns() { | |||
return Collections.unmodifiableList(primaryKeyColumns); | |||
} | |||
public String getVersionColumn() { | |||
return versionColumn; | |||
} | |||
public void setVersionColumn(String column) { | |||
versionColumn = column; | |||
} | |||
public String getTableName() { | |||
return tableName; | |||
} | |||
public SQLGenerator getSqlGenerator() { | |||
return sqlGenerator; | |||
} | |||
/** | |||
* Executes the given query string using either the active connection if a | |||
* transaction is already open, or a new connection from this query's | |||
* connection pool. | |||
* | |||
* @param sh | |||
* an instance of StatementHelper, containing the query string | |||
* and parameter values. | |||
* @return ResultSet of the query | |||
* @throws SQLException | |||
*/ | |||
private ResultSet executeQuery(StatementHelper sh) throws SQLException { | |||
Connection c = null; | |||
if (transactionOpen && activeConnection != null) { | |||
c = activeConnection; | |||
} else { | |||
throw new SQLException("No active transaction!"); | |||
} | |||
PreparedStatement pstmt = c.prepareStatement(sh.getQueryString()); | |||
sh.setParameterValuesToStatement(pstmt); | |||
debug("DB -> " + sh.getQueryString()); | |||
return pstmt.executeQuery(); | |||
} | |||
/** | |||
* Executes the given update query string using either the active connection | |||
* if a transaction is already open, or a new connection from this query's | |||
* connection pool. | |||
* | |||
* @param sh | |||
* an instance of StatementHelper, containing the query string | |||
* and parameter values. | |||
* @return Number of affected rows | |||
* @throws SQLException | |||
*/ | |||
private int executeUpdate(StatementHelper sh) throws SQLException { | |||
Connection c = null; | |||
PreparedStatement pstmt = null; | |||
try { | |||
if (transactionOpen && activeConnection != null) { | |||
c = activeConnection; | |||
} else { | |||
c = connectionPool.reserveConnection(); | |||
} | |||
pstmt = c.prepareStatement(sh.getQueryString()); | |||
sh.setParameterValuesToStatement(pstmt); | |||
debug("DB -> " + sh.getQueryString()); | |||
int retval = pstmt.executeUpdate(); | |||
return retval; | |||
} finally { | |||
if (pstmt != null) { | |||
pstmt.clearParameters(); | |||
pstmt.close(); | |||
} | |||
if (!transactionOpen) { | |||
connectionPool.releaseConnection(c); | |||
} | |||
} | |||
} | |||
/** | |||
* Executes the given update query string using either the active connection | |||
* if a transaction is already open, or a new connection from this query's | |||
* connection pool. | |||
* | |||
* Additionally adds a new RowIdChangeEvent to the event buffer. | |||
* | |||
* @param sh | |||
* an instance of StatementHelper, containing the query string | |||
* and parameter values. | |||
* @param row | |||
* the row item to update | |||
* @return Number of affected rows | |||
* @throws SQLException | |||
*/ | |||
private int executeUpdateReturnKeys(StatementHelper sh, RowItem row) | |||
throws SQLException { | |||
Connection c = null; | |||
PreparedStatement pstmt = null; | |||
ResultSet genKeys = null; | |||
try { | |||
if (transactionOpen && activeConnection != null) { | |||
c = activeConnection; | |||
} else { | |||
c = connectionPool.reserveConnection(); | |||
} | |||
pstmt = c.prepareStatement(sh.getQueryString(), | |||
primaryKeyColumns.toArray(new String[0])); | |||
sh.setParameterValuesToStatement(pstmt); | |||
debug("DB -> " + sh.getQueryString()); | |||
int result = pstmt.executeUpdate(); | |||
genKeys = pstmt.getGeneratedKeys(); | |||
RowId newId = getNewRowId(row, genKeys); | |||
bufferedEvents.add(new RowIdChangeEvent(row.getId(), newId)); | |||
return result; | |||
} finally { | |||
if (genKeys != null) { | |||
genKeys.close(); | |||
} | |||
if (pstmt != null) { | |||
pstmt.clearParameters(); | |||
pstmt.close(); | |||
} | |||
if (!transactionOpen) { | |||
connectionPool.releaseConnection(c); | |||
} | |||
} | |||
} | |||
/** | |||
* Fetches name(s) of primary key column(s) from DB metadata. | |||
* | |||
* Also tries to get the escape string to be used in search strings. | |||
*/ | |||
private void fetchMetaData() { | |||
Connection c = null; | |||
try { | |||
c = connectionPool.reserveConnection(); | |||
DatabaseMetaData dbmd = c.getMetaData(); | |||
if (dbmd != null) { | |||
tableName = SQLUtil.escapeSQL(tableName); | |||
ResultSet tables = dbmd.getTables(null, null, tableName, null); | |||
if (!tables.next()) { | |||
tables = dbmd.getTables(null, null, | |||
tableName.toUpperCase(), null); | |||
if (!tables.next()) { | |||
throw new IllegalArgumentException( | |||
"Table with the name \"" | |||
+ tableName | |||
+ "\" was not found. Check your database contents."); | |||
} else { | |||
tableName = tableName.toUpperCase(); | |||
} | |||
} | |||
tables.close(); | |||
ResultSet rs = dbmd.getPrimaryKeys(null, null, tableName); | |||
List<String> names = new ArrayList<String>(); | |||
while (rs.next()) { | |||
names.add(rs.getString("COLUMN_NAME")); | |||
} | |||
rs.close(); | |||
if (!names.isEmpty()) { | |||
primaryKeyColumns = names; | |||
} | |||
if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) { | |||
throw new IllegalArgumentException( | |||
"Primary key constraints have not been defined for the table \"" | |||
+ tableName | |||
+ "\". Use FreeFormQuery to access this table."); | |||
} | |||
for (String colName : primaryKeyColumns) { | |||
if (colName.equalsIgnoreCase("rownum")) { | |||
if (getSqlGenerator() instanceof MSSQLGenerator | |||
|| getSqlGenerator() instanceof MSSQLGenerator) { | |||
throw new IllegalArgumentException( | |||
"When using Oracle or MSSQL, a primary key column" | |||
+ " named \'rownum\' is not allowed!"); | |||
} | |||
} | |||
} | |||
} | |||
} catch (SQLException e) { | |||
throw new RuntimeException(e); | |||
} finally { | |||
connectionPool.releaseConnection(c); | |||
} | |||
} | |||
private RowId getNewRowId(RowItem row, ResultSet genKeys) { | |||
try { | |||
/* Fetch primary key values and generate a map out of them. */ | |||
Map<String, Object> values = new HashMap<String, Object>(); | |||
ResultSetMetaData rsmd = genKeys.getMetaData(); | |||
int colCount = rsmd.getColumnCount(); | |||
if (genKeys.next()) { | |||
for (int i = 1; i <= colCount; i++) { | |||
values.put(rsmd.getColumnName(i), genKeys.getObject(i)); | |||
} | |||
} | |||
/* Generate new RowId */ | |||
List<Object> newRowId = new ArrayList<Object>(); | |||
if (values.size() == 1) { | |||
if (primaryKeyColumns.size() == 1) { | |||
newRowId.add(values.get(values.keySet().iterator().next())); | |||
} else { | |||
for (String s : primaryKeyColumns) { | |||
if (!((ColumnProperty) row.getItemProperty(s)) | |||
.isReadOnlyChangeAllowed()) { | |||
newRowId.add(values.get(values.keySet().iterator() | |||
.next())); | |||
} else { | |||
newRowId.add(values.get(s)); | |||
} | |||
} | |||
} | |||
} else { | |||
for (String s : primaryKeyColumns) { | |||
newRowId.add(values.get(s)); | |||
} | |||
} | |||
return new RowId(newRowId.toArray()); | |||
} catch (Exception e) { | |||
debug("Failed to fetch key values on insert: " + e.getMessage()); | |||
return null; | |||
} | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.vaadin.addon.sqlcontainer.query.QueryDelegate#removeRow(com.vaadin | |||
* .addon.sqlcontainer.RowItem) | |||
*/ | |||
public boolean removeRow(RowItem row) throws UnsupportedOperationException, | |||
SQLException { | |||
debug("Removing row with id: " + row.getId().getId()[0].toString()); | |||
if (executeUpdate(sqlGenerator.generateDeleteQuery(getTableName(), | |||
primaryKeyColumns, versionColumn, row)) == 1) { | |||
return true; | |||
} | |||
if (versionColumn != null) { | |||
throw new OptimisticLockException( | |||
"Someone else changed the row that was being deleted.", | |||
row.getId()); | |||
} | |||
return false; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see | |||
* com.vaadin.addon.sqlcontainer.query.QueryDelegate#containsRowWithKey( | |||
* java.lang.Object[]) | |||
*/ | |||
public boolean containsRowWithKey(Object... keys) throws SQLException { | |||
ArrayList<Filter> filtersAndKeys = new ArrayList<Filter>(); | |||
if (filters != null) { | |||
filtersAndKeys.addAll(filters); | |||
} | |||
int ix = 0; | |||
for (String colName : primaryKeyColumns) { | |||
filtersAndKeys.add(new Equal(colName, keys[ix])); | |||
ix++; | |||
} | |||
StatementHelper sh = sqlGenerator.generateSelectQuery(tableName, | |||
filtersAndKeys, orderBys, 0, 0, "*"); | |||
boolean shouldCloseTransaction = false; | |||
if (!transactionOpen) { | |||
shouldCloseTransaction = true; | |||
beginTransaction(); | |||
} | |||
ResultSet rs = null; | |||
try { | |||
rs = executeQuery(sh); | |||
boolean contains = rs.next(); | |||
return contains; | |||
} finally { | |||
if (rs != null) { | |||
if (rs.getStatement() != null) { | |||
rs.getStatement().close(); | |||
} | |||
rs.close(); | |||
} | |||
if (shouldCloseTransaction) { | |||
commit(); | |||
} | |||
} | |||
} | |||
/** | |||
* Output a debug message | |||
* | |||
* @param message | |||
*/ | |||
private void debug(String message) { | |||
if (debug) { | |||
System.out.println(message); | |||
} | |||
} | |||
/** | |||
* Enable or disable debug mode. | |||
* | |||
* @param debug | |||
*/ | |||
public void setDebug(boolean debug) { | |||
this.debug = debug; | |||
} | |||
/** | |||
* Custom writeObject to call rollback() if object is serialized. | |||
*/ | |||
private void writeObject(java.io.ObjectOutputStream out) throws IOException { | |||
try { | |||
rollback(); | |||
} catch (SQLException ignored) { | |||
} | |||
out.defaultWriteObject(); | |||
} | |||
/** | |||
* Simple RowIdChangeEvent implementation. | |||
*/ | |||
public class RowIdChangeEvent extends EventObject implements | |||
QueryDelegate.RowIdChangeEvent { | |||
private final RowId oldId; | |||
private final RowId newId; | |||
private RowIdChangeEvent(RowId oldId, RowId newId) { | |||
super(oldId); | |||
this.oldId = oldId; | |||
this.newId = newId; | |||
} | |||
public RowId getNewRowId() { | |||
return newId; | |||
} | |||
public RowId getOldRowId() { | |||
return oldId; | |||
} | |||
} | |||
/** | |||
* Adds RowIdChangeListener to this query | |||
*/ | |||
public void addListener(RowIdChangeListener listener) { | |||
if (rowIdChangeListeners == null) { | |||
rowIdChangeListeners = new LinkedList<QueryDelegate.RowIdChangeListener>(); | |||
} | |||
rowIdChangeListeners.add(listener); | |||
} | |||
/** | |||
* Removes the given RowIdChangeListener from this query | |||
*/ | |||
public void removeListener(RowIdChangeListener listener) { | |||
if (rowIdChangeListeners != null) { | |||
rowIdChangeListeners.remove(listener); | |||
} | |||
} | |||
} |
@@ -0,0 +1,308 @@ | |||
package com.vaadin.data.util.query.generator; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.ColumnProperty; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.SQLUtil; | |||
import com.vaadin.data.util.TemporaryRowId; | |||
import com.vaadin.data.util.query.OrderBy; | |||
import com.vaadin.data.util.query.generator.filter.QueryBuilder; | |||
import com.vaadin.data.util.query.generator.filter.StringDecorator; | |||
/** | |||
* Generates generic SQL that is supported by HSQLDB, MySQL and PostgreSQL. | |||
* | |||
* @author Jonatan Kronqvist / IT Mill Ltd | |||
*/ | |||
@SuppressWarnings("serial") | |||
public class DefaultSQLGenerator implements SQLGenerator { | |||
public DefaultSQLGenerator() { | |||
} | |||
/** | |||
* Construct a DefaultSQLGenerator with the specified identifiers for start | |||
* and end of quoted strings. The identifiers may be different depending on | |||
* the database engine and it's settings. | |||
* | |||
* @param quoteStart | |||
* the identifier (character) denoting the start of a quoted | |||
* string | |||
* @param quoteEnd | |||
* the identifier (character) denoting the end of a quoted string | |||
*/ | |||
public DefaultSQLGenerator(String quoteStart, String quoteEnd) { | |||
QueryBuilder.setStringDecorator(new StringDecorator(quoteStart, | |||
quoteEnd)); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator# | |||
* generateSelectQuery(java.lang.String, java.util.List, java.util.List, | |||
* int, int, java.lang.String) | |||
*/ | |||
public StatementHelper generateSelectQuery(String tableName, | |||
List<Filter> filters, List<OrderBy> orderBys, int offset, | |||
int pagelength, String toSelect) { | |||
if (tableName == null || tableName.trim().equals("")) { | |||
throw new IllegalArgumentException("Table name must be given."); | |||
} | |||
toSelect = toSelect == null ? "*" : toSelect; | |||
StatementHelper sh = new StatementHelper(); | |||
StringBuffer query = new StringBuffer(); | |||
query.append("SELECT " + toSelect + " FROM ").append( | |||
SQLUtil.escapeSQL(tableName)); | |||
if (filters != null) { | |||
query.append(QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
} | |||
if (orderBys != null) { | |||
for (OrderBy o : orderBys) { | |||
generateOrderBy(query, o, orderBys.indexOf(o) == 0); | |||
} | |||
} | |||
if (pagelength != 0) { | |||
generateLimits(query, offset, pagelength); | |||
} | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator# | |||
* generateUpdateQuery(java.lang.String, | |||
* com.vaadin.addon.sqlcontainer.RowItem) | |||
*/ | |||
public StatementHelper generateUpdateQuery(String tableName, RowItem item) { | |||
if (tableName == null || tableName.trim().equals("")) { | |||
throw new IllegalArgumentException("Table name must be given."); | |||
} | |||
if (item == null) { | |||
throw new IllegalArgumentException("Updated item must be given."); | |||
} | |||
StatementHelper sh = new StatementHelper(); | |||
StringBuffer query = new StringBuffer(); | |||
query.append("UPDATE ").append(tableName).append(" SET"); | |||
/* Generate column<->value and rowidentifiers map */ | |||
Map<String, Object> columnToValueMap = generateColumnToValueMap(item); | |||
Map<String, Object> rowIdentifiers = generateRowIdentifiers(item); | |||
/* Generate columns and values to update */ | |||
boolean first = true; | |||
for (String column : columnToValueMap.keySet()) { | |||
if (first) { | |||
query.append(" " + QueryBuilder.quote(column) + " = ?"); | |||
} else { | |||
query.append(", " + QueryBuilder.quote(column) + " = ?"); | |||
} | |||
sh.addParameterValue(columnToValueMap.get(column), item | |||
.getItemProperty(column).getType()); | |||
first = false; | |||
} | |||
/* Generate identifiers for the row to be updated */ | |||
first = true; | |||
for (String column : rowIdentifiers.keySet()) { | |||
if (first) { | |||
query.append(" WHERE " + QueryBuilder.quote(column) + " = ?"); | |||
} else { | |||
query.append(" AND " + QueryBuilder.quote(column) + " = ?"); | |||
} | |||
sh.addParameterValue(rowIdentifiers.get(column), item | |||
.getItemProperty(column).getType()); | |||
first = false; | |||
} | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator# | |||
* generateInsertQuery(java.lang.String, | |||
* com.vaadin.addon.sqlcontainer.RowItem) | |||
*/ | |||
public StatementHelper generateInsertQuery(String tableName, RowItem item) { | |||
if (tableName == null || tableName.trim().equals("")) { | |||
throw new IllegalArgumentException("Table name must be given."); | |||
} | |||
if (item == null) { | |||
throw new IllegalArgumentException("New item must be given."); | |||
} | |||
if (!(item.getId() instanceof TemporaryRowId)) { | |||
throw new IllegalArgumentException( | |||
"Cannot generate an insert query for item already in database."); | |||
} | |||
StatementHelper sh = new StatementHelper(); | |||
StringBuffer query = new StringBuffer(); | |||
query.append("INSERT INTO ").append(tableName).append(" ("); | |||
/* Generate column<->value map */ | |||
Map<String, Object> columnToValueMap = generateColumnToValueMap(item); | |||
/* Generate column names for insert query */ | |||
boolean first = true; | |||
for (String column : columnToValueMap.keySet()) { | |||
if (!first) { | |||
query.append(", "); | |||
} | |||
query.append(QueryBuilder.quote(column)); | |||
first = false; | |||
} | |||
/* Generate values for insert query */ | |||
query.append(") VALUES ("); | |||
first = true; | |||
for (String column : columnToValueMap.keySet()) { | |||
if (!first) { | |||
query.append(", "); | |||
} | |||
query.append("?"); | |||
sh.addParameterValue(columnToValueMap.get(column), item | |||
.getItemProperty(column).getType()); | |||
first = false; | |||
} | |||
query.append(")"); | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator# | |||
* generateDeleteQuery(java.lang.String, | |||
* com.vaadin.addon.sqlcontainer.RowItem) | |||
*/ | |||
public StatementHelper generateDeleteQuery(String tableName, | |||
List<String> primaryKeyColumns, String versionColumn, RowItem item) { | |||
if (tableName == null || tableName.trim().equals("")) { | |||
throw new IllegalArgumentException("Table name must be given."); | |||
} | |||
if (item == null) { | |||
throw new IllegalArgumentException( | |||
"Item to be deleted must be given."); | |||
} | |||
if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) { | |||
throw new IllegalArgumentException( | |||
"Valid keyColumnNames must be provided."); | |||
} | |||
StatementHelper sh = new StatementHelper(); | |||
StringBuffer query = new StringBuffer(); | |||
query.append("DELETE FROM ").append(tableName).append(" WHERE "); | |||
int count = 1; | |||
for (String keyColName : primaryKeyColumns) { | |||
if ((this instanceof MSSQLGenerator || this instanceof OracleGenerator) | |||
&& keyColName.equalsIgnoreCase("rownum")) { | |||
count++; | |||
continue; | |||
} | |||
if (count > 1) { | |||
query.append(" AND "); | |||
} | |||
if (item.getItemProperty(keyColName).getValue() != null) { | |||
query.append(QueryBuilder.quote(keyColName) + " = ?"); | |||
sh.addParameterValue(item.getItemProperty(keyColName) | |||
.getValue(), item.getItemProperty(keyColName).getType()); | |||
} | |||
count++; | |||
} | |||
if (versionColumn != null) { | |||
query.append(String.format(" AND %s = ?", | |||
QueryBuilder.quote(versionColumn))); | |||
sh.addParameterValue( | |||
item.getItemProperty(versionColumn).getValue(), item | |||
.getItemProperty(versionColumn).getType()); | |||
} | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/** | |||
* Generates sorting rules as an ORDER BY -clause | |||
* | |||
* @param sb | |||
* StringBuffer to which the clause is appended. | |||
* @param o | |||
* OrderBy object to be added into the sb. | |||
* @param firstOrderBy | |||
* If true, this is the first OrderBy. | |||
* @return | |||
*/ | |||
protected StringBuffer generateOrderBy(StringBuffer sb, OrderBy o, | |||
boolean firstOrderBy) { | |||
if (firstOrderBy) { | |||
sb.append(" ORDER BY "); | |||
} else { | |||
sb.append(", "); | |||
} | |||
sb.append(QueryBuilder.quote(o.getColumn())); | |||
if (o.isAscending()) { | |||
sb.append(" ASC"); | |||
} else { | |||
sb.append(" DESC"); | |||
} | |||
return sb; | |||
} | |||
/** | |||
* Generates the LIMIT and OFFSET clause. | |||
* | |||
* @param sb | |||
* StringBuffer to which the clause is appended. | |||
* @param offset | |||
* Value for offset. | |||
* @param pagelength | |||
* Value for pagelength. | |||
* @return StringBuffer with LIMIT and OFFSET clause added. | |||
*/ | |||
protected StringBuffer generateLimits(StringBuffer sb, int offset, | |||
int pagelength) { | |||
sb.append(" LIMIT ").append(pagelength).append(" OFFSET ") | |||
.append(offset); | |||
return sb; | |||
} | |||
protected Map<String, Object> generateColumnToValueMap(RowItem item) { | |||
Map<String, Object> columnToValueMap = new HashMap<String, Object>(); | |||
for (Object id : item.getItemPropertyIds()) { | |||
ColumnProperty cp = (ColumnProperty) item.getItemProperty(id); | |||
/* Prevent "rownum" usage as a column name if MSSQL or ORACLE */ | |||
if ((this instanceof MSSQLGenerator || this instanceof OracleGenerator) | |||
&& cp.getPropertyId().equalsIgnoreCase("rownum")) { | |||
continue; | |||
} | |||
Object value = cp.getValue() == null ? null : cp.getValue(); | |||
/* Only include properties whose read-only status can be altered */ | |||
if (cp.isReadOnlyChangeAllowed() && !cp.isVersionColumn()) { | |||
columnToValueMap.put(cp.getPropertyId(), value); | |||
} | |||
} | |||
return columnToValueMap; | |||
} | |||
protected Map<String, Object> generateRowIdentifiers(RowItem item) { | |||
Map<String, Object> rowIdentifiers = new HashMap<String, Object>(); | |||
for (Object id : item.getItemPropertyIds()) { | |||
ColumnProperty cp = (ColumnProperty) item.getItemProperty(id); | |||
/* Prevent "rownum" usage as a column name if MSSQL or ORACLE */ | |||
if ((this instanceof MSSQLGenerator || this instanceof OracleGenerator) | |||
&& cp.getPropertyId().equalsIgnoreCase("rownum")) { | |||
continue; | |||
} | |||
Object value = cp.getValue() == null ? null : cp.getValue(); | |||
if (!cp.isReadOnlyChangeAllowed() || cp.isVersionColumn()) { | |||
rowIdentifiers.put(cp.getPropertyId(), value); | |||
} | |||
} | |||
return rowIdentifiers; | |||
} | |||
} |
@@ -0,0 +1,101 @@ | |||
package com.vaadin.data.util.query.generator; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.query.OrderBy; | |||
import com.vaadin.data.util.query.generator.filter.QueryBuilder; | |||
@SuppressWarnings("serial") | |||
public class MSSQLGenerator extends DefaultSQLGenerator { | |||
public MSSQLGenerator() { | |||
} | |||
/** | |||
* Construct a MSSQLGenerator with the specified identifiers for start and | |||
* end of quoted strings. The identifiers may be different depending on the | |||
* database engine and it's settings. | |||
* | |||
* @param quoteStart | |||
* the identifier (character) denoting the start of a quoted | |||
* string | |||
* @param quoteEnd | |||
* the identifier (character) denoting the end of a quoted string | |||
*/ | |||
public MSSQLGenerator(String quoteStart, String quoteEnd) { | |||
super(quoteStart, quoteEnd); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.generator.DefaultSQLGenerator# | |||
* generateSelectQuery(java.lang.String, java.util.List, | |||
* com.vaadin.addon.sqlcontainer.query.FilteringMode, java.util.List, int, | |||
* int, java.lang.String) | |||
*/ | |||
@Override | |||
public StatementHelper generateSelectQuery(String tableName, | |||
List<Filter> filters, List<OrderBy> orderBys, int offset, | |||
int pagelength, String toSelect) { | |||
if (tableName == null || tableName.trim().equals("")) { | |||
throw new IllegalArgumentException("Table name must be given."); | |||
} | |||
/* Adjust offset and page length parameters to match "row numbers" */ | |||
offset = pagelength > 1 ? ++offset : offset; | |||
pagelength = pagelength > 1 ? --pagelength : pagelength; | |||
toSelect = toSelect == null ? "*" : toSelect; | |||
StatementHelper sh = new StatementHelper(); | |||
StringBuffer query = new StringBuffer(); | |||
/* Row count request is handled here */ | |||
if ("COUNT(*)".equalsIgnoreCase(toSelect)) { | |||
query.append(String.format( | |||
"SELECT COUNT(*) AS %s FROM (SELECT * FROM %s", | |||
QueryBuilder.quote("rowcount"), tableName)); | |||
if (filters != null && !filters.isEmpty()) { | |||
query.append(QueryBuilder.getWhereStringForFilters( | |||
filters, sh)); | |||
} | |||
query.append(") AS t"); | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/* SELECT without row number constraints */ | |||
if (offset == 0 && pagelength == 0) { | |||
query.append("SELECT ").append(toSelect).append(" FROM ") | |||
.append(tableName); | |||
if (filters != null) { | |||
query.append(QueryBuilder.getWhereStringForFilters( | |||
filters, sh)); | |||
} | |||
if (orderBys != null) { | |||
for (OrderBy o : orderBys) { | |||
generateOrderBy(query, o, orderBys.indexOf(o) == 0); | |||
} | |||
} | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/* Remaining SELECT cases are handled here */ | |||
query.append("SELECT * FROM (SELECT row_number() OVER ("); | |||
if (orderBys != null) { | |||
for (OrderBy o : orderBys) { | |||
generateOrderBy(query, o, orderBys.indexOf(o) == 0); | |||
} | |||
} | |||
query.append(") AS rownum, " + toSelect + " FROM ").append(tableName); | |||
if (filters != null) { | |||
query.append(QueryBuilder.getWhereStringForFilters( | |||
filters, sh)); | |||
} | |||
query.append(") AS a WHERE a.rownum BETWEEN ").append(offset) | |||
.append(" AND ").append(Integer.toString(offset + pagelength)); | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
} |
@@ -0,0 +1,99 @@ | |||
package com.vaadin.data.util.query.generator; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.query.OrderBy; | |||
import com.vaadin.data.util.query.generator.filter.QueryBuilder; | |||
@SuppressWarnings("serial") | |||
public class OracleGenerator extends DefaultSQLGenerator { | |||
public OracleGenerator() { | |||
} | |||
/** | |||
* Construct an OracleSQLGenerator with the specified identifiers for start | |||
* and end of quoted strings. The identifiers may be different depending on | |||
* the database engine and it's settings. | |||
* | |||
* @param quoteStart | |||
* the identifier (character) denoting the start of a quoted | |||
* string | |||
* @param quoteEnd | |||
* the identifier (character) denoting the end of a quoted string | |||
*/ | |||
public OracleGenerator(String quoteStart, String quoteEnd) { | |||
super(quoteStart, quoteEnd); | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.addon.sqlcontainer.query.generator.DefaultSQLGenerator# | |||
* generateSelectQuery(java.lang.String, java.util.List, | |||
* com.vaadin.addon.sqlcontainer.query.FilteringMode, java.util.List, int, | |||
* int, java.lang.String) | |||
*/ | |||
@Override | |||
public StatementHelper generateSelectQuery(String tableName, | |||
List<Filter> filters, List<OrderBy> orderBys, int offset, | |||
int pagelength, String toSelect) { | |||
if (tableName == null || tableName.trim().equals("")) { | |||
throw new IllegalArgumentException("Table name must be given."); | |||
} | |||
/* Adjust offset and page length parameters to match "row numbers" */ | |||
offset = pagelength > 1 ? ++offset : offset; | |||
pagelength = pagelength > 1 ? --pagelength : pagelength; | |||
toSelect = toSelect == null ? "*" : toSelect; | |||
StatementHelper sh = new StatementHelper(); | |||
StringBuffer query = new StringBuffer(); | |||
/* Row count request is handled here */ | |||
if ("COUNT(*)".equalsIgnoreCase(toSelect)) { | |||
query.append(String.format( | |||
"SELECT COUNT(*) AS %s FROM (SELECT * FROM %s", | |||
QueryBuilder.quote("rowcount"), tableName)); | |||
if (filters != null && !filters.isEmpty()) { | |||
query.append(QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
} | |||
query.append(")"); | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/* SELECT without row number constraints */ | |||
if (offset == 0 && pagelength == 0) { | |||
query.append("SELECT ").append(toSelect).append(" FROM ") | |||
.append(tableName); | |||
if (filters != null) { | |||
query.append(QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
} | |||
if (orderBys != null) { | |||
for (OrderBy o : orderBys) { | |||
generateOrderBy(query, o, orderBys.indexOf(o) == 0); | |||
} | |||
} | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
/* Remaining SELECT cases are handled here */ | |||
query.append(String | |||
.format("SELECT * FROM (SELECT x.*, ROWNUM AS %s FROM (SELECT %s FROM %s", | |||
QueryBuilder.quote("rownum"), toSelect, tableName)); | |||
if (filters != null) { | |||
query.append(QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
} | |||
if (orderBys != null) { | |||
for (OrderBy o : orderBys) { | |||
generateOrderBy(query, o, orderBys.indexOf(o) == 0); | |||
} | |||
} | |||
query.append(String.format(") x) WHERE %s BETWEEN %d AND %d", | |||
QueryBuilder.quote("rownum"), offset, offset + pagelength)); | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
} |
@@ -0,0 +1,85 @@ | |||
package com.vaadin.data.util.query.generator; | |||
import java.io.Serializable; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.query.OrderBy; | |||
/** | |||
* The SQLGenerator interface is meant to be implemented for each different SQL | |||
* syntax that is to be supported. By default there are implementations for | |||
* HSQLDB, MySQL, PostgreSQL, MSSQL and Oracle syntaxes. | |||
* | |||
* @author Jonatan Kronqvist / IT Mill Ltd | |||
*/ | |||
public interface SQLGenerator extends Serializable { | |||
/** | |||
* Generates a SELECT query with the provided parameters. Uses default | |||
* filtering mode (INCLUSIVE). | |||
* | |||
* @param tableName | |||
* Name of the table queried | |||
* @param filters | |||
* The filters, converted into a WHERE clause | |||
* @param orderBys | |||
* The the ordering conditions, converted into an ORDER BY clause | |||
* @param offset | |||
* The offset of the first row to be included | |||
* @param pagelength | |||
* The number of rows to be returned when the query executes | |||
* @param toSelect | |||
* String containing what to select, e.g. "*", "COUNT(*)" | |||
* @return StatementHelper instance containing the query string for a | |||
* PreparedStatement and the values required for the parameters | |||
*/ | |||
public StatementHelper generateSelectQuery(String tableName, | |||
List<Filter> filters, List<OrderBy> orderBys, int offset, | |||
int pagelength, String toSelect); | |||
/** | |||
* Generates an UPDATE query with the provided parameters. | |||
* | |||
* @param tableName | |||
* Name of the table queried | |||
* @param item | |||
* RowItem containing the updated values update. | |||
* @return StatementHelper instance containing the query string for a | |||
* PreparedStatement and the values required for the parameters | |||
*/ | |||
public StatementHelper generateUpdateQuery(String tableName, RowItem item); | |||
/** | |||
* Generates an INSERT query for inserting a new row with the provided | |||
* values. | |||
* | |||
* @param tableName | |||
* Name of the table queried | |||
* @param item | |||
* New RowItem to be inserted into the database. | |||
* @return StatementHelper instance containing the query string for a | |||
* PreparedStatement and the values required for the parameters | |||
*/ | |||
public StatementHelper generateInsertQuery(String tableName, RowItem item); | |||
/** | |||
* Generates a DELETE query for deleting data related to the given RowItem | |||
* from the database. | |||
* | |||
* @param tableName | |||
* Name of the table queried | |||
* @param primaryKeyColumns | |||
* the names of the columns holding the primary key. Usually just | |||
* one column, but might be several. | |||
* @param versionColumn | |||
* the column containing the version number of the row, null if | |||
* versioning (optimistic locking) not enabled. | |||
* @param item | |||
* Item to be deleted from the database | |||
* @return StatementHelper instance containing the query string for a | |||
* PreparedStatement and the values required for the parameters | |||
*/ | |||
public StatementHelper generateDeleteQuery(String tableName, | |||
List<String> primaryKeyColumns, String versionColumn, RowItem item); | |||
} |
@@ -0,0 +1,131 @@ | |||
package com.vaadin.data.util.query.generator; | |||
import java.math.BigDecimal; | |||
import java.sql.Date; | |||
import java.sql.PreparedStatement; | |||
import java.sql.SQLException; | |||
import java.sql.Time; | |||
import java.sql.Timestamp; | |||
import java.sql.Types; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
/** | |||
* StatementHelper is a simple helper class that assists TableQuery and the | |||
* query generators in filling a PreparedStatement. The actual statement is | |||
* generated by the query generator methods, but the resulting statement and all | |||
* the parameter values are stored in an instance of StatementHelper. | |||
* | |||
* This class will also fill the values with correct setters into the | |||
* PreparedStatement on request. | |||
*/ | |||
public class StatementHelper { | |||
private String queryString; | |||
private List<Object> parameters = new ArrayList<Object>(); | |||
private Map<Integer, Class<?>> dataTypes = new HashMap<Integer, Class<?>>(); | |||
public StatementHelper() { | |||
} | |||
public void setQueryString(String queryString) { | |||
this.queryString = queryString; | |||
} | |||
public String getQueryString() { | |||
return queryString; | |||
} | |||
public void addParameterValue(Object parameter) { | |||
if (parameter != null) { | |||
parameters.add(parameter); | |||
dataTypes.put(parameters.size() - 1, parameter.getClass()); | |||
} | |||
} | |||
public void addParameterValue(Object parameter, Class<?> type) { | |||
parameters.add(parameter); | |||
dataTypes.put(parameters.size() - 1, type); | |||
} | |||
public void setParameterValuesToStatement(PreparedStatement pstmt) | |||
throws SQLException { | |||
for (int i = 0; i < parameters.size(); i++) { | |||
if (parameters.get(i) == null) { | |||
handleNullValue(i, pstmt); | |||
} else { | |||
pstmt.setObject(i + 1, parameters.get(i)); | |||
} | |||
} | |||
/* | |||
* The following list contains the data types supported by | |||
* PreparedStatement but not supported by SQLContainer: | |||
* | |||
* [The list is provided as PreparedStatement method signatures] | |||
* | |||
* setNCharacterStream(int parameterIndex, Reader value) | |||
* | |||
* setNClob(int parameterIndex, NClob value) | |||
* | |||
* setNString(int parameterIndex, String value) | |||
* | |||
* setRef(int parameterIndex, Ref x) | |||
* | |||
* setRowId(int parameterIndex, RowId x) | |||
* | |||
* setSQLXML(int parameterIndex, SQLXML xmlObject) | |||
* | |||
* setBytes(int parameterIndex, byte[] x) | |||
* | |||
* setCharacterStream(int parameterIndex, Reader reader) | |||
* | |||
* setClob(int parameterIndex, Clob x) | |||
* | |||
* setURL(int parameterIndex, URL x) | |||
* | |||
* setArray(int parameterIndex, Array x) | |||
* | |||
* setAsciiStream(int parameterIndex, InputStream x) | |||
* | |||
* setBinaryStream(int parameterIndex, InputStream x) | |||
* | |||
* setBlob(int parameterIndex, Blob x) | |||
*/ | |||
} | |||
private void handleNullValue(int i, PreparedStatement pstmt) | |||
throws SQLException { | |||
if (BigDecimal.class.equals(dataTypes.get(i))) { | |||
pstmt.setBigDecimal(i + 1, null); | |||
} else if (Boolean.class.equals(dataTypes.get(i))) { | |||
pstmt.setNull(i + 1, Types.BOOLEAN); | |||
} else if (Byte.class.equals(dataTypes.get(i))) { | |||
pstmt.setNull(i + 1, Types.SMALLINT); | |||
} else if (Date.class.equals(dataTypes.get(i))) { | |||
pstmt.setDate(i + 1, null); | |||
} else if (Double.class.equals(dataTypes.get(i))) { | |||
pstmt.setNull(i + 1, Types.DOUBLE); | |||
} else if (Float.class.equals(dataTypes.get(i))) { | |||
pstmt.setNull(i + 1, Types.FLOAT); | |||
} else if (Integer.class.equals(dataTypes.get(i))) { | |||
pstmt.setNull(i + 1, Types.INTEGER); | |||
} else if (Long.class.equals(dataTypes.get(i))) { | |||
pstmt.setNull(i + 1, Types.BIGINT); | |||
} else if (Short.class.equals(dataTypes.get(i))) { | |||
pstmt.setNull(i + 1, Types.SMALLINT); | |||
} else if (String.class.equals(dataTypes.get(i))) { | |||
pstmt.setString(i + 1, null); | |||
} else if (Time.class.equals(dataTypes.get(i))) { | |||
pstmt.setTime(i + 1, null); | |||
} else if (Timestamp.class.equals(dataTypes.get(i))) { | |||
pstmt.setTimestamp(i + 1, null); | |||
} else { | |||
throw new SQLException("Data type not supported by SQLContainer: " | |||
+ parameters.get(i).getClass().toString()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.And; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class AndTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof And; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
return QueryBuilder.group(QueryBuilder | |||
.getJoinedFilterString(((And) filter).getFilters(), "AND", sh)); | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.Between; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class BetweenTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof Between; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
Between between = (Between) filter; | |||
sh.addParameterValue(between.getStartValue()); | |||
sh.addParameterValue(between.getEndValue()); | |||
return QueryBuilder.quote(between.getPropertyId()) | |||
+ " BETWEEN ? AND ?"; | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.Compare; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class CompareTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof Compare; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
Compare compare = (Compare) filter; | |||
sh.addParameterValue(compare.getValue()); | |||
String prop = QueryBuilder.quote(compare.getPropertyId()); | |||
switch (compare.getOperation()) { | |||
case EQUAL: | |||
return prop + " = ?"; | |||
case GREATER: | |||
return prop + " > ?"; | |||
case GREATER_OR_EQUAL: | |||
return prop + " >= ?"; | |||
case LESS: | |||
return prop + " < ?"; | |||
case LESS_OR_EQUAL: | |||
return prop + " <= ?"; | |||
default: | |||
return ""; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public interface FilterTranslator { | |||
public boolean translatesFilter(Filter filter); | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh); | |||
} |
@@ -0,0 +1,17 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.IsNull; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class IsNullTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof IsNull; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
IsNull in = (IsNull) filter; | |||
return QueryBuilder.quote(in.getPropertyId()) + " IS NULL"; | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.Like; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class LikeTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof Like; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
Like like = (Like) filter; | |||
if (like.isCaseSensitive()) { | |||
sh.addParameterValue(like.getValue()); | |||
return QueryBuilder.quote(like.getPropertyId()) | |||
+ " LIKE ?"; | |||
} else { | |||
sh.addParameterValue(like.getValue().toUpperCase()); | |||
return "UPPER(" | |||
+ QueryBuilder.quote(like.getPropertyId()) | |||
+ ") LIKE ?"; | |||
} | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.IsNull; | |||
import com.vaadin.data.util.filter.Not; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class NotTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof Not; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
Not not = (Not) filter; | |||
if (not.getFilter() instanceof IsNull) { | |||
IsNull in = (IsNull) not.getFilter(); | |||
return QueryBuilder.quote(in.getPropertyId()) | |||
+ " IS NOT NULL"; | |||
} | |||
return "NOT " | |||
+ QueryBuilder.getWhereStringForFilter( | |||
not.getFilter(), sh); | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.Or; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class OrTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof Or; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
return QueryBuilder.group(QueryBuilder | |||
.getJoinedFilterString(((Or) filter).getFilters(), "OR", sh)); | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class QueryBuilder { | |||
private static ArrayList<FilterTranslator> filterTranslators = new ArrayList<FilterTranslator>(); | |||
private static StringDecorator stringDecorator = new StringDecorator("\"", | |||
"\""); | |||
static { | |||
/* Register all default filter translators */ | |||
addFilterTranslator(new AndTranslator()); | |||
addFilterTranslator(new OrTranslator()); | |||
addFilterTranslator(new LikeTranslator()); | |||
addFilterTranslator(new BetweenTranslator()); | |||
addFilterTranslator(new CompareTranslator()); | |||
addFilterTranslator(new NotTranslator()); | |||
addFilterTranslator(new IsNullTranslator()); | |||
addFilterTranslator(new SimpleStringTranslator()); | |||
} | |||
public synchronized static void addFilterTranslator( | |||
FilterTranslator translator) { | |||
filterTranslators.add(translator); | |||
} | |||
/** | |||
* Allows specification of a custom ColumnQuoter instance that handles | |||
* quoting of column names for the current DB dialect. | |||
* | |||
* @param decorator | |||
* the ColumnQuoter instance to use. | |||
*/ | |||
public static void setStringDecorator(StringDecorator decorator) { | |||
stringDecorator = decorator; | |||
} | |||
public static String quote(Object str) { | |||
return stringDecorator.quote(str); | |||
} | |||
public static String group(String str) { | |||
return stringDecorator.group(str); | |||
} | |||
/** | |||
* Constructs and returns a string representing the filter that can be used | |||
* in a WHERE clause. | |||
* | |||
* @param filter | |||
* the filter to translate | |||
* @param sh | |||
* the statement helper to update with the value(s) of the filter | |||
* @return a string representing the filter. | |||
*/ | |||
public synchronized static String getWhereStringForFilter(Filter filter, | |||
StatementHelper sh) { | |||
for (FilterTranslator ft : filterTranslators) { | |||
if (ft.translatesFilter(filter)) { | |||
return ft.getWhereStringForFilter(filter, sh); | |||
} | |||
} | |||
return ""; | |||
} | |||
public static String getJoinedFilterString(Collection<Filter> filters, | |||
String joinString, StatementHelper sh) { | |||
StringBuilder result = new StringBuilder(); | |||
for (Filter f : filters) { | |||
result.append(getWhereStringForFilter(f, sh)); | |||
result.append(" ").append(joinString).append(" "); | |||
} | |||
// Remove the last instance of joinString | |||
result.delete(result.length() - joinString.length() - 2, | |||
result.length()); | |||
return result.toString(); | |||
} | |||
public static String getWhereStringForFilters(List<Filter> filters, | |||
StatementHelper sh) { | |||
if (filters == null || filters.isEmpty()) { | |||
return ""; | |||
} | |||
StringBuilder where = new StringBuilder(" WHERE "); | |||
where.append(getJoinedFilterString(filters, "AND", sh)); | |||
return where.toString(); | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.Like; | |||
import com.vaadin.data.util.filter.SimpleStringFilter; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
public class SimpleStringTranslator implements FilterTranslator { | |||
public boolean translatesFilter(Filter filter) { | |||
return filter instanceof SimpleStringFilter; | |||
} | |||
public String getWhereStringForFilter(Filter filter, StatementHelper sh) { | |||
SimpleStringFilter ssf = (SimpleStringFilter) filter; | |||
// Create a Like filter based on the SimpleStringFilter and execute the | |||
// LikeTranslator | |||
String likeStr = ssf.isOnlyMatchPrefix() ? ssf.getFilterString() + "%" | |||
: "%" + ssf.getFilterString() + "%"; | |||
Like like = new Like(ssf.getPropertyId().toString(), likeStr); | |||
like.setCaseSensitive(!ssf.isIgnoreCase()); | |||
return new LikeTranslator().getWhereStringForFilter(like, sh); | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
package com.vaadin.data.util.query.generator.filter; | |||
/** | |||
* The StringDecorator knows how to produce a quoted string using the specified | |||
* quote start and quote end characters. It also handles grouping of a string | |||
* (surrounding it in parenthesis). | |||
* | |||
* Extend this class if you need to support special characters for grouping | |||
* (parenthesis). | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
public class StringDecorator { | |||
private final String quoteStart; | |||
private final String quoteEnd; | |||
/** | |||
* Constructs a StringDecorator that uses the quoteStart and quoteEnd | |||
* characters to create quoted strings. | |||
* | |||
* @param quoteStart | |||
* the character denoting the start of a quote. | |||
* @param quoteEnd | |||
* the character denoting the end of a quote. | |||
*/ | |||
public StringDecorator(String quoteStart, String quoteEnd) { | |||
this.quoteStart = quoteStart; | |||
this.quoteEnd = quoteEnd; | |||
} | |||
/** | |||
* Surround a string with quote characters. | |||
* | |||
* @param str | |||
* the string to quote | |||
* @return the quoted string | |||
*/ | |||
public String quote(Object str) { | |||
return quoteStart + str + quoteEnd; | |||
} | |||
/** | |||
* Groups a string by surrounding it in parenthesis | |||
* | |||
* @param str | |||
* the string to group | |||
* @return the grouped string | |||
*/ | |||
public String group(String str) { | |||
return "(" + str + ")"; | |||
} | |||
} |
@@ -0,0 +1,191 @@ | |||
package com.vaadin.tests.containers.sqlcontainer; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
import com.vaadin.Application; | |||
import com.vaadin.data.Container.ItemSetChangeEvent; | |||
import com.vaadin.data.Container.ItemSetChangeListener; | |||
import com.vaadin.data.Item; | |||
import com.vaadin.data.Property; | |||
import com.vaadin.data.Property.ValueChangeEvent; | |||
import com.vaadin.data.util.SQLContainer; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.connection.SimpleJDBCConnectionPool; | |||
import com.vaadin.data.util.query.TableQuery; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
import com.vaadin.ui.Form; | |||
import com.vaadin.ui.HorizontalSplitPanel; | |||
import com.vaadin.ui.Table; | |||
import com.vaadin.ui.Window; | |||
public class CheckboxUpdateProblem extends Application implements | |||
Property.ValueChangeListener { | |||
private final DatabaseHelper databaseHelper = new DatabaseHelper(); | |||
private Table testList; | |||
private final HorizontalSplitPanel horizontalSplit = new HorizontalSplitPanel(); | |||
private TestForm testForm = new TestForm(); | |||
@Override | |||
public void init() { | |||
setMainWindow(new Window("Test window")); | |||
horizontalSplit.setSizeFull(); | |||
testList = new Table(); | |||
horizontalSplit.setFirstComponent(testList); | |||
testList.setSizeFull(); | |||
testList.setContainerDataSource(databaseHelper.getTestContainer()); | |||
testList.setSelectable(true); | |||
testList.setImmediate(true); | |||
testList.addListener(this); | |||
databaseHelper.getTestContainer().addListener( | |||
new ItemSetChangeListener() { | |||
public void containerItemSetChange(ItemSetChangeEvent event) { | |||
Object selected = testList.getValue(); | |||
if (selected != null) { | |||
testForm.setItemDataSource(testList | |||
.getItem(selected)); | |||
} | |||
} | |||
}); | |||
testForm = new TestForm(); | |||
testForm.setItemDataSource(null); | |||
horizontalSplit.setSecondComponent(testForm); | |||
getMainWindow().setContent(horizontalSplit); | |||
} | |||
public void valueChange(ValueChangeEvent event) { | |||
Property property = event.getProperty(); | |||
if (property == testList) { | |||
Item item = testList.getItem(testList.getValue()); | |||
if (item != testForm.getItemDataSource()) { | |||
testForm.setItemDataSource(item); | |||
} | |||
} | |||
} | |||
private class TestForm extends Form implements Button.ClickListener { | |||
private final Button save; | |||
private TestForm() { | |||
setSizeFull(); | |||
setWriteThrough(false); | |||
setInvalidCommitted(false); | |||
save = new Button("Save", (ClickListener) this); | |||
getFooter().addComponent(save); | |||
getFooter().setVisible(false); | |||
} | |||
public void buttonClick(ClickEvent event) { | |||
if (event.getSource() == save) { | |||
super.commit(); | |||
try { | |||
databaseHelper.getTestContainer().commit(); | |||
getMainWindow().showNotification("Saved"); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
} | |||
@Override | |||
public void setItemDataSource(Item newDataSource) { | |||
super.setItemDataSource(newDataSource); | |||
if (newDataSource != null) { | |||
getFooter().setVisible(true); | |||
} else { | |||
getFooter().setVisible(false); | |||
} | |||
} | |||
} | |||
private class DatabaseHelper { | |||
private JDBCConnectionPool connectionPool = null; | |||
private SQLContainer testContainer = null; | |||
private static final String TABLENAME = "testtable"; | |||
public DatabaseHelper() { | |||
initConnectionPool(); | |||
initDatabase(); | |||
initContainers(); | |||
} | |||
private void initDatabase() { | |||
try { | |||
Connection conn = connectionPool.reserveConnection(); | |||
Statement statement = conn.createStatement(); | |||
try { | |||
statement.execute("drop table " + TABLENAME); | |||
} catch (SQLException e) { | |||
// Will fail if table doesn't exist, which is OK. | |||
conn.rollback(); | |||
} | |||
switch (AllTests.db) { | |||
case MYSQL: | |||
statement | |||
.execute("create table " | |||
+ TABLENAME | |||
+ " (id integer auto_increment not null, field1 varchar(100), field2 boolean, primary key(id))"); | |||
break; | |||
case POSTGRESQL: | |||
statement | |||
.execute("create table " | |||
+ TABLENAME | |||
+ " (\"id\" serial primary key, \"field1\" varchar(100), \"field2\" boolean)"); | |||
break; | |||
} | |||
statement.executeUpdate("insert into " + TABLENAME | |||
+ " values(default, 'Kalle', 'true')"); | |||
statement.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
private void initContainers() { | |||
try { | |||
TableQuery q1 = new TableQuery(TABLENAME, connectionPool); | |||
q1.setVersionColumn("id"); | |||
testContainer = new SQLContainer(q1); | |||
testContainer.setDebugMode(true); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
private void initConnectionPool() { | |||
try { | |||
connectionPool = new SimpleJDBCConnectionPool( | |||
AllTests.dbDriver, AllTests.dbURL, AllTests.dbUser, | |||
AllTests.dbPwd, 2, 5); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
public SQLContainer getTestContainer() { | |||
return testContainer; | |||
} | |||
} | |||
} |
@@ -0,0 +1,134 @@ | |||
package com.vaadin.tests.containers.sqlcontainer; | |||
import java.sql.SQLException; | |||
import com.vaadin.Application; | |||
import com.vaadin.data.util.SQLContainer; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.connection.SimpleJDBCConnectionPool; | |||
import com.vaadin.data.util.query.TableQuery; | |||
import com.vaadin.ui.Alignment; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.ComponentContainer; | |||
import com.vaadin.ui.ProgressIndicator; | |||
import com.vaadin.ui.VerticalLayout; | |||
import com.vaadin.ui.Window; | |||
// author table in testdb (MySQL) is set out as follows | |||
// +-------------+-------------+------+-----+---------+----------------+ | |||
// | Field | Type | Null | Key | Default | Extra | | |||
// +-------------+-------------+------+-----+---------+----------------+ | |||
// | id | int(11) | NO | PRI | NULL | auto_increment | | |||
// | last_name | varchar(40) | NO | | NULL | | | |||
// | first_names | varchar(80) | NO | | NULL | | | |||
// +-------------+-------------+------+-----+---------+----------------+ | |||
@SuppressWarnings("serial") | |||
public class MassInsertMemoryLeakTestApp extends Application { | |||
ProgressIndicator proggress = new ProgressIndicator(); | |||
Button process = new Button("Mass insert"); | |||
@Override | |||
public void init() { | |||
setMainWindow(new Window("SQLContainer Test", buildLayout())); | |||
process.addListener(new Button.ClickListener() { | |||
public void buttonClick(ClickEvent event) { | |||
MassInsert mi = new MassInsert(); | |||
mi.start(); | |||
} | |||
}); | |||
} | |||
private class MassInsert extends Thread { | |||
@Override | |||
public synchronized void start() { | |||
proggress.setVisible(true); | |||
proggress.setValue(new Float(0)); | |||
proggress.setPollingInterval(100); | |||
process.setEnabled(false); | |||
proggress.setCaption(""); | |||
super.start(); | |||
} | |||
@Override | |||
public void run() { | |||
JDBCConnectionPool pool = getConnectionPool(); | |||
if (pool != null) { | |||
try { | |||
int cents = 100; | |||
for (int cent = 0; cent < cents; cent++) { | |||
TableQuery q = new TableQuery("AUTHOR", pool); | |||
q.setVersionColumn("ID"); | |||
SQLContainer c = new SQLContainer(q); | |||
for (int i = 0; i < 100; i++) { | |||
Object id = c.addItem(); | |||
c.getContainerProperty(id, "FIRST_NAMES").setValue( | |||
getRandonName()); | |||
c.getContainerProperty(id, "LAST_NAME").setValue( | |||
getRandonName()); | |||
} | |||
c.commit(); | |||
synchronized (MassInsertMemoryLeakTestApp.this) { | |||
proggress | |||
.setValue(new Float((1.0f * cent) / cents)); | |||
proggress.setCaption("" + 100 * cent | |||
+ " rows inserted"); | |||
} | |||
} | |||
} catch (SQLException e) { | |||
getMainWindow().showNotification( | |||
"SQLException while processing", | |||
e.getLocalizedMessage()); | |||
e.printStackTrace(); | |||
} | |||
} | |||
synchronized (MassInsertMemoryLeakTestApp.this) { | |||
proggress.setVisible(false); | |||
proggress.setPollingInterval(0); | |||
process.setEnabled(true); | |||
} | |||
} | |||
} | |||
private ComponentContainer buildLayout() { | |||
VerticalLayout lo = new VerticalLayout(); | |||
lo.setSizeFull(); | |||
lo.addComponent(proggress); | |||
lo.addComponent(process); | |||
lo.setComponentAlignment(proggress, Alignment.BOTTOM_CENTER); | |||
lo.setComponentAlignment(process, Alignment.TOP_CENTER); | |||
lo.setSpacing(true); | |||
proggress.setIndeterminate(false); | |||
proggress.setVisible(false); | |||
return lo; | |||
} | |||
private String getRandonName() { | |||
final String[] tokens = new String[] { "sa", "len", "da", "vid", "ma", | |||
"ry", "an", "na", "jo", "bri", "son", "mat", "e", "ric", "ge", | |||
"eu", "han", "har", "ri", "ja", "lo" }; | |||
StringBuffer sb = new StringBuffer(); | |||
int len = (int) (Math.random() * 3 + 2); | |||
while (len-- > 0) { | |||
sb.append(tokens[(int) (Math.random() * tokens.length)]); | |||
} | |||
return Character.toUpperCase(sb.charAt(0)) + sb.toString().substring(1); | |||
} | |||
private JDBCConnectionPool getConnectionPool() { | |||
SimpleJDBCConnectionPool pool = null; | |||
try { | |||
pool = new SimpleJDBCConnectionPool("com.mysql.jdbc.Driver", | |||
"jdbc:mysql://localhost:3306/sqlcontainer", "sqlcontainer", | |||
"sqlcontainer"); | |||
} catch (SQLException e) { | |||
getMainWindow().showNotification("Error connecting to database"); | |||
} | |||
return pool; | |||
} | |||
} |
@@ -0,0 +1,146 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Suite; | |||
import org.junit.runners.Suite.SuiteClasses; | |||
import com.vaadin.data.util.query.generator.DefaultSQLGenerator; | |||
import com.vaadin.data.util.query.generator.MSSQLGenerator; | |||
import com.vaadin.data.util.query.generator.OracleGenerator; | |||
import com.vaadin.data.util.query.generator.SQLGenerator; | |||
import com.vaadin.tests.server.container.sqlcontainer.connection.J2EEConnectionPoolTest; | |||
import com.vaadin.tests.server.container.sqlcontainer.connection.SimpleJDBCConnectionPoolTest; | |||
import com.vaadin.tests.server.container.sqlcontainer.filters.BetweenTest; | |||
import com.vaadin.tests.server.container.sqlcontainer.filters.LikeTest; | |||
import com.vaadin.tests.server.container.sqlcontainer.generator.SQLGeneratorsTest; | |||
import com.vaadin.tests.server.container.sqlcontainer.query.FreeformQueryTest; | |||
import com.vaadin.tests.server.container.sqlcontainer.query.QueryBuilderTest; | |||
import com.vaadin.tests.server.container.sqlcontainer.query.TableQueryTest; | |||
@RunWith(Suite.class) | |||
@SuiteClasses({ SimpleJDBCConnectionPoolTest.class, | |||
J2EEConnectionPoolTest.class, LikeTest.class, | |||
QueryBuilderTest.class, FreeformQueryTest.class, | |||
RowIdTest.class, SQLContainerTest.class, | |||
SQLContainerTableQueryTest.class, ColumnPropertyTest.class, | |||
TableQueryTest.class, SQLGeneratorsTest.class, UtilTest.class, | |||
TicketTests.class, BetweenTest.class, ReadOnlyRowIdTest.class}) | |||
public class AllTests { | |||
/* Set the DB used for testing here! */ | |||
public enum DB { | |||
HSQLDB, MYSQL, POSTGRESQL, MSSQL, ORACLE; | |||
} | |||
/* 0 = HSQLDB, 1 = MYSQL, 2 = POSTGRESQL, 3 = MSSQL, 4 = ORACLE */ | |||
public static final DB db = DB.HSQLDB; | |||
/* Auto-increment column offset (HSQLDB = 0, MYSQL = 1, POSTGRES = 1) */ | |||
public static int offset; | |||
/* Garbage table creation query (=three queries for oracle) */ | |||
public static String createGarbage; | |||
public static String createGarbageSecond; | |||
public static String createGarbageThird; | |||
/* DB Drivers, urls, usernames and passwords */ | |||
public static String dbDriver; | |||
public static String dbURL; | |||
public static String dbUser; | |||
public static String dbPwd; | |||
/* People -test table creation statement(s) */ | |||
public static String peopleFirst; | |||
public static String peopleSecond; | |||
public static String peopleThird; | |||
/* Versioned -test table createion statement(s) */ | |||
public static String[] versionStatements; | |||
/* SQL Generator used during the testing */ | |||
public static SQLGenerator sqlGen; | |||
/* Set DB-specific settings based on selected DB */ | |||
static { | |||
sqlGen = new DefaultSQLGenerator(); | |||
switch (db) { | |||
case HSQLDB: | |||
offset = 0; | |||
createGarbage = "create table garbage (id integer generated always as identity, type varchar(32), PRIMARY KEY(id))"; | |||
dbDriver = "org.hsqldb.jdbc.JDBCDriver"; | |||
dbURL = "jdbc:hsqldb:mem:sqlcontainer"; | |||
dbUser = "SA"; | |||
dbPwd = ""; | |||
peopleFirst = "create table people (id integer generated always as identity, name varchar(32), AGE INTEGER)"; | |||
peopleSecond = "alter table people add primary key (id)"; | |||
versionStatements = new String[] { | |||
"create table versioned (id integer generated always as identity, text varchar(255), version tinyint default 0)", | |||
"alter table versioned add primary key (id)" }; | |||
break; | |||
case MYSQL: | |||
offset = 1; | |||
createGarbage = "create table GARBAGE (ID integer auto_increment, type varchar(32), PRIMARY KEY(ID))"; | |||
dbDriver = "com.mysql.jdbc.Driver"; | |||
dbURL = "jdbc:mysql:///sqlcontainer"; | |||
dbUser = "sqlcontainer"; | |||
dbPwd = "sqlcontainer"; | |||
peopleFirst = "create table PEOPLE (ID integer auto_increment not null, NAME varchar(32), AGE INTEGER, primary key(ID))"; | |||
peopleSecond = null; | |||
versionStatements = new String[] { | |||
"create table VERSIONED (ID integer auto_increment not null, TEXT varchar(255), VERSION tinyint default 0, primary key(ID))", | |||
"CREATE TRIGGER upd_version BEFORE UPDATE ON VERSIONED" | |||
+ " FOR EACH ROW SET NEW.VERSION = @VERSION+1" }; | |||
break; | |||
case POSTGRESQL: | |||
offset = 1; | |||
createGarbage = "create table GARBAGE (\"ID\" serial PRIMARY KEY, \"TYPE\" varchar(32))"; | |||
dbDriver = "org.postgresql.Driver"; | |||
dbURL = "jdbc:postgresql://localhost:5432/test"; | |||
dbUser = "postgres"; | |||
dbPwd = "postgres"; | |||
peopleFirst = "create table PEOPLE (\"ID\" serial primary key, \"NAME\" VARCHAR(32), \"AGE\" INTEGER)"; | |||
peopleSecond = null; | |||
versionStatements = new String[] { | |||
"create table VERSIONED (\"ID\" serial primary key, \"TEXT\" VARCHAR(255), \"VERSION\" INTEGER DEFAULT 0)", | |||
"CREATE OR REPLACE FUNCTION zz_row_version() RETURNS TRIGGER AS $$" | |||
+ "BEGIN" | |||
+ " IF TG_OP = 'UPDATE'" | |||
+ " AND NEW.\"VERSION\" = old.\"VERSION\"" | |||
+ " AND ROW(NEW.*) IS DISTINCT FROM ROW (old.*)" | |||
+ " THEN" | |||
+ " NEW.\"VERSION\" := NEW.\"VERSION\" + 1;" | |||
+ " END IF;" + " RETURN NEW;" + "END;" | |||
+ "$$ LANGUAGE plpgsql;", | |||
"CREATE TRIGGER \"mytable_modify_dt_tr\" BEFORE UPDATE" | |||
+ " ON VERSIONED FOR EACH ROW" | |||
+ " EXECUTE PROCEDURE \"public\".\"zz_row_version\"();" }; | |||
break; | |||
case MSSQL: | |||
offset = 1; | |||
createGarbage = "create table GARBAGE (\"ID\" int identity(1,1) primary key, \"TYPE\" varchar(32))"; | |||
dbDriver = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; | |||
dbURL = "jdbc:sqlserver://localhost:1433;databaseName=tempdb;"; | |||
dbUser = "sa"; | |||
dbPwd = "sa"; | |||
peopleFirst = "create table PEOPLE (\"ID\" int identity(1,1) primary key, \"NAME\" VARCHAR(32), \"AGE\" INTEGER)"; | |||
peopleSecond = null; | |||
versionStatements = new String[] { "create table VERSIONED (\"ID\" int identity(1,1) primary key, \"TEXT\" VARCHAR(255), \"VERSION\" rowversion not null)" }; | |||
sqlGen = new MSSQLGenerator(); | |||
break; | |||
case ORACLE: | |||
offset = 1; | |||
createGarbage = "create table GARBAGE (\"ID\" integer primary key, \"TYPE\" varchar2(32))"; | |||
createGarbageSecond = "create sequence garbage_seq start with 1 increment by 1 nomaxvalue"; | |||
createGarbageThird = "create trigger garbage_trigger before insert on GARBAGE for each row begin select garbage_seq.nextval into :new.ID from dual; end;"; | |||
dbDriver = "oracle.jdbc.OracleDriver"; | |||
dbURL = "jdbc:oracle:thin:test/test@localhost:1521:XE"; | |||
dbUser = "test"; | |||
dbPwd = "test"; | |||
peopleFirst = "create table PEOPLE (\"ID\" integer primary key, \"NAME\" VARCHAR2(32), \"AGE\" INTEGER)"; | |||
peopleSecond = "create sequence people_seq start with 1 increment by 1 nomaxvalue"; | |||
peopleThird = "create trigger people_trigger before insert on PEOPLE for each row begin select people_seq.nextval into :new.ID from dual; end;"; | |||
versionStatements = new String[] { | |||
"create table VERSIONED (\"ID\" integer primary key, \"TEXT\" VARCHAR(255), \"VERSION\" INTEGER DEFAULT 0)", | |||
"create sequence versioned_seq start with 1 increment by 1 nomaxvalue", | |||
"create trigger versioned_trigger before insert on VERSIONED for each row begin select versioned_seq.nextval into :new.ID from dual; end;", | |||
"create sequence versioned_version start with 1 increment by 1 nomaxvalue", | |||
"create trigger versioned_version_trigger before insert or update on VERSIONED for each row begin select versioned_version.nextval into :new.VERSION from dual; end;" }; | |||
sqlGen = new OracleGenerator(); | |||
break; | |||
} | |||
} | |||
} |
@@ -0,0 +1,177 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import java.util.Arrays; | |||
import org.easymock.EasyMock; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.Property.ReadOnlyException; | |||
import com.vaadin.data.util.ColumnProperty; | |||
import com.vaadin.data.util.ColumnProperty.NotNullableException; | |||
import com.vaadin.data.util.RowId; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.SQLContainer; | |||
public class ColumnPropertyTest { | |||
@Test | |||
public void constructor_legalParameters_shouldSucceed() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
Assert.assertNotNull(cp); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void constructor_missingPropertyId_shouldFail() { | |||
new ColumnProperty(null, false, true, true, "Ville", String.class); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void constructor_missingType_shouldFail() { | |||
new ColumnProperty("NAME", false, true, true, "Ville", null); | |||
} | |||
@Test | |||
public void getValue_defaultValue_returnsVille() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
Assert.assertEquals("Ville", cp.getValue()); | |||
} | |||
/*- | |||
* TODO Removed test since currently the Vaadin test package structure | |||
* does not allow testing protected methods. When it has been fixed | |||
* then re-enable test. | |||
@Test | |||
public void setValue_readWriteNullable_returnsKalle() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
SQLContainer container = EasyMock.createMock(SQLContainer.class); | |||
RowItem owner = new RowItem(container, new RowId(new Object[] { 1 }), | |||
Arrays.asList(cp)); | |||
container.itemChangeNotification(owner); | |||
EasyMock.replay(container); | |||
cp.setValue("Kalle"); | |||
Assert.assertEquals("Kalle", cp.getValue()); | |||
EasyMock.verify(container); | |||
} | |||
*/ | |||
@Test(expected = ReadOnlyException.class) | |||
public void setValue_readOnlyNullable_shouldFail() { | |||
ColumnProperty cp = new ColumnProperty("NAME", true, true, true, | |||
"Ville", String.class); | |||
SQLContainer container = EasyMock.createMock(SQLContainer.class); | |||
new RowItem(container, new RowId(new Object[] { 1 }), Arrays.asList(cp)); | |||
EasyMock.replay(container); | |||
cp.setValue("Kalle"); | |||
EasyMock.verify(container); | |||
} | |||
/*- | |||
* TODO Removed test since currently the Vaadin test package structure | |||
* does not allow testing protected methods. When it has been fixed | |||
* then re-enable test. | |||
@Test | |||
public void setValue_readWriteNullable_nullShouldWork() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
SQLContainer container = EasyMock.createMock(SQLContainer.class); | |||
RowItem owner = new RowItem(container, new RowId(new Object[] { 1 }), | |||
Arrays.asList(cp)); | |||
container.itemChangeNotification(owner); | |||
EasyMock.replay(container); | |||
cp.setValue(null); | |||
Assert.assertNull(cp.getValue()); | |||
EasyMock.verify(container); | |||
} | |||
@Test(expected = NotNullableException.class) | |||
public void setValue_readWriteNotNullable_nullShouldFail() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, false, | |||
"Ville", String.class); | |||
SQLContainer container = EasyMock.createMock(SQLContainer.class); | |||
RowItem owner = new RowItem(container, new RowId(new Object[] { 1 }), | |||
Arrays.asList(cp)); | |||
container.itemChangeNotification(owner); | |||
EasyMock.replay(container); | |||
cp.setValue(null); | |||
Assert.assertNotNull(cp.getValue()); | |||
EasyMock.verify(container); | |||
} | |||
*/ | |||
@Test | |||
public void getType_normal_returnsStringClass() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
Assert.assertSame(String.class, cp.getType()); | |||
} | |||
@Test | |||
public void isReadOnly_readWriteNullable_returnsTrue() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
Assert.assertFalse(cp.isReadOnly()); | |||
} | |||
@Test | |||
public void isReadOnly_readOnlyNullable_returnsTrue() { | |||
ColumnProperty cp = new ColumnProperty("NAME", true, true, true, | |||
"Ville", String.class); | |||
Assert.assertTrue(cp.isReadOnly()); | |||
} | |||
@Test | |||
public void setReadOnly_readOnlyChangeAllowed_shouldSucceed() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
cp.setReadOnly(true); | |||
Assert.assertTrue(cp.isReadOnly()); | |||
} | |||
@Test | |||
public void setReadOnly_readOnlyChangeDisallowed_shouldFail() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, false, true, | |||
"Ville", String.class); | |||
cp.setReadOnly(true); | |||
Assert.assertFalse(cp.isReadOnly()); | |||
} | |||
@Test | |||
public void getPropertyId_normal_returnsNAME() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, false, true, | |||
"Ville", String.class); | |||
Assert.assertEquals("NAME", cp.getPropertyId()); | |||
} | |||
/*- | |||
* TODO Removed test since currently the Vaadin test package structure | |||
* does not allow testing protected methods. When it has been fixed | |||
* then re-enable test. | |||
@Test | |||
public void isModified_valueModified_returnsTrue() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, true, true, | |||
"Ville", String.class); | |||
SQLContainer container = EasyMock.createMock(SQLContainer.class); | |||
RowItem owner = new RowItem(container, new RowId(new Object[] { 1 }), | |||
Arrays.asList(cp)); | |||
container.itemChangeNotification(owner); | |||
EasyMock.replay(container); | |||
cp.setValue("Kalle"); | |||
Assert.assertEquals("Kalle", cp.getValue()); | |||
Assert.assertTrue(cp.isModified()); | |||
EasyMock.verify(container); | |||
} | |||
*/ | |||
@Test | |||
public void isModified_valueNotModified_returnsFalse() { | |||
ColumnProperty cp = new ColumnProperty("NAME", false, false, true, | |||
"Ville", String.class); | |||
Assert.assertFalse(cp.isModified()); | |||
} | |||
} |
@@ -0,0 +1,132 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import java.sql.Connection; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
import org.junit.Assert; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests.DB; | |||
public class DataGenerator { | |||
public static void addPeopleToDatabase(JDBCConnectionPool connectionPool) | |||
throws SQLException { | |||
Connection conn = connectionPool.reserveConnection(); | |||
Statement statement = conn.createStatement(); | |||
try { | |||
statement.execute("drop table PEOPLE"); | |||
if (AllTests.db == DB.ORACLE) { | |||
statement.execute("drop sequence people_seq"); | |||
} | |||
} catch (SQLException e) { | |||
// Will fail if table doesn't exist, which is OK. | |||
conn.rollback(); | |||
} | |||
statement.execute(AllTests.peopleFirst); | |||
if (AllTests.peopleSecond != null) { | |||
statement.execute(AllTests.peopleSecond); | |||
} | |||
if (AllTests.db == DB.ORACLE) { | |||
statement.execute(AllTests.peopleThird); | |||
} | |||
if (AllTests.db == DB.MSSQL) { | |||
statement.executeUpdate("insert into people values('Ville', '23')"); | |||
statement.executeUpdate("insert into people values('Kalle', '7')"); | |||
statement.executeUpdate("insert into people values('Pelle', '18')"); | |||
statement.executeUpdate("insert into people values('Börje', '64')"); | |||
} else { | |||
statement | |||
.executeUpdate("insert into people values(default, 'Ville', '23')"); | |||
statement | |||
.executeUpdate("insert into people values(default, 'Kalle', '7')"); | |||
statement | |||
.executeUpdate("insert into people values(default, 'Pelle', '18')"); | |||
statement | |||
.executeUpdate("insert into people values(default, 'Börje', '64')"); | |||
} | |||
statement.close(); | |||
statement = conn.createStatement(); | |||
ResultSet rs = statement.executeQuery("select * from PEOPLE"); | |||
Assert.assertTrue(rs.next()); | |||
statement.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
} | |||
public static void addFiveThousandPeople(JDBCConnectionPool connectionPool) | |||
throws SQLException { | |||
Connection conn = connectionPool.reserveConnection(); | |||
Statement statement = conn.createStatement(); | |||
for (int i = 4; i < 5000; i++) { | |||
if (AllTests.db == DB.MSSQL) { | |||
statement.executeUpdate("insert into people values('Person " | |||
+ i + "', '" + i % 99 + "')"); | |||
} else { | |||
statement | |||
.executeUpdate("insert into people values(default, 'Person " | |||
+ i + "', '" + i % 99 + "')"); | |||
} | |||
} | |||
statement.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
} | |||
public static void addVersionedData(JDBCConnectionPool connectionPool) | |||
throws SQLException { | |||
Connection conn = connectionPool.reserveConnection(); | |||
Statement statement = conn.createStatement(); | |||
try { | |||
statement.execute("DROP TABLE VERSIONED"); | |||
if (AllTests.db == DB.ORACLE) { | |||
statement.execute("drop sequence versioned_seq"); | |||
statement.execute("drop sequence versioned_version"); | |||
} | |||
} catch (SQLException e) { | |||
// Will fail if table doesn't exist, which is OK. | |||
conn.rollback(); | |||
} | |||
for (String stmtString : AllTests.versionStatements) { | |||
statement.execute(stmtString); | |||
} | |||
if (AllTests.db == DB.MSSQL) { | |||
statement | |||
.executeUpdate("insert into VERSIONED values('Junk', default)"); | |||
} else { | |||
statement | |||
.executeUpdate("insert into VERSIONED values(default, 'Junk', default)"); | |||
} | |||
statement.close(); | |||
statement = conn.createStatement(); | |||
ResultSet rs = statement.executeQuery("select * from VERSIONED"); | |||
Assert.assertTrue(rs.next()); | |||
statement.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
} | |||
public static void createGarbage(JDBCConnectionPool connectionPool) | |||
throws SQLException { | |||
Connection conn = connectionPool.reserveConnection(); | |||
Statement statement = conn.createStatement(); | |||
try { | |||
statement.execute("drop table GARBAGE"); | |||
if (AllTests.db == DB.ORACLE) { | |||
statement.execute("drop sequence garbage_seq"); | |||
} | |||
} catch (SQLException e) { | |||
// Will fail if table doesn't exist, which is OK. | |||
conn.rollback(); | |||
} | |||
statement.execute(AllTests.createGarbage); | |||
if (AllTests.db == DB.ORACLE) { | |||
statement.execute(AllTests.createGarbageSecond); | |||
statement.execute(AllTests.createGarbageThird); | |||
} | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import java.util.List; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
import com.vaadin.data.util.query.generator.filter.QueryBuilder; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests.DB; | |||
public class FreeformQueryUtil { | |||
public static StatementHelper getQueryWithFilters(List<Filter> filters, | |||
int offset, int limit) { | |||
StatementHelper sh = new StatementHelper(); | |||
if (AllTests.db == DB.MSSQL) { | |||
if (limit > 1) { | |||
offset++; | |||
limit--; | |||
} | |||
StringBuilder query = new StringBuilder(); | |||
query.append("SELECT * FROM (SELECT row_number() OVER ("); | |||
query.append("ORDER BY \"ID\" ASC"); | |||
query.append(") AS rownum, * FROM \"PEOPLE\""); | |||
if (!filters.isEmpty()) { | |||
query.append(QueryBuilder.getWhereStringForFilters( | |||
filters, sh)); | |||
} | |||
query.append(") AS a WHERE a.rownum BETWEEN ").append(offset) | |||
.append(" AND ").append(Integer.toString(offset + limit)); | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} else if (AllTests.db == DB.ORACLE) { | |||
if (limit > 1) { | |||
offset++; | |||
limit--; | |||
} | |||
StringBuilder query = new StringBuilder(); | |||
query.append("SELECT * FROM (SELECT x.*, ROWNUM AS " | |||
+ "\"rownum\" FROM (SELECT * FROM \"PEOPLE\""); | |||
if (!filters.isEmpty()) { | |||
query.append(QueryBuilder.getWhereStringForFilters( | |||
filters, sh)); | |||
} | |||
query.append(") x) WHERE \"rownum\" BETWEEN ? AND ?"); | |||
sh.addParameterValue(offset); | |||
sh.addParameterValue(offset + limit); | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} else { | |||
StringBuilder query = new StringBuilder("SELECT * FROM people"); | |||
if (!filters.isEmpty()) { | |||
query.append(QueryBuilder.getWhereStringForFilters( | |||
filters, sh)); | |||
} | |||
if (limit != 0 || offset != 0) { | |||
query.append(" LIMIT ? OFFSET ?"); | |||
sh.addParameterValue(limit); | |||
sh.addParameterValue(offset); | |||
} | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import junit.framework.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.ReadOnlyRowId; | |||
public class ReadOnlyRowIdTest { | |||
@Test | |||
public void getRowNum_shouldReturnRowNumGivenInConstructor() { | |||
int rowNum = 1337; | |||
ReadOnlyRowId rid = new ReadOnlyRowId(rowNum); | |||
Assert.assertEquals(rowNum, rid.getRowNum()); | |||
} | |||
@Test | |||
public void hashCode_shouldBeEqualToHashCodeOfRowNum() { | |||
int rowNum = 1337; | |||
ReadOnlyRowId rid = new ReadOnlyRowId(rowNum); | |||
Assert.assertEquals(Integer.valueOf(rowNum).hashCode(), rid.hashCode()); | |||
} | |||
@Test | |||
public void equals_compareWithNull_shouldBeFalse() { | |||
ReadOnlyRowId rid = new ReadOnlyRowId(1337); | |||
Assert.assertFalse(rid.equals(null)); | |||
} | |||
@Test | |||
public void equals_compareWithSameInstance_shouldBeTrue() { | |||
ReadOnlyRowId rid = new ReadOnlyRowId(1337); | |||
ReadOnlyRowId rid2 = rid; | |||
Assert.assertTrue(rid.equals(rid2)); | |||
} | |||
@Test | |||
public void equals_compareWithOtherType_shouldBeFalse() { | |||
ReadOnlyRowId rid = new ReadOnlyRowId(1337); | |||
Assert.assertFalse(rid.equals(new Object())); | |||
} | |||
@Test | |||
public void equals_compareWithOtherRowId_shouldBeFalse() { | |||
ReadOnlyRowId rid = new ReadOnlyRowId(1337); | |||
ReadOnlyRowId rid2 = new ReadOnlyRowId(42); | |||
Assert.assertFalse(rid.equals(rid2)); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.RowId; | |||
public class RowIdTest { | |||
@Test | |||
public void constructor_withArrayOfPrimaryKeyColumns_shouldSucceed() { | |||
RowId id = new RowId(new Object[] { "id", "name" }); | |||
Assert.assertArrayEquals(new Object[] { "id", "name" }, id.getId()); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void constructor_withNullParameter_shouldFail() { | |||
new RowId(null); | |||
} | |||
@Test | |||
public void hashCode_samePrimaryKeys_sameResult() { | |||
RowId id = new RowId(new Object[] { "id", "name" }); | |||
RowId id2 = new RowId(new Object[] { "id", "name" }); | |||
Assert.assertEquals(id.hashCode(), id2.hashCode()); | |||
} | |||
@Test | |||
public void hashCode_differentPrimaryKeys_differentResult() { | |||
RowId id = new RowId(new Object[] { "id", "name" }); | |||
RowId id2 = new RowId(new Object[] { "id" }); | |||
Assert.assertFalse(id.hashCode() == id2.hashCode()); | |||
} | |||
@Test | |||
public void equals_samePrimaryKeys_returnsTrue() { | |||
RowId id = new RowId(new Object[] { "id", "name" }); | |||
RowId id2 = new RowId(new Object[] { "id", "name" }); | |||
Assert.assertEquals(id, id2); | |||
} | |||
@Test | |||
public void equals_differentPrimaryKeys_returnsFalse() { | |||
RowId id = new RowId(new Object[] { "id", "name" }); | |||
RowId id2 = new RowId(new Object[] { "id" }); | |||
Assert.assertFalse(id.equals(id2.hashCode())); | |||
} | |||
@Test | |||
public void equals_differentDataType_returnsFalse() { | |||
RowId id = new RowId(new Object[] { "id", "name" }); | |||
Assert.assertFalse(id.equals("Tudiluu")); | |||
Assert.assertFalse(id.equals(new Integer(1337))); | |||
} | |||
} |
@@ -0,0 +1,157 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import java.math.BigDecimal; | |||
import java.sql.SQLException; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.easymock.EasyMock; | |||
import org.easymock.IAnswer; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.SQLContainer; | |||
import com.vaadin.data.util.connection.SimpleJDBCConnectionPool; | |||
import com.vaadin.data.util.filter.Compare.Equal; | |||
import com.vaadin.data.util.query.FreeformQuery; | |||
import com.vaadin.data.util.query.FreeformStatementDelegate; | |||
import com.vaadin.data.util.query.TableQuery; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
import com.vaadin.data.util.query.generator.filter.QueryBuilder; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests.DB; | |||
import com.vaadin.ui.Table; | |||
import com.vaadin.ui.Window; | |||
public class TicketTests { | |||
private SimpleJDBCConnectionPool connectionPool; | |||
@Before | |||
public void setUp() throws SQLException { | |||
connectionPool = new SimpleJDBCConnectionPool(AllTests.dbDriver, | |||
AllTests.dbURL, AllTests.dbUser, AllTests.dbPwd, 2, 2); | |||
DataGenerator.addPeopleToDatabase(connectionPool); | |||
} | |||
@Test | |||
public void ticket5867_throwsIllegalState_transactionAlreadyActive() | |||
throws SQLException { | |||
SQLContainer container = new SQLContainer(new FreeformQuery( | |||
"SELECT * FROM people", Arrays.asList("ID"), connectionPool)); | |||
Table table = new Table(); | |||
Window w = new Window(); | |||
w.addComponent(table); | |||
table.setContainerDataSource(container); | |||
} | |||
@SuppressWarnings("unchecked") | |||
@Test | |||
public void ticket6136_freeform_ageIs18() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformStatementDelegate delegate = EasyMock | |||
.createMock(FreeformStatementDelegate.class); | |||
final ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
delegate.setFilters(null); | |||
EasyMock.expectLastCall().anyTimes(); | |||
delegate.setOrderBy(EasyMock.isA(List.class)); | |||
EasyMock.expectLastCall().anyTimes(); | |||
delegate.setOrderBy(null); | |||
EasyMock.expectLastCall().anyTimes(); | |||
delegate.setFilters(EasyMock.isA(List.class)); | |||
EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() { | |||
public Object answer() throws Throwable { | |||
List<Filter> orders = (List<Filter>) EasyMock | |||
.getCurrentArguments()[0]; | |||
filters.clear(); | |||
filters.addAll(orders); | |||
return null; | |||
} | |||
}).anyTimes(); | |||
EasyMock.expect( | |||
delegate.getQueryStatement(EasyMock.anyInt(), EasyMock.anyInt())) | |||
.andAnswer(new IAnswer<StatementHelper>() { | |||
public StatementHelper answer() throws Throwable { | |||
Object[] args = EasyMock.getCurrentArguments(); | |||
int offset = (Integer) (args[0]); | |||
int limit = (Integer) (args[1]); | |||
return FreeformQueryUtil.getQueryWithFilters(filters, | |||
offset, limit); | |||
} | |||
}).anyTimes(); | |||
EasyMock.expect(delegate.getCountStatement()) | |||
.andAnswer(new IAnswer<StatementHelper>() { | |||
@SuppressWarnings("deprecation") | |||
public StatementHelper answer() throws Throwable { | |||
StatementHelper sh = new StatementHelper(); | |||
StringBuffer query = new StringBuffer( | |||
"SELECT COUNT(*) FROM people"); | |||
if (!filters.isEmpty()) { | |||
query.append(QueryBuilder.getWhereStringForFilters( | |||
filters, sh)); | |||
} | |||
sh.setQueryString(query.toString()); | |||
return sh; | |||
} | |||
}).anyTimes(); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
SQLContainer container = new SQLContainer(query); | |||
// Ville, Kalle, Pelle, Börje | |||
Assert.assertEquals(4, container.size()); | |||
Assert.assertEquals("Börje", | |||
container.getContainerProperty(container.lastItemId(), "NAME") | |||
.getValue()); | |||
container.addContainerFilter(new Equal("AGE", 18)); | |||
// Pelle | |||
Assert.assertEquals(1, container.size()); | |||
Assert.assertEquals("Pelle", | |||
container.getContainerProperty(container.firstItemId(), "NAME") | |||
.getValue()); | |||
if (AllTests.db == DB.ORACLE) { | |||
Assert.assertEquals(new BigDecimal(18), container | |||
.getContainerProperty(container.firstItemId(), "AGE") | |||
.getValue()); | |||
} else { | |||
Assert.assertEquals( | |||
18, | |||
container.getContainerProperty(container.firstItemId(), | |||
"AGE").getValue()); | |||
} | |||
EasyMock.verify(delegate); | |||
} | |||
@Test | |||
public void ticket6136_table_ageIs18() throws SQLException { | |||
TableQuery query = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
SQLContainer container = new SQLContainer(query); | |||
// Ville, Kalle, Pelle, Börje | |||
Assert.assertEquals(4, container.size()); | |||
container.addContainerFilter(new Equal("AGE", 18)); | |||
// Pelle | |||
Assert.assertEquals(1, container.size()); | |||
Assert.assertEquals("Pelle", | |||
container.getContainerProperty(container.firstItemId(), "NAME") | |||
.getValue()); | |||
if (AllTests.db == DB.ORACLE) { | |||
Assert.assertEquals(new BigDecimal(18), container | |||
.getContainerProperty(container.firstItemId(), "AGE") | |||
.getValue()); | |||
} else { | |||
Assert.assertEquals( | |||
18, | |||
container.getContainerProperty(container.firstItemId(), | |||
"AGE").getValue()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer; | |||
import junit.framework.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.SQLUtil; | |||
public class UtilTest { | |||
@Test | |||
public void escapeSQL_noQuotes_returnsSameString() { | |||
Assert.assertEquals("asdf", SQLUtil.escapeSQL("asdf")); | |||
} | |||
@Test | |||
public void escapeSQL_singleQuotes_returnsEscapedString() { | |||
Assert.assertEquals("O''Brien", SQLUtil.escapeSQL("O'Brien")); | |||
} | |||
@Test | |||
public void escapeSQL_severalQuotes_returnsEscapedString() { | |||
Assert.assertEquals("asdf''ghjk''qwerty", | |||
SQLUtil.escapeSQL("asdf'ghjk'qwerty")); | |||
} | |||
@Test | |||
public void escapeSQL_doubleQuotes_returnsEscapedString() { | |||
Assert.assertEquals("asdf\\\"foo", SQLUtil.escapeSQL("asdf\"foo")); | |||
} | |||
@Test | |||
public void escapeSQL_multipleDoubleQuotes_returnsEscapedString() { | |||
Assert.assertEquals("asdf\\\"foo\\\"bar", | |||
SQLUtil.escapeSQL("asdf\"foo\"bar")); | |||
} | |||
@Test | |||
public void escapeSQL_backslashes_returnsEscapedString() { | |||
Assert.assertEquals("foo\\\\nbar\\\\r", SQLUtil.escapeSQL("foo\\nbar\\r")); | |||
} | |||
@Test | |||
public void escapeSQL_x00_removesX00() { | |||
Assert.assertEquals("foobar", SQLUtil.escapeSQL("foo\\x00bar")); | |||
} | |||
@Test | |||
public void escapeSQL_x1a_removesX1a() { | |||
Assert.assertEquals("foobar", SQLUtil.escapeSQL("foo\\x1abar")); | |||
} | |||
} |
@@ -0,0 +1,101 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.connection; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
import javax.naming.Context; | |||
import javax.naming.NamingException; | |||
import javax.sql.DataSource; | |||
import junit.framework.Assert; | |||
import org.easymock.EasyMock; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.connection.J2EEConnectionPool; | |||
public class J2EEConnectionPoolTest { | |||
@Test | |||
public void reserveConnection_dataSourceSpecified_shouldReturnValidConnection() | |||
throws SQLException { | |||
Connection connection = EasyMock.createMock(Connection.class); | |||
connection.setAutoCommit(false); | |||
EasyMock.expectLastCall(); | |||
DataSource ds = EasyMock.createMock(DataSource.class); | |||
ds.getConnection(); | |||
EasyMock.expectLastCall().andReturn(connection); | |||
EasyMock.replay(connection, ds); | |||
J2EEConnectionPool pool = new J2EEConnectionPool(ds); | |||
Connection c = pool.reserveConnection(); | |||
Assert.assertEquals(connection, c); | |||
EasyMock.verify(connection, ds); | |||
} | |||
@Test | |||
public void releaseConnection_shouldCloseConnection() throws SQLException { | |||
Connection connection = EasyMock.createMock(Connection.class); | |||
connection.setAutoCommit(false); | |||
EasyMock.expectLastCall(); | |||
connection.close(); | |||
EasyMock.expectLastCall(); | |||
DataSource ds = EasyMock.createMock(DataSource.class); | |||
ds.getConnection(); | |||
EasyMock.expectLastCall().andReturn(connection); | |||
EasyMock.replay(connection, ds); | |||
J2EEConnectionPool pool = new J2EEConnectionPool(ds); | |||
Connection c = pool.reserveConnection(); | |||
Assert.assertEquals(connection, c); | |||
pool.releaseConnection(c); | |||
EasyMock.verify(connection, ds); | |||
} | |||
@Test | |||
public void reserveConnection_dataSourceLookedUp_shouldReturnValidConnection() | |||
throws SQLException, NamingException { | |||
Connection connection = EasyMock.createMock(Connection.class); | |||
connection.setAutoCommit(false); | |||
EasyMock.expectLastCall(); | |||
connection.close(); | |||
EasyMock.expectLastCall(); | |||
DataSource ds = EasyMock.createMock(DataSource.class); | |||
ds.getConnection(); | |||
EasyMock.expectLastCall().andReturn(connection); | |||
System.setProperty("java.naming.factory.initial", | |||
"com.vaadin.tests.server.container.sqlcontainer.connection.MockInitialContextFactory"); | |||
Context context = EasyMock.createMock(Context.class); | |||
context.lookup("testDataSource"); | |||
EasyMock.expectLastCall().andReturn(ds); | |||
MockInitialContextFactory.setMockContext(context); | |||
EasyMock.replay(context, connection, ds); | |||
J2EEConnectionPool pool = new J2EEConnectionPool("testDataSource"); | |||
Connection c = pool.reserveConnection(); | |||
Assert.assertEquals(connection, c); | |||
pool.releaseConnection(c); | |||
EasyMock.verify(context, connection, ds); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void reserveConnection_nonExistantDataSourceLookedUp_shouldFail() | |||
throws SQLException, NamingException { | |||
System.setProperty("java.naming.factory.initial", | |||
"com.vaadin.addon.sqlcontainer.connection.MockInitialContextFactory"); | |||
Context context = EasyMock.createMock(Context.class); | |||
context.lookup("foo"); | |||
EasyMock.expectLastCall().andThrow(new NamingException("fail")); | |||
MockInitialContextFactory.setMockContext(context); | |||
EasyMock.replay(context); | |||
J2EEConnectionPool pool = new J2EEConnectionPool("foo"); | |||
Connection c = pool.reserveConnection(); | |||
EasyMock.verify(context); | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.connection; | |||
import javax.naming.Context; | |||
import javax.naming.NamingException; | |||
import javax.naming.spi.InitialContextFactory; | |||
/** | |||
* Provides a JNDI initial context factory for the MockContext. | |||
*/ | |||
public class MockInitialContextFactory implements InitialContextFactory { | |||
private static Context mockCtx = null; | |||
public static void setMockContext(Context ctx) { | |||
mockCtx = ctx; | |||
} | |||
public Context getInitialContext(java.util.Hashtable<?, ?> environment) | |||
throws NamingException { | |||
if (mockCtx == null) { | |||
throw new IllegalStateException("mock context was not set."); | |||
} | |||
return mockCtx; | |||
} | |||
} |
@@ -0,0 +1,172 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.connection; | |||
import java.sql.Connection; | |||
import java.sql.SQLException; | |||
import junit.framework.Assert; | |||
import org.easymock.EasyMock; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.connection.SimpleJDBCConnectionPool; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests; | |||
public class SimpleJDBCConnectionPoolTest { | |||
private JDBCConnectionPool connectionPool; | |||
@Before | |||
public void setUp() throws SQLException { | |||
connectionPool = new SimpleJDBCConnectionPool(AllTests.dbDriver, | |||
AllTests.dbURL, AllTests.dbUser, AllTests.dbPwd, 2, 2); | |||
} | |||
@Test | |||
public void reserveConnection_reserveNewConnection_returnsConnection() | |||
throws SQLException { | |||
Connection conn = connectionPool.reserveConnection(); | |||
Assert.assertNotNull(conn); | |||
} | |||
@Test | |||
public void releaseConnection_releaseUnused_shouldNotThrowException() | |||
throws SQLException { | |||
Connection conn = connectionPool.reserveConnection(); | |||
connectionPool.releaseConnection(conn); | |||
Assert.assertFalse(conn.isClosed()); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void reserveConnection_noConnectionsLeft_shouldFail() | |||
throws SQLException { | |||
try { | |||
connectionPool.reserveConnection(); | |||
connectionPool.reserveConnection(); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
Assert.fail("Exception before all connections used! " | |||
+ e.getMessage()); | |||
} | |||
connectionPool.reserveConnection(); | |||
Assert.fail("Reserving connection didn't fail even though no connections are available!"); | |||
} | |||
@Test | |||
public void reserveConnection_oneConnectionLeft_returnsConnection() | |||
throws SQLException { | |||
try { | |||
connectionPool.reserveConnection(); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
Assert.fail("Exception before all connections used! " | |||
+ e.getMessage()); | |||
} | |||
Connection conn = connectionPool.reserveConnection(); | |||
Assert.assertNotNull(conn); | |||
} | |||
@Test | |||
public void reserveConnection_oneConnectionJustReleased_returnsConnection() | |||
throws SQLException { | |||
Connection conn2 = null; | |||
try { | |||
connectionPool.reserveConnection(); | |||
conn2 = connectionPool.reserveConnection(); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
Assert.fail("Exception before all connections used! " | |||
+ e.getMessage()); | |||
} | |||
connectionPool.releaseConnection(conn2); | |||
connectionPool.reserveConnection(); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construct_allParametersNull_shouldFail() throws SQLException { | |||
SimpleJDBCConnectionPool cp = new SimpleJDBCConnectionPool(null, null, | |||
null, null); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construct_onlyDriverNameGiven_shouldFail() throws SQLException { | |||
SimpleJDBCConnectionPool cp = new SimpleJDBCConnectionPool( | |||
AllTests.dbDriver, null, null, null); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construct_onlyDriverNameAndUrlGiven_shouldFail() | |||
throws SQLException { | |||
SimpleJDBCConnectionPool cp = new SimpleJDBCConnectionPool( | |||
AllTests.dbDriver, AllTests.dbURL, null, null); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construct_onlyDriverNameAndUrlAndUserGiven_shouldFail() | |||
throws SQLException { | |||
SimpleJDBCConnectionPool cp = new SimpleJDBCConnectionPool( | |||
AllTests.dbDriver, AllTests.dbURL, AllTests.dbUser, null); | |||
} | |||
@Test(expected = RuntimeException.class) | |||
public void construct_nonExistingDriver_shouldFail() throws SQLException { | |||
SimpleJDBCConnectionPool cp = new SimpleJDBCConnectionPool("foo", | |||
AllTests.dbURL, AllTests.dbUser, AllTests.dbPwd); | |||
} | |||
@Test | |||
public void reserveConnection_newConnectionOpened_shouldSucceed() | |||
throws SQLException { | |||
connectionPool = new SimpleJDBCConnectionPool(AllTests.dbDriver, | |||
AllTests.dbURL, AllTests.dbUser, AllTests.dbPwd, 0, 2); | |||
Connection c = connectionPool.reserveConnection(); | |||
Assert.assertNotNull(c); | |||
} | |||
@Test | |||
public void releaseConnection_nullConnection_shouldDoNothing() { | |||
connectionPool.releaseConnection(null); | |||
} | |||
@Test | |||
public void releaseConnection_failingRollback_shouldCallClose() | |||
throws SQLException { | |||
Connection c = EasyMock.createMock(Connection.class); | |||
c.getAutoCommit(); | |||
EasyMock.expectLastCall().andReturn(false); | |||
c.rollback(); | |||
EasyMock.expectLastCall().andThrow(new SQLException("Rollback failed")); | |||
c.close(); | |||
EasyMock.expectLastCall().atLeastOnce(); | |||
EasyMock.replay(c); | |||
// make sure the connection pool is initialized | |||
connectionPool.reserveConnection(); | |||
connectionPool.releaseConnection(c); | |||
EasyMock.verify(c); | |||
} | |||
@Test | |||
public void destroy_shouldCloseAllConnections() throws SQLException { | |||
Connection c1 = connectionPool.reserveConnection(); | |||
Connection c2 = connectionPool.reserveConnection(); | |||
connectionPool.destroy(); | |||
Assert.assertTrue(c1.isClosed()); | |||
Assert.assertTrue(c2.isClosed()); | |||
} | |||
@Test | |||
public void destroy_shouldCloseAllConnections2() throws SQLException { | |||
Connection c1 = connectionPool.reserveConnection(); | |||
Connection c2 = connectionPool.reserveConnection(); | |||
connectionPool.releaseConnection(c1); | |||
connectionPool.releaseConnection(c2); | |||
connectionPool.destroy(); | |||
Assert.assertTrue(c1.isClosed()); | |||
Assert.assertTrue(c2.isClosed()); | |||
} | |||
} |
@@ -0,0 +1,122 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.filters; | |||
import junit.framework.Assert; | |||
import org.easymock.EasyMock; | |||
import org.junit.Test; | |||
import com.vaadin.data.Item; | |||
import com.vaadin.data.Property; | |||
import com.vaadin.data.util.filter.Between; | |||
public class BetweenTest { | |||
private Item itemWithPropertyValue(Object propertyId, Object value) { | |||
Property property = EasyMock.createMock(Property.class); | |||
property.getValue(); | |||
EasyMock.expectLastCall().andReturn(value).anyTimes(); | |||
EasyMock.replay(property); | |||
Item item = EasyMock.createMock(Item.class); | |||
item.getItemProperty(propertyId); | |||
EasyMock.expectLastCall().andReturn(property).anyTimes(); | |||
EasyMock.replay(item); | |||
return item; | |||
} | |||
@Test | |||
public void passesFilter_valueIsInRange_shouldBeTrue() { | |||
Item item = itemWithPropertyValue("foo", 15); | |||
Between between = new Between("foo", 1, 30); | |||
Assert.assertTrue(between.passesFilter("foo", item)); | |||
} | |||
@Test | |||
public void passesFilter_valueIsOutOfRange_shouldBeFalse() { | |||
Item item = itemWithPropertyValue("foo", 15); | |||
Between between = new Between("foo", 0, 2); | |||
Assert.assertFalse(between.passesFilter("foo", item)); | |||
} | |||
@Test | |||
public void passesFilter_valueNotComparable_shouldBeFalse() { | |||
Item item = itemWithPropertyValue("foo", new Object()); | |||
Between between = new Between("foo", 0, 2); | |||
Assert.assertFalse(between.passesFilter("foo", item)); | |||
} | |||
@Test | |||
public void appliesToProperty_differentProperties_shoudlBeFalse() { | |||
Between between = new Between("foo", 0, 2); | |||
Assert.assertFalse(between.appliesToProperty("bar")); | |||
} | |||
@Test | |||
public void appliesToProperty_sameProperties_shouldBeTrue() { | |||
Between between = new Between("foo", 0, 2); | |||
Assert.assertTrue(between.appliesToProperty("foo")); | |||
} | |||
@Test | |||
public void hashCode_equalInstances_shouldBeEqual() { | |||
Between b1 = new Between("foo", 0, 2); | |||
Between b2 = new Between("foo", 0, 2); | |||
Assert.assertEquals(b1.hashCode(), b2.hashCode()); | |||
} | |||
@Test | |||
public void equals_differentObjects_shouldBeFalse() { | |||
Between b1 = new Between("foo", 0, 2); | |||
Object obj = new Object(); | |||
Assert.assertFalse(b1.equals(obj)); | |||
} | |||
@Test | |||
public void equals_sameInstance_shouldBeTrue() { | |||
Between b1 = new Between("foo", 0, 2); | |||
Between b2 = b1; | |||
Assert.assertTrue(b1.equals(b2)); | |||
} | |||
@Test | |||
public void equals_equalInstances_shouldBeTrue() { | |||
Between b1 = new Between("foo", 0, 2); | |||
Between b2 = new Between("foo", 0, 2); | |||
Assert.assertTrue(b1.equals(b2)); | |||
} | |||
@Test | |||
public void equals_equalInstances2_shouldBeTrue() { | |||
Between b1 = new Between(null, null, null); | |||
Between b2 = new Between(null, null, null); | |||
Assert.assertTrue(b1.equals(b2)); | |||
} | |||
@Test | |||
public void equals_secondValueDiffers_shouldBeFalse() { | |||
Between b1 = new Between("foo", 0, 1); | |||
Between b2 = new Between("foo", 0, 2); | |||
Assert.assertFalse(b1.equals(b2)); | |||
} | |||
@Test | |||
public void equals_firstAndSecondValueDiffers_shouldBeFalse() { | |||
Between b1 = new Between("foo", 0, null); | |||
Between b2 = new Between("foo", 1, 2); | |||
Assert.assertFalse(b1.equals(b2)); | |||
} | |||
@Test | |||
public void equals_propertyAndFirstAndSecondValueDiffers_shouldBeFalse() { | |||
Between b1 = new Between("foo", null, 1); | |||
Between b2 = new Between("bar", 1, 2); | |||
Assert.assertFalse(b1.equals(b2)); | |||
} | |||
@Test | |||
public void equals_propertiesDiffer_shouldBeFalse() { | |||
Between b1 = new Between(null, 0, 1); | |||
Between b2 = new Between("bar", 0, 1); | |||
Assert.assertFalse(b1.equals(b2)); | |||
} | |||
} |
@@ -0,0 +1,229 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.filters; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.data.Item; | |||
import com.vaadin.data.util.ObjectProperty; | |||
import com.vaadin.data.util.PropertysetItem; | |||
import com.vaadin.data.util.filter.Like; | |||
public class LikeTest { | |||
@Test | |||
public void passesFilter_valueIsNotStringType_shouldFail() { | |||
Like like = new Like("test", "%foo%"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<Integer>(5)); | |||
Assert.assertFalse(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_containsLikeQueryOnStringContainingValue_shouldSucceed() { | |||
Like like = new Like("test", "%foo%"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("asdfooghij")); | |||
Assert.assertTrue(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_containsLikeQueryOnStringContainingValueCaseInsensitive_shouldSucceed() { | |||
Like like = new Like("test", "%foo%"); | |||
like.setCaseSensitive(false); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("asdfOOghij")); | |||
Assert.assertTrue(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_containsLikeQueryOnStringContainingValueConstructedCaseInsensitive_shouldSucceed() { | |||
Like like = new Like("test", "%foo%", false); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("asdfOOghij")); | |||
Assert.assertTrue(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_containsLikeQueryOnStringNotContainingValue_shouldFail() { | |||
Like like = new Like("test", "%foo%"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("asdbarghij")); | |||
Assert.assertFalse(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_containsLikeQueryOnStringExactlyEqualToValue_shouldSucceed() { | |||
Like like = new Like("test", "%foo%"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("foo")); | |||
Assert.assertTrue(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_containsLikeQueryOnStringEqualToValueMinusOneCharAtTheEnd_shouldFail() { | |||
Like like = new Like("test", "%foo%"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("fo")); | |||
Assert.assertFalse(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_beginsWithLikeQueryOnStringBeginningWithValue_shouldSucceed() { | |||
Like like = new Like("test", "foo%"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("foobar")); | |||
Assert.assertTrue(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_beginsWithLikeQueryOnStringNotBeginningWithValue_shouldFail() { | |||
Like like = new Like("test", "foo%"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("barfoo")); | |||
Assert.assertFalse(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_endsWithLikeQueryOnStringEndingWithValue_shouldSucceed() { | |||
Like like = new Like("test", "%foo"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("barfoo")); | |||
Assert.assertTrue(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_endsWithLikeQueryOnStringNotEndingWithValue_shouldFail() { | |||
Like like = new Like("test", "%foo"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("foobar")); | |||
Assert.assertFalse(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void passesFilter_startsWithAndEndsWithOnMatchingValue_shouldSucceed() { | |||
Like like = new Like("test", "foo%bar"); | |||
Item item = new PropertysetItem(); | |||
item.addItemProperty("test", new ObjectProperty<String>("fooASDFbar")); | |||
Assert.assertTrue(like.passesFilter("id", item)); | |||
} | |||
@Test | |||
public void appliesToProperty_valueIsProperty_shouldBeTrue() { | |||
Like like = new Like("test", "%foo"); | |||
Assert.assertTrue(like.appliesToProperty("test")); | |||
} | |||
@Test | |||
public void appliesToProperty_valueIsNotProperty_shouldBeFalse() { | |||
Like like = new Like("test", "%foo"); | |||
Assert.assertFalse(like.appliesToProperty("bar")); | |||
} | |||
@Test | |||
public void equals_sameInstances_shouldBeTrue() { | |||
Like like1 = new Like("test", "%foo"); | |||
Like like2 = like1; | |||
Assert.assertTrue(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_twoEqualInstances_shouldBeTrue() { | |||
Like like1 = new Like("test", "foo"); | |||
Like like2 = new Like("test", "foo"); | |||
Assert.assertTrue(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_differentValues_shouldBeFalse() { | |||
Like like1 = new Like("test", "foo"); | |||
Like like2 = new Like("test", "bar"); | |||
Assert.assertFalse(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_differentProperties_shouldBeFalse() { | |||
Like like1 = new Like("foo", "test"); | |||
Like like2 = new Like("bar", "test"); | |||
Assert.assertFalse(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_differentPropertiesAndValues_shouldBeFalse() { | |||
Like like1 = new Like("foo", "bar"); | |||
Like like2 = new Like("baz", "zomg"); | |||
Assert.assertFalse(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_differentClasses_shouldBeFalse() { | |||
Like like1 = new Like("foo", "bar"); | |||
Object obj = new Object(); | |||
Assert.assertFalse(like1.equals(obj)); | |||
} | |||
@Test | |||
public void equals_bothHaveNullProperties_shouldBeTrue() { | |||
Like like1 = new Like(null, "foo"); | |||
Like like2 = new Like(null, "foo"); | |||
Assert.assertTrue(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_bothHaveNullValues_shouldBeTrue() { | |||
Like like1 = new Like("foo", null); | |||
Like like2 = new Like("foo", null); | |||
Assert.assertTrue(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_onePropertyIsNull_shouldBeFalse() { | |||
Like like1 = new Like(null, "bar"); | |||
Like like2 = new Like("foo", "baz"); | |||
Assert.assertFalse(like1.equals(like2)); | |||
} | |||
@Test | |||
public void equals_oneValueIsNull_shouldBeFalse() { | |||
Like like1 = new Like("foo", null); | |||
Like like2 = new Like("baz", "bar"); | |||
Assert.assertFalse(like1.equals(like2)); | |||
} | |||
@Test | |||
public void hashCode_equalInstances_shouldBeEqual() { | |||
Like like1 = new Like("test", "foo"); | |||
Like like2 = new Like("test", "foo"); | |||
Assert.assertEquals(like1.hashCode(), like2.hashCode()); | |||
} | |||
@Test | |||
public void hashCode_differentPropertiesAndValues_shouldNotEqual() { | |||
Like like1 = new Like("foo", "bar"); | |||
Like like2 = new Like("baz", "zomg"); | |||
Assert.assertTrue(like1.hashCode() != like2.hashCode()); | |||
} | |||
} |
@@ -0,0 +1,241 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.generator; | |||
import java.sql.SQLException; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.junit.After; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.SQLContainer; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.connection.SimpleJDBCConnectionPool; | |||
import com.vaadin.data.util.filter.Like; | |||
import com.vaadin.data.util.filter.Or; | |||
import com.vaadin.data.util.query.OrderBy; | |||
import com.vaadin.data.util.query.TableQuery; | |||
import com.vaadin.data.util.query.generator.DefaultSQLGenerator; | |||
import com.vaadin.data.util.query.generator.MSSQLGenerator; | |||
import com.vaadin.data.util.query.generator.OracleGenerator; | |||
import com.vaadin.data.util.query.generator.SQLGenerator; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests; | |||
import com.vaadin.tests.server.container.sqlcontainer.DataGenerator; | |||
public class SQLGeneratorsTest { | |||
private JDBCConnectionPool connectionPool; | |||
@Before | |||
public void setUp() throws SQLException { | |||
try { | |||
connectionPool = new SimpleJDBCConnectionPool(AllTests.dbDriver, | |||
AllTests.dbURL, AllTests.dbUser, AllTests.dbPwd, 2, 2); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
Assert.fail(e.getMessage()); | |||
} | |||
DataGenerator.addPeopleToDatabase(connectionPool); | |||
} | |||
@After | |||
public void tearDown() { | |||
if (connectionPool != null) { | |||
connectionPool.destroy(); | |||
} | |||
} | |||
@Test | |||
public void generateSelectQuery_basicQuery_shouldSucceed() { | |||
SQLGenerator sg = new DefaultSQLGenerator(); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", null, null, 0, 0, | |||
null); | |||
Assert.assertEquals(sh.getQueryString(), "SELECT * FROM TABLE"); | |||
} | |||
@Test | |||
public void generateSelectQuery_pagingAndColumnsSet_shouldSucceed() { | |||
SQLGenerator sg = new DefaultSQLGenerator(); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", null, null, 4, 8, | |||
"COL1, COL2, COL3"); | |||
Assert.assertEquals(sh.getQueryString(), | |||
"SELECT COL1, COL2, COL3 FROM TABLE LIMIT 8 OFFSET 4"); | |||
} | |||
/** | |||
* Note: Only tests one kind of filter and ordering. | |||
*/ | |||
@Test | |||
public void generateSelectQuery_filtersAndOrderingSet_shouldSucceed() { | |||
SQLGenerator sg = new DefaultSQLGenerator(); | |||
List<com.vaadin.data.Container.Filter> f = new ArrayList<Filter>(); | |||
f.add(new Like("name", "%lle")); | |||
List<OrderBy> ob = Arrays.asList(new OrderBy("name", true)); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", f, ob, 0, 0, null); | |||
Assert.assertEquals(sh.getQueryString(), | |||
"SELECT * FROM TABLE WHERE \"name\" LIKE ? ORDER BY \"name\" ASC"); | |||
} | |||
@Test | |||
public void generateSelectQuery_filtersAndOrderingSet_exclusiveFilteringMode_shouldSucceed() { | |||
SQLGenerator sg = new DefaultSQLGenerator(); | |||
List<Filter> f = new ArrayList<Filter>(); | |||
f.add(new Or(new Like("name", "%lle"), new Like("name", "vi%"))); | |||
List<OrderBy> ob = Arrays.asList(new OrderBy("name", true)); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", f, ob, 0, 0, null); | |||
// TODO | |||
Assert.assertEquals(sh.getQueryString(), | |||
"SELECT * FROM TABLE WHERE (\"name\" LIKE ? " | |||
+ "OR \"name\" LIKE ?) ORDER BY \"name\" ASC"); | |||
} | |||
@Test | |||
public void generateDeleteQuery_basicQuery_shouldSucceed() | |||
throws SQLException { | |||
/* | |||
* No need to run this for Oracle/MSSQL generators since the | |||
* DefaultSQLGenerator method would be called anyway. | |||
*/ | |||
if (AllTests.sqlGen instanceof MSSQLGenerator | |||
|| AllTests.sqlGen instanceof OracleGenerator) { | |||
return; | |||
} | |||
SQLGenerator sg = AllTests.sqlGen; | |||
TableQuery query = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
SQLContainer container = new SQLContainer(query); | |||
StatementHelper sh = sg.generateDeleteQuery( | |||
"people", | |||
query.getPrimaryKeyColumns(), | |||
null, | |||
(RowItem) container.getItem(container.getItemIds().iterator() | |||
.next())); | |||
Assert.assertEquals("DELETE FROM people WHERE \"ID\" = ?", | |||
sh.getQueryString()); | |||
} | |||
@Test | |||
public void generateUpdateQuery_basicQuery_shouldSucceed() | |||
throws SQLException { | |||
/* | |||
* No need to run this for Oracle/MSSQL generators since the | |||
* DefaultSQLGenerator method would be called anyway. | |||
*/ | |||
if (AllTests.sqlGen instanceof MSSQLGenerator | |||
|| AllTests.sqlGen instanceof OracleGenerator) { | |||
return; | |||
} | |||
SQLGenerator sg = new DefaultSQLGenerator(); | |||
TableQuery query = new TableQuery("people", connectionPool); | |||
SQLContainer container = new SQLContainer(query); | |||
RowItem ri = (RowItem) container.getItem(container.getItemIds() | |||
.iterator().next()); | |||
ri.getItemProperty("NAME").setValue("Viljami"); | |||
StatementHelper sh = sg.generateUpdateQuery("people", ri); | |||
Assert.assertTrue("UPDATE people SET \"NAME\" = ?, \"AGE\" = ? WHERE \"ID\" = ?" | |||
.equals(sh.getQueryString()) | |||
|| "UPDATE people SET \"AGE\" = ?, \"NAME\" = ? WHERE \"ID\" = ?" | |||
.equals(sh.getQueryString())); | |||
} | |||
@Test | |||
public void generateInsertQuery_basicQuery_shouldSucceed() | |||
throws SQLException { | |||
/* | |||
* No need to run this for Oracle/MSSQL generators since the | |||
* DefaultSQLGenerator method would be called anyway. | |||
*/ | |||
if (AllTests.sqlGen instanceof MSSQLGenerator | |||
|| AllTests.sqlGen instanceof OracleGenerator) { | |||
return; | |||
} | |||
SQLGenerator sg = new DefaultSQLGenerator(); | |||
TableQuery query = new TableQuery("people", connectionPool); | |||
SQLContainer container = new SQLContainer(query); | |||
RowItem ri = (RowItem) container.getItem(container.addItem()); | |||
ri.getItemProperty("NAME").setValue("Viljami"); | |||
StatementHelper sh = sg.generateInsertQuery("people", ri); | |||
Assert.assertTrue("INSERT INTO people (\"NAME\", \"AGE\") VALUES (?, ?)" | |||
.equals(sh.getQueryString()) | |||
|| "INSERT INTO people (\"AGE\", \"NAME\") VALUES (?, ?)" | |||
.equals(sh.getQueryString())); | |||
} | |||
@Test | |||
public void generateComplexSelectQuery_forOracle_shouldSucceed() | |||
throws SQLException { | |||
SQLGenerator sg = new OracleGenerator(); | |||
List<Filter> f = new ArrayList<Filter>(); | |||
f.add(new Like("name", "%lle")); | |||
List<OrderBy> ob = Arrays.asList(new OrderBy("name", true)); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", f, ob, 4, 8, | |||
"NAME, ID"); | |||
Assert.assertEquals( | |||
"SELECT * FROM (SELECT x.*, ROWNUM AS \"rownum\" FROM" | |||
+ " (SELECT NAME, ID FROM TABLE WHERE \"name\" LIKE ?" | |||
+ " ORDER BY \"name\" ASC) x) WHERE \"rownum\" BETWEEN 5 AND 12", | |||
sh.getQueryString()); | |||
} | |||
@Test | |||
public void generateComplexSelectQuery_forMSSQL_shouldSucceed() | |||
throws SQLException { | |||
SQLGenerator sg = new MSSQLGenerator(); | |||
List<Filter> f = new ArrayList<Filter>(); | |||
f.add(new Like("name", "%lle")); | |||
List<OrderBy> ob = Arrays.asList(new OrderBy("name", true)); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", f, ob, 4, 8, | |||
"NAME, ID"); | |||
Assert.assertEquals(sh.getQueryString(), | |||
"SELECT * FROM (SELECT row_number() OVER " | |||
+ "( ORDER BY \"name\" ASC) AS rownum, NAME, ID " | |||
+ "FROM TABLE WHERE \"name\" LIKE ?) " | |||
+ "AS a WHERE a.rownum BETWEEN 5 AND 12"); | |||
} | |||
@Test | |||
public void generateComplexSelectQuery_forOracle_exclusiveFilteringMode_shouldSucceed() | |||
throws SQLException { | |||
SQLGenerator sg = new OracleGenerator(); | |||
List<Filter> f = new ArrayList<Filter>(); | |||
f.add(new Or(new Like("name", "%lle"), new Like("name", "vi%"))); | |||
List<OrderBy> ob = Arrays.asList(new OrderBy("name", true)); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", f, ob, 4, 8, | |||
"NAME, ID"); | |||
Assert.assertEquals( | |||
sh.getQueryString(), | |||
"SELECT * FROM (SELECT x.*, ROWNUM AS \"rownum\" FROM" | |||
+ " (SELECT NAME, ID FROM TABLE WHERE (\"name\" LIKE ?" | |||
+ " OR \"name\" LIKE ?) " | |||
+ "ORDER BY \"name\" ASC) x) WHERE \"rownum\" BETWEEN 5 AND 12"); | |||
} | |||
@Test | |||
public void generateComplexSelectQuery_forMSSQL_exclusiveFilteringMode_shouldSucceed() | |||
throws SQLException { | |||
SQLGenerator sg = new MSSQLGenerator(); | |||
List<Filter> f = new ArrayList<Filter>(); | |||
f.add(new Or(new Like("name", "%lle"), new Like("name", "vi%"))); | |||
List<OrderBy> ob = Arrays.asList(new OrderBy("name", true)); | |||
StatementHelper sh = sg.generateSelectQuery("TABLE", f, ob, 4, 8, | |||
"NAME, ID"); | |||
Assert.assertEquals(sh.getQueryString(), | |||
"SELECT * FROM (SELECT row_number() OVER " | |||
+ "( ORDER BY \"name\" ASC) AS rownum, NAME, ID " | |||
+ "FROM TABLE WHERE (\"name\" LIKE ? " | |||
+ "OR \"name\" LIKE ?)) " | |||
+ "AS a WHERE a.rownum BETWEEN 5 AND 12"); | |||
} | |||
} |
@@ -0,0 +1,897 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.query; | |||
import java.sql.Connection; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.easymock.EasyMock; | |||
import org.junit.After; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.RowId; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.SQLContainer; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.connection.SimpleJDBCConnectionPool; | |||
import com.vaadin.data.util.filter.Like; | |||
import com.vaadin.data.util.query.FreeformQuery; | |||
import com.vaadin.data.util.query.FreeformQueryDelegate; | |||
import com.vaadin.data.util.query.OrderBy; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests.DB; | |||
import com.vaadin.tests.server.container.sqlcontainer.DataGenerator; | |||
public class FreeformQueryTest { | |||
private static final int offset = AllTests.offset; | |||
private JDBCConnectionPool connectionPool; | |||
@Before | |||
public void setUp() throws SQLException { | |||
try { | |||
connectionPool = new SimpleJDBCConnectionPool(AllTests.dbDriver, | |||
AllTests.dbURL, AllTests.dbUser, AllTests.dbPwd, 2, 2); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
Assert.fail(e.getMessage()); | |||
} | |||
DataGenerator.addPeopleToDatabase(connectionPool); | |||
} | |||
@After | |||
public void tearDown() { | |||
if (connectionPool != null) { | |||
connectionPool.destroy(); | |||
} | |||
} | |||
@Test | |||
public void construction_legalParameters_shouldSucceed() { | |||
FreeformQuery ffQuery = new FreeformQuery("SELECT * FROM foo", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertArrayEquals(new Object[] { "ID" }, ffQuery | |||
.getPrimaryKeyColumns().toArray()); | |||
Assert.assertEquals("SELECT * FROM foo", ffQuery.getQueryString()); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construction_emptyQueryString_shouldFail() { | |||
new FreeformQuery("", Arrays.asList("ID"), connectionPool); | |||
} | |||
@Test | |||
public void construction_nullPrimaryKeys_shouldSucceed() { | |||
new FreeformQuery("SELECT * FROM foo", null, connectionPool); | |||
} | |||
@Test | |||
public void construction_nullPrimaryKeys2_shouldSucceed() { | |||
new FreeformQuery("SELECT * FROM foo", connectionPool); | |||
} | |||
@Test | |||
public void construction_emptyPrimaryKeys_shouldSucceed() { | |||
new FreeformQuery("SELECT * FROM foo", connectionPool); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construction_emptyStringsInPrimaryKeys_shouldFail() { | |||
new FreeformQuery("SELECT * FROM foo", Arrays.asList(""), | |||
connectionPool); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construction_nullConnectionPool_shouldFail() { | |||
new FreeformQuery("SELECT * FROM foo", Arrays.asList("ID"), null); | |||
} | |||
@Test | |||
public void getCount_simpleQuery_returnsFour() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertEquals(4, query.getCount()); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void getCount_illegalQuery_shouldThrowSQLException() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM asdf", | |||
Arrays.asList("ID"), connectionPool); | |||
query.getResults(0, 50); | |||
} | |||
@Test | |||
public void getCount_simpleQueryTwoMorePeopleAdded_returnsSix() | |||
throws SQLException { | |||
// Add some people | |||
Connection conn = connectionPool.reserveConnection(); | |||
Statement statement = conn.createStatement(); | |||
if (AllTests.db == DB.MSSQL) { | |||
statement.executeUpdate("insert into people values('Bengt', 30)"); | |||
statement.executeUpdate("insert into people values('Ingvar', 50)"); | |||
} else { | |||
statement | |||
.executeUpdate("insert into people values(default, 'Bengt', 30)"); | |||
statement | |||
.executeUpdate("insert into people values(default, 'Ingvar', 50)"); | |||
} | |||
statement.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertEquals(6, query.getCount()); | |||
} | |||
@Test | |||
public void getCount_moreComplexQuery_returnsThree() throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT * FROM people WHERE \"NAME\" LIKE '%lle'", | |||
connectionPool, new String[] { "ID" }); | |||
Assert.assertEquals(3, query.getCount()); | |||
} | |||
@Test | |||
public void getCount_normalState_releasesConnection() throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT * FROM people WHERE \"NAME\" LIKE '%lle'", | |||
connectionPool, "ID"); | |||
query.getCount(); | |||
query.getCount(); | |||
Assert.assertNotNull(connectionPool.reserveConnection()); | |||
} | |||
@Test | |||
public void getCount_delegateRegistered_shouldUseDelegate() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect(delegate.getCountQuery()).andReturn( | |||
"SELECT COUNT(*) FROM people WHERE \"NAME\" LIKE '%lle'"); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
Assert.assertEquals(3, query.getCount()); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test | |||
public void getCount_delegateRegisteredZeroRows_returnsZero() | |||
throws SQLException { | |||
DataGenerator.createGarbage(connectionPool); | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM GARBAGE", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect(delegate.getCountQuery()).andReturn( | |||
"SELECT COUNT(*) FROM GARBAGE"); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
Assert.assertEquals(0, query.getCount()); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test | |||
public void getResults_simpleQuery_returnsFourRecords() throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT \"ID\",\"NAME\" FROM people", Arrays.asList("ID"), | |||
connectionPool); | |||
query.beginTransaction(); | |||
ResultSet rs = query.getResults(0, 0); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(0 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Ville", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(1 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Kalle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(2 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Pelle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(3 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Börje", rs.getString(2)); | |||
Assert.assertFalse(rs.next()); | |||
query.commit(); | |||
} | |||
@Test | |||
public void getResults_moreComplexQuery_returnsThreeRecords() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT * FROM people WHERE \"NAME\" LIKE '%lle'", | |||
Arrays.asList("ID"), connectionPool); | |||
query.beginTransaction(); | |||
ResultSet rs = query.getResults(0, 0); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(0 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Ville", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(1 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Kalle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(2 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Pelle", rs.getString(2)); | |||
Assert.assertFalse(rs.next()); | |||
query.commit(); | |||
} | |||
@Test | |||
public void getResults_noDelegate5000Rows_returns5000rows() | |||
throws SQLException { | |||
DataGenerator.addFiveThousandPeople(connectionPool); | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.beginTransaction(); | |||
ResultSet rs = query.getResults(0, 0); | |||
for (int i = 0; i < 5000; i++) { | |||
Assert.assertTrue(rs.next()); | |||
} | |||
Assert.assertFalse(rs.next()); | |||
query.commit(); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void setFilters_noDelegate_shouldFail() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Like("name", "%lle")); | |||
query.setFilters(filters); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void setOrderBy_noDelegate_shouldFail() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.setOrderBy(Arrays.asList(new OrderBy("name", true))); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void storeRow_noDelegateNoTransactionActive_shouldFail() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.storeRow(new RowItem(new SQLContainer(query), new RowId( | |||
new Object[] { 1 }), null)); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void storeRow_noDelegate_shouldFail() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
SQLContainer container = EasyMock.createNiceMock(SQLContainer.class); | |||
EasyMock.replay(container); | |||
query.beginTransaction(); | |||
query.storeRow(new RowItem(container, new RowId(new Object[] { 1 }), | |||
null)); | |||
query.commit(); | |||
EasyMock.verify(container); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void removeRow_noDelegate_shouldFail() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
SQLContainer container = EasyMock.createNiceMock(SQLContainer.class); | |||
EasyMock.replay(container); | |||
query.beginTransaction(); | |||
query.removeRow(new RowItem(container, new RowId(new Object[] { 1 }), | |||
null)); | |||
query.commit(); | |||
EasyMock.verify(container); | |||
} | |||
@Test | |||
public void beginTransaction_readOnly_shouldSucceed() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.beginTransaction(); | |||
} | |||
@Test | |||
public void commit_readOnly_shouldSucceed() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.beginTransaction(); | |||
query.commit(); | |||
} | |||
@Test | |||
public void rollback_readOnly_shouldSucceed() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.beginTransaction(); | |||
query.rollback(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void commit_noActiveTransaction_shouldFail() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.commit(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void rollback_noActiveTransaction_shouldFail() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.rollback(); | |||
} | |||
@Test | |||
public void containsRowWithKeys_simpleQueryWithExistingKeys_returnsTrue() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertTrue(query.containsRowWithKey(1)); | |||
} | |||
@Test | |||
public void containsRowWithKeys_simpleQueryWithNonexistingKeys_returnsTrue() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertFalse(query.containsRowWithKey(1337)); | |||
} | |||
// (expected = SQLException.class) | |||
@Test | |||
public void containsRowWithKeys_simpleQueryWithInvalidKeys_shouldFail() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertFalse(query.containsRowWithKey(38796)); | |||
} | |||
@Test | |||
public void containsRowWithKeys_queryContainingWhereClauseAndExistingKeys_returnsTrue() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT * FROM people WHERE \"NAME\" LIKE '%lle'", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertTrue(query.containsRowWithKey(1)); | |||
} | |||
@Test | |||
public void containsRowWithKeys_queryContainingLowercaseWhereClauseAndExistingKeys_returnsTrue() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"select * from people where \"NAME\" like '%lle'", | |||
Arrays.asList("ID"), connectionPool); | |||
Assert.assertTrue(query.containsRowWithKey(1)); | |||
} | |||
@Test | |||
public void containsRowWithKeys_nullKeys_shouldFailAndReleaseConnections() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"select * from people where \"NAME\" like '%lle'", | |||
Arrays.asList("ID"), connectionPool); | |||
try { | |||
query.containsRowWithKey(new Object[] { null }); | |||
} catch (SQLException e) { | |||
// We should now be able to reserve two connections | |||
connectionPool.reserveConnection(); | |||
connectionPool.reserveConnection(); | |||
} | |||
} | |||
/* | |||
* -------- Tests with a delegate --------- | |||
*/ | |||
@Test | |||
public void setDelegate_noExistingDelegate_shouldRegisterNewDelegate() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
query.setDelegate(delegate); | |||
Assert.assertEquals(delegate, query.getDelegate()); | |||
} | |||
@Test | |||
public void getResults_hasDelegate_shouldCallDelegate() throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
if (AllTests.db == DB.MSSQL) { | |||
EasyMock.expect(delegate.getQueryString(0, 2)) | |||
.andReturn( | |||
"SELECT * FROM (SELECT row_number()" | |||
+ "OVER (ORDER BY id ASC) AS rownum, * FROM people)" | |||
+ " AS a WHERE a.rownum BETWEEN 0 AND 2"); | |||
} else if (AllTests.db == DB.ORACLE) { | |||
EasyMock.expect(delegate.getQueryString(0, 2)) | |||
.andReturn( | |||
"SELECT * FROM (SELECT x.*, ROWNUM AS r FROM" | |||
+ " (SELECT * FROM people) x) WHERE r BETWEEN 1 AND 2"); | |||
} else { | |||
EasyMock.expect(delegate.getQueryString(0, 2)).andReturn( | |||
"SELECT * FROM people LIMIT 2 OFFSET 0"); | |||
} | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
query.getResults(0, 2); | |||
EasyMock.verify(delegate); | |||
query.commit(); | |||
} | |||
@Test | |||
public void getResults_delegateImplementsGetQueryString_shouldHonorOffsetAndPagelength() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
if (AllTests.db == DB.MSSQL) { | |||
EasyMock.expect(delegate.getQueryString(0, 2)) | |||
.andReturn( | |||
"SELECT * FROM (SELECT row_number()" | |||
+ "OVER (ORDER BY id ASC) AS rownum, * FROM people)" | |||
+ " AS a WHERE a.rownum BETWEEN 0 AND 2"); | |||
} else if (AllTests.db == DB.ORACLE) { | |||
EasyMock.expect(delegate.getQueryString(0, 2)) | |||
.andReturn( | |||
"SELECT * FROM (SELECT x.*, ROWNUM AS r FROM" | |||
+ " (SELECT * FROM people) x) WHERE r BETWEEN 1 AND 2"); | |||
} else { | |||
EasyMock.expect(delegate.getQueryString(0, 2)).andReturn( | |||
"SELECT * FROM people LIMIT 2 OFFSET 0"); | |||
} | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
ResultSet rs = query.getResults(0, 2); | |||
int rsoffset = 0; | |||
if (AllTests.db == DB.MSSQL) { | |||
rsoffset++; | |||
} | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(0 + offset, rs.getInt(1 + rsoffset)); | |||
Assert.assertEquals("Ville", rs.getString(2 + rsoffset)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(1 + offset, rs.getInt(1 + rsoffset)); | |||
Assert.assertEquals("Kalle", rs.getString(2 + rsoffset)); | |||
Assert.assertFalse(rs.next()); | |||
EasyMock.verify(delegate); | |||
query.commit(); | |||
} | |||
@Test | |||
public void getResults_delegateRegistered5000Rows_returns100rows() | |||
throws SQLException { | |||
DataGenerator.addFiveThousandPeople(connectionPool); | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
if (AllTests.db == DB.MSSQL) { | |||
EasyMock.expect(delegate.getQueryString(200, 100)) | |||
.andReturn( | |||
"SELECT * FROM (SELECT row_number()" | |||
+ "OVER (ORDER BY id ASC) AS rownum, * FROM people)" | |||
+ " AS a WHERE a.rownum BETWEEN 201 AND 300"); | |||
} else if (AllTests.db == DB.ORACLE) { | |||
EasyMock.expect(delegate.getQueryString(200, 100)) | |||
.andReturn( | |||
"SELECT * FROM (SELECT x.*, ROWNUM AS r FROM" | |||
+ " (SELECT * FROM people ORDER BY ID ASC) x) WHERE r BETWEEN 201 AND 300"); | |||
} else { | |||
EasyMock.expect(delegate.getQueryString(200, 100)).andReturn( | |||
"SELECT * FROM people LIMIT 100 OFFSET 200"); | |||
} | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
ResultSet rs = query.getResults(200, 100); | |||
for (int i = 0; i < 100; i++) { | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(200 + i + offset, rs.getInt("ID")); | |||
} | |||
Assert.assertFalse(rs.next()); | |||
query.commit(); | |||
} | |||
@Test | |||
public void setFilters_delegateImplementsSetFilters_shouldPassFiltersToDelegate() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
List<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Like("name", "%lle")); | |||
delegate.setFilters(filters); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.setFilters(filters); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void setFilters_delegateDoesNotImplementSetFilters_shouldFail() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
List<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Like("name", "%lle")); | |||
delegate.setFilters(filters); | |||
EasyMock.expectLastCall().andThrow(new UnsupportedOperationException()); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.setFilters(filters); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test | |||
public void setOrderBy_delegateImplementsSetOrderBy_shouldPassArgumentsToDelegate() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
List<OrderBy> orderBys = Arrays.asList(new OrderBy("name", false)); | |||
delegate.setOrderBy(orderBys); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.setOrderBy(orderBys); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void setOrderBy_delegateDoesNotImplementSetOrderBy_shouldFail() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
List<OrderBy> orderBys = Arrays.asList(new OrderBy("name", false)); | |||
delegate.setOrderBy(orderBys); | |||
EasyMock.expectLastCall().andThrow(new UnsupportedOperationException()); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.setOrderBy(orderBys); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test | |||
public void setFilters_noDelegateAndNullParameter_shouldSucceed() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.setFilters(null); | |||
} | |||
@Test | |||
public void setOrderBy_noDelegateAndNullParameter_shouldSucceed() { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.setOrderBy(null); | |||
} | |||
@Test | |||
public void storeRow_delegateImplementsStoreRow_shouldPassToDelegate() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect( | |||
delegate.storeRow(EasyMock.isA(Connection.class), | |||
EasyMock.isA(RowItem.class))).andReturn(1); | |||
SQLContainer container = EasyMock.createNiceMock(SQLContainer.class); | |||
EasyMock.replay(delegate, container); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
RowItem row = new RowItem(container, new RowId(new Object[] { 1 }), | |||
null); | |||
query.storeRow(row); | |||
query.commit(); | |||
EasyMock.verify(delegate, container); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void storeRow_delegateDoesNotImplementStoreRow_shouldFail() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect( | |||
delegate.storeRow(EasyMock.isA(Connection.class), | |||
EasyMock.isA(RowItem.class))).andThrow( | |||
new UnsupportedOperationException()); | |||
SQLContainer container = EasyMock.createNiceMock(SQLContainer.class); | |||
EasyMock.replay(delegate, container); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
RowItem row = new RowItem(container, new RowId(new Object[] { 1 }), | |||
null); | |||
query.storeRow(row); | |||
query.commit(); | |||
EasyMock.verify(delegate, container); | |||
} | |||
@Test | |||
public void removeRow_delegateImplementsRemoveRow_shouldPassToDelegate() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect( | |||
delegate.removeRow(EasyMock.isA(Connection.class), | |||
EasyMock.isA(RowItem.class))).andReturn(true); | |||
SQLContainer container = EasyMock.createNiceMock(SQLContainer.class); | |||
EasyMock.replay(delegate, container); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
RowItem row = new RowItem(container, new RowId(new Object[] { 1 }), | |||
null); | |||
query.removeRow(row); | |||
query.commit(); | |||
EasyMock.verify(delegate, container); | |||
} | |||
@Test(expected = UnsupportedOperationException.class) | |||
public void removeRow_delegateDoesNotImplementRemoveRow_shouldFail() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect( | |||
delegate.removeRow(EasyMock.isA(Connection.class), | |||
EasyMock.isA(RowItem.class))).andThrow( | |||
new UnsupportedOperationException()); | |||
SQLContainer container = EasyMock.createNiceMock(SQLContainer.class); | |||
EasyMock.replay(delegate, container); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
RowItem row = new RowItem(container, new RowId(new Object[] { 1 }), | |||
null); | |||
query.removeRow(row); | |||
query.commit(); | |||
EasyMock.verify(delegate, container); | |||
} | |||
@Test | |||
public void beginTransaction_delegateRegistered_shouldSucceed() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void beginTransaction_transactionAlreadyActive_shouldFail() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
query.beginTransaction(); | |||
query.beginTransaction(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void commit_delegateRegisteredNoActiveTransaction_shouldFail() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.commit(); | |||
} | |||
@Test | |||
public void commit_delegateRegisteredActiveTransaction_shouldSucceed() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
query.commit(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void commit_delegateRegisteredActiveTransactionDoubleCommit_shouldFail() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
query.commit(); | |||
query.commit(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void rollback_delegateRegisteredNoActiveTransaction_shouldFail() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.rollback(); | |||
} | |||
@Test | |||
public void rollback_delegateRegisteredActiveTransaction_shouldSucceed() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
query.rollback(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void rollback_delegateRegisteredActiveTransactionDoubleRollback_shouldFail() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
query.rollback(); | |||
query.rollback(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void rollback_delegateRegisteredCommittedTransaction_shouldFail() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
query.commit(); | |||
query.rollback(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void commit_delegateRegisteredRollbackedTransaction_shouldFail() | |||
throws UnsupportedOperationException, SQLException { | |||
FreeformQuery query = new FreeformQuery("SELECT * FROM people", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.beginTransaction(); | |||
query.rollback(); | |||
query.commit(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void containsRowWithKeys_delegateRegistered_shouldCallGetContainsRowQueryString() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT * FROM people WHERE name LIKE '%lle'", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect(delegate.getContainsRowQueryString(1)).andReturn(""); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
query.containsRowWithKey(1); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test | |||
public void containsRowWithKeys_delegateRegistered_shouldUseResultFromGetContainsRowQueryString() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT * FROM people WHERE \"NAME\" LIKE '%lle'", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
// In order to test that this is the query that is actually used, we use | |||
// a non-existing id in place of the existing one. | |||
EasyMock.expect(delegate.getContainsRowQueryString(1)) | |||
.andReturn( | |||
"SELECT * FROM people WHERE \"NAME\" LIKE '%lle' AND \"ID\" = 1337"); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
// The id (key) used should be 1337 as above, for the call with key = 1 | |||
Assert.assertFalse(query.containsRowWithKey(1)); | |||
EasyMock.verify(delegate); | |||
} | |||
@Test | |||
public void containsRowWithKeys_delegateRegisteredGetContainsRowQueryStringNotImplemented_shouldBuildQueryString() | |||
throws SQLException { | |||
FreeformQuery query = new FreeformQuery( | |||
"SELECT * FROM people WHERE \"NAME\" LIKE '%lle'", | |||
Arrays.asList("ID"), connectionPool); | |||
FreeformQueryDelegate delegate = EasyMock | |||
.createMock(FreeformQueryDelegate.class); | |||
EasyMock.expect(delegate.getContainsRowQueryString(1)).andThrow( | |||
new UnsupportedOperationException()); | |||
EasyMock.replay(delegate); | |||
query.setDelegate(delegate); | |||
Assert.assertTrue(query.containsRowWithKey(1)); | |||
EasyMock.verify(delegate); | |||
} | |||
} |
@@ -0,0 +1,311 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.query; | |||
import java.util.ArrayList; | |||
import junit.framework.Assert; | |||
import org.easymock.EasyMock; | |||
import org.junit.Test; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.filter.And; | |||
import com.vaadin.data.util.filter.Between; | |||
import com.vaadin.data.util.filter.Compare.Equal; | |||
import com.vaadin.data.util.filter.Compare.Greater; | |||
import com.vaadin.data.util.filter.Compare.GreaterOrEqual; | |||
import com.vaadin.data.util.filter.Compare.Less; | |||
import com.vaadin.data.util.filter.Compare.LessOrEqual; | |||
import com.vaadin.data.util.filter.IsNull; | |||
import com.vaadin.data.util.filter.Like; | |||
import com.vaadin.data.util.filter.Not; | |||
import com.vaadin.data.util.filter.Or; | |||
import com.vaadin.data.util.filter.SimpleStringFilter; | |||
import com.vaadin.data.util.query.generator.StatementHelper; | |||
import com.vaadin.data.util.query.generator.filter.QueryBuilder; | |||
import com.vaadin.data.util.query.generator.filter.StringDecorator; | |||
public class QueryBuilderTest { | |||
private StatementHelper mockedStatementHelper(Object... values) { | |||
StatementHelper sh = EasyMock.createMock(StatementHelper.class); | |||
for (Object val : values) { | |||
sh.addParameterValue(val); | |||
EasyMock.expectLastCall(); | |||
} | |||
EasyMock.replay(sh); | |||
return sh; | |||
} | |||
// escape bad characters and wildcards | |||
@Test | |||
public void getWhereStringForFilter_equals() { | |||
StatementHelper sh = mockedStatementHelper("Fido"); | |||
Equal f = new Equal("NAME", "Fido"); | |||
Assert.assertEquals("\"NAME\" = ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_greater() { | |||
StatementHelper sh = mockedStatementHelper(18); | |||
Greater f = new Greater("AGE", 18); | |||
Assert.assertEquals("\"AGE\" > ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_less() { | |||
StatementHelper sh = mockedStatementHelper(65); | |||
Less f = new Less("AGE", 65); | |||
Assert.assertEquals("\"AGE\" < ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_greaterOrEqual() { | |||
StatementHelper sh = mockedStatementHelper(18); | |||
GreaterOrEqual f = new GreaterOrEqual("AGE", 18); | |||
Assert.assertEquals("\"AGE\" >= ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_lessOrEqual() { | |||
StatementHelper sh = mockedStatementHelper(65); | |||
LessOrEqual f = new LessOrEqual("AGE", 65); | |||
Assert.assertEquals("\"AGE\" <= ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_simpleStringFilter() { | |||
StatementHelper sh = mockedStatementHelper("Vi%"); | |||
SimpleStringFilter f = new SimpleStringFilter("NAME", "Vi", false, true); | |||
Assert.assertEquals("\"NAME\" LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_simpleStringFilterMatchAnywhere() { | |||
StatementHelper sh = mockedStatementHelper("%Vi%"); | |||
SimpleStringFilter f = new SimpleStringFilter("NAME", "Vi", false, | |||
false); | |||
Assert.assertEquals("\"NAME\" LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_simpleStringFilterMatchAnywhereIgnoreCase() { | |||
StatementHelper sh = mockedStatementHelper("%VI%"); | |||
SimpleStringFilter f = new SimpleStringFilter("NAME", "Vi", true, false); | |||
Assert.assertEquals("UPPER(\"NAME\") LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_startsWith() { | |||
StatementHelper sh = mockedStatementHelper("Vi%"); | |||
Like f = new Like("NAME", "Vi%"); | |||
Assert.assertEquals("\"NAME\" LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_startsWithNumber() { | |||
StatementHelper sh = mockedStatementHelper("1%"); | |||
Like f = new Like("AGE", "1%"); | |||
Assert.assertEquals("\"AGE\" LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_endsWith() { | |||
StatementHelper sh = mockedStatementHelper("%lle"); | |||
Like f = new Like("NAME", "%lle"); | |||
Assert.assertEquals("\"NAME\" LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_contains() { | |||
StatementHelper sh = mockedStatementHelper("%ill%"); | |||
Like f = new Like("NAME", "%ill%"); | |||
Assert.assertEquals("\"NAME\" LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_between() { | |||
StatementHelper sh = mockedStatementHelper(18, 65); | |||
Between f = new Between("AGE", 18, 65); | |||
Assert.assertEquals("\"AGE\" BETWEEN ? AND ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_caseInsensitive_equals() { | |||
StatementHelper sh = mockedStatementHelper("FIDO"); | |||
Like f = new Like("NAME", "Fido"); | |||
f.setCaseSensitive(false); | |||
Assert.assertEquals("UPPER(\"NAME\") LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_caseInsensitive_startsWith() { | |||
StatementHelper sh = mockedStatementHelper("VI%"); | |||
Like f = new Like("NAME", "Vi%"); | |||
f.setCaseSensitive(false); | |||
Assert.assertEquals("UPPER(\"NAME\") LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_caseInsensitive_endsWith() { | |||
StatementHelper sh = mockedStatementHelper("%LLE"); | |||
Like f = new Like("NAME", "%lle"); | |||
f.setCaseSensitive(false); | |||
Assert.assertEquals("UPPER(\"NAME\") LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilter_caseInsensitive_contains() { | |||
StatementHelper sh = mockedStatementHelper("%ILL%"); | |||
Like f = new Like("NAME", "%ill%"); | |||
f.setCaseSensitive(false); | |||
Assert.assertEquals("UPPER(\"NAME\") LIKE ?", | |||
QueryBuilder.getWhereStringForFilter(f, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_listOfFilters() { | |||
StatementHelper sh = mockedStatementHelper("%lle", 18); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Like("NAME", "%lle")); | |||
filters.add(new Greater("AGE", 18)); | |||
Assert.assertEquals(" WHERE \"NAME\" LIKE ? AND \"AGE\" > ?", | |||
QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_oneAndFilter() { | |||
StatementHelper sh = mockedStatementHelper("%lle", 18); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new And(new Like("NAME", "%lle"), new Greater("AGE", 18))); | |||
Assert.assertEquals(" WHERE (\"NAME\" LIKE ? AND \"AGE\" > ?)", | |||
QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_oneOrFilter() { | |||
StatementHelper sh = mockedStatementHelper("%lle", 18); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Or(new Like("NAME", "%lle"), new Greater("AGE", 18))); | |||
Assert.assertEquals(" WHERE (\"NAME\" LIKE ? OR \"AGE\" > ?)", | |||
QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_complexCompoundFilters() { | |||
StatementHelper sh = mockedStatementHelper("%lle", 18, 65, "Pelle"); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Or(new And(new Like("NAME", "%lle"), new Or(new Less( | |||
"AGE", 18), new Greater("AGE", 65))), | |||
new Equal("NAME", "Pelle"))); | |||
Assert.assertEquals( | |||
" WHERE ((\"NAME\" LIKE ? AND (\"AGE\" < ? OR \"AGE\" > ?)) OR \"NAME\" = ?)", | |||
QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_complexCompoundFiltersAndSingleFilter() { | |||
StatementHelper sh = mockedStatementHelper("%lle", 18, 65, "Pelle", | |||
"Virtanen"); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Or(new And(new Like("NAME", "%lle"), new Or(new Less( | |||
"AGE", 18), new Greater("AGE", 65))), | |||
new Equal("NAME", "Pelle"))); | |||
filters.add(new Equal("LASTNAME", "Virtanen")); | |||
Assert.assertEquals( | |||
" WHERE ((\"NAME\" LIKE ? AND (\"AGE\" < ? OR \"AGE\" > ?)) OR \"NAME\" = ?) AND \"LASTNAME\" = ?", | |||
QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_emptyList_shouldReturnEmptyString() { | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
Assert.assertEquals("", QueryBuilder.getWhereStringForFilters(filters, | |||
new StatementHelper())); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_NotFilter() { | |||
StatementHelper sh = mockedStatementHelper(18); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Not(new Equal("AGE", 18))); | |||
Assert.assertEquals(" WHERE NOT \"AGE\" = ?", | |||
QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_complexNegatedFilter() { | |||
StatementHelper sh = mockedStatementHelper(65, 18); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Not(new Or(new Equal("AGE", 65), new Equal("AGE", 18)))); | |||
Assert.assertEquals(" WHERE NOT (\"AGE\" = ? OR \"AGE\" = ?)", | |||
QueryBuilder.getWhereStringForFilters(filters, sh)); | |||
EasyMock.verify(sh); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_isNull() { | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new IsNull("NAME")); | |||
Assert.assertEquals(" WHERE \"NAME\" IS NULL", QueryBuilder | |||
.getWhereStringForFilters(filters, new StatementHelper())); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_isNotNull() { | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Not(new IsNull("NAME"))); | |||
Assert.assertEquals(" WHERE \"NAME\" IS NOT NULL", QueryBuilder | |||
.getWhereStringForFilters(filters, new StatementHelper())); | |||
} | |||
@Test | |||
public void getWhereStringForFilters_customStringDecorator() { | |||
QueryBuilder.setStringDecorator(new StringDecorator("[", "]")); | |||
ArrayList<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Not(new IsNull("NAME"))); | |||
Assert.assertEquals(" WHERE [NAME] IS NOT NULL", QueryBuilder | |||
.getWhereStringForFilters(filters, new StatementHelper())); | |||
// Reset the default string decorator | |||
QueryBuilder.setStringDecorator(new StringDecorator("\"", "\"")); | |||
} | |||
} |
@@ -0,0 +1,619 @@ | |||
package com.vaadin.tests.server.container.sqlcontainer.query; | |||
import java.sql.Connection; | |||
import java.sql.PreparedStatement; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.junit.After; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.data.Container.Filter; | |||
import com.vaadin.data.util.OptimisticLockException; | |||
import com.vaadin.data.util.RowItem; | |||
import com.vaadin.data.util.SQLContainer; | |||
import com.vaadin.data.util.connection.JDBCConnectionPool; | |||
import com.vaadin.data.util.connection.SimpleJDBCConnectionPool; | |||
import com.vaadin.data.util.filter.Compare.Equal; | |||
import com.vaadin.data.util.filter.Like; | |||
import com.vaadin.data.util.query.OrderBy; | |||
import com.vaadin.data.util.query.TableQuery; | |||
import com.vaadin.data.util.query.generator.DefaultSQLGenerator; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests; | |||
import com.vaadin.tests.server.container.sqlcontainer.AllTests.DB; | |||
import com.vaadin.tests.server.container.sqlcontainer.DataGenerator; | |||
public class TableQueryTest { | |||
private static final int offset = AllTests.offset; | |||
private JDBCConnectionPool connectionPool; | |||
@Before | |||
public void setUp() throws SQLException { | |||
try { | |||
connectionPool = new SimpleJDBCConnectionPool(AllTests.dbDriver, | |||
AllTests.dbURL, AllTests.dbUser, AllTests.dbPwd, 2, 2); | |||
} catch (SQLException e) { | |||
e.printStackTrace(); | |||
Assert.fail(e.getMessage()); | |||
} | |||
DataGenerator.addPeopleToDatabase(connectionPool); | |||
} | |||
@After | |||
public void tearDown() { | |||
if (connectionPool != null) { | |||
connectionPool.destroy(); | |||
} | |||
} | |||
/********************************************************************** | |||
* TableQuery construction tests | |||
**********************************************************************/ | |||
@Test | |||
public void construction_legalParameters_shouldSucceed() { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
new DefaultSQLGenerator()); | |||
Assert.assertArrayEquals(new Object[] { "ID" }, tQuery | |||
.getPrimaryKeyColumns().toArray()); | |||
boolean correctTableName = "people".equalsIgnoreCase(tQuery | |||
.getTableName()); | |||
Assert.assertTrue(correctTableName); | |||
} | |||
@Test | |||
public void construction_legalParameters_defaultGenerator_shouldSucceed() { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
Assert.assertArrayEquals(new Object[] { "ID" }, tQuery | |||
.getPrimaryKeyColumns().toArray()); | |||
boolean correctTableName = "people".equalsIgnoreCase(tQuery | |||
.getTableName()); | |||
Assert.assertTrue(correctTableName); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construction_nonExistingTableName_shouldFail() { | |||
new TableQuery("skgwaguhsd", connectionPool, new DefaultSQLGenerator()); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construction_emptyTableName_shouldFail() { | |||
new TableQuery("", connectionPool, new DefaultSQLGenerator()); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construction_nullSqlGenerator_shouldFail() { | |||
new TableQuery("people", connectionPool, null); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void construction_nullConnectionPool_shouldFail() { | |||
new TableQuery("people", null, new DefaultSQLGenerator()); | |||
} | |||
/********************************************************************** | |||
* TableQuery row count tests | |||
**********************************************************************/ | |||
@Test | |||
public void getCount_simpleQuery_returnsFour() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
Assert.assertEquals(4, tQuery.getCount()); | |||
} | |||
@Test | |||
public void getCount_simpleQueryTwoMorePeopleAdded_returnsSix() | |||
throws SQLException { | |||
// Add some people | |||
Connection conn = connectionPool.reserveConnection(); | |||
Statement statement = conn.createStatement(); | |||
if (AllTests.db == DB.MSSQL) { | |||
statement.executeUpdate("insert into people values('Bengt', 30)"); | |||
statement.executeUpdate("insert into people values('Ingvar', 50)"); | |||
} else { | |||
statement | |||
.executeUpdate("insert into people values(default, 'Bengt', 30)"); | |||
statement | |||
.executeUpdate("insert into people values(default, 'Ingvar', 50)"); | |||
} | |||
statement.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
Assert.assertEquals(6, tQuery.getCount()); | |||
} | |||
@Test | |||
public void getCount_normalState_releasesConnection() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.getCount(); | |||
tQuery.getCount(); | |||
Assert.assertNotNull(connectionPool.reserveConnection()); | |||
} | |||
/********************************************************************** | |||
* TableQuery get results tests | |||
**********************************************************************/ | |||
@Test | |||
public void getResults_simpleQuery_returnsFourRecords() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.beginTransaction(); | |||
ResultSet rs = tQuery.getResults(0, 0); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(0 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Ville", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(1 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Kalle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(2 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Pelle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(3 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Börje", rs.getString(2)); | |||
Assert.assertFalse(rs.next()); | |||
tQuery.commit(); | |||
} | |||
@Test | |||
public void getResults_noDelegate5000Rows_returns5000rows() | |||
throws SQLException { | |||
DataGenerator.addFiveThousandPeople(connectionPool); | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.beginTransaction(); | |||
ResultSet rs = tQuery.getResults(0, 0); | |||
for (int i = 0; i < 5000; i++) { | |||
Assert.assertTrue(rs.next()); | |||
} | |||
Assert.assertFalse(rs.next()); | |||
tQuery.commit(); | |||
} | |||
/********************************************************************** | |||
* TableQuery transaction management tests | |||
**********************************************************************/ | |||
@Test | |||
public void beginTransaction_readOnly_shouldSucceed() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.beginTransaction(); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void beginTransaction_transactionAlreadyActive_shouldFail() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.beginTransaction(); | |||
tQuery.beginTransaction(); | |||
} | |||
@Test | |||
public void commit_readOnly_shouldSucceed() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.beginTransaction(); | |||
tQuery.commit(); | |||
} | |||
@Test | |||
public void rollback_readOnly_shouldSucceed() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.beginTransaction(); | |||
tQuery.rollback(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void commit_noActiveTransaction_shouldFail() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.commit(); | |||
} | |||
@Test(expected = SQLException.class) | |||
public void rollback_noActiveTransaction_shouldFail() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.rollback(); | |||
} | |||
/********************************************************************** | |||
* TableQuery row query with given keys tests | |||
**********************************************************************/ | |||
@Test | |||
public void containsRowWithKeys_existingKeys_returnsTrue() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
Assert.assertTrue(tQuery.containsRowWithKey(1)); | |||
} | |||
@Test | |||
public void containsRowWithKeys_nonexistingKeys_returnsTrue() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
Assert.assertFalse(tQuery.containsRowWithKey(1337)); | |||
} | |||
@Test | |||
public void containsRowWithKeys_invalidKeys_shouldFail() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
boolean b = true; | |||
try { | |||
b = tQuery.containsRowWithKey("foo"); | |||
} catch (SQLException se) { | |||
return; | |||
} | |||
Assert.assertFalse(b); | |||
} | |||
@Test | |||
public void containsRowWithKeys_nullKeys_shouldFailAndReleaseConnections() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
try { | |||
tQuery.containsRowWithKey(new Object[] { null }); | |||
} catch (SQLException e) { | |||
// We should now be able to reserve two connections | |||
connectionPool.reserveConnection(); | |||
connectionPool.reserveConnection(); | |||
} | |||
} | |||
/********************************************************************** | |||
* TableQuery filtering and ordering tests | |||
**********************************************************************/ | |||
@Test | |||
public void setFilters_shouldReturnCorrectCount() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
List<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Like("NAME", "%lle")); | |||
tQuery.setFilters(filters); | |||
Assert.assertEquals(3, tQuery.getCount()); | |||
} | |||
@Test | |||
public void setOrderByNameAscending_shouldReturnCorrectOrder() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
List<OrderBy> orderBys = Arrays.asList(new OrderBy("NAME", true)); | |||
tQuery.setOrderBy(orderBys); | |||
tQuery.beginTransaction(); | |||
ResultSet rs; | |||
rs = tQuery.getResults(0, 0); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(3 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Börje", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(1 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Kalle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(2 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Pelle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(0 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Ville", rs.getString(2)); | |||
Assert.assertFalse(rs.next()); | |||
tQuery.commit(); | |||
} | |||
@Test | |||
public void setOrderByNameDescending_shouldReturnCorrectOrder() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
List<OrderBy> orderBys = Arrays.asList(new OrderBy("NAME", false)); | |||
tQuery.setOrderBy(orderBys); | |||
tQuery.beginTransaction(); | |||
ResultSet rs; | |||
rs = tQuery.getResults(0, 0); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(0 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Ville", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(2 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Pelle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(1 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Kalle", rs.getString(2)); | |||
Assert.assertTrue(rs.next()); | |||
Assert.assertEquals(3 + offset, rs.getInt(1)); | |||
Assert.assertEquals("Börje", rs.getString(2)); | |||
Assert.assertFalse(rs.next()); | |||
tQuery.commit(); | |||
} | |||
@Test | |||
public void setFilters_nullParameter_shouldSucceed() { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.setFilters(null); | |||
} | |||
@Test | |||
public void setOrderBy_nullParameter_shouldSucceed() { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.setOrderBy(null); | |||
} | |||
/********************************************************************** | |||
* TableQuery row removal tests | |||
**********************************************************************/ | |||
@Test | |||
public void removeRowThroughContainer_legalRowItem_shouldSucceed() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
container.setAutoCommit(false); | |||
Assert.assertTrue(container.removeItem(container.getItemIds() | |||
.iterator().next())); | |||
Assert.assertEquals(4, tQuery.getCount()); | |||
Assert.assertEquals(3, container.size()); | |||
container.commit(); | |||
Assert.assertEquals(3, tQuery.getCount()); | |||
Assert.assertEquals(3, container.size()); | |||
} | |||
@Test | |||
public void removeRowThroughContainer_nonexistingRowId_shouldFail() | |||
throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
container.setAutoCommit(true); | |||
Assert.assertFalse(container.removeItem("foo")); | |||
} | |||
/********************************************************************** | |||
* TableQuery row adding / modification tests | |||
**********************************************************************/ | |||
@Test | |||
public void insertRowThroughContainer_shouldSucceed() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.setVersionColumn("ID"); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
container.setAutoCommit(false); | |||
Object item = container.addItem(); | |||
Assert.assertNotNull(item); | |||
Assert.assertEquals(4, tQuery.getCount()); | |||
Assert.assertEquals(5, container.size()); | |||
container.commit(); | |||
Assert.assertEquals(5, tQuery.getCount()); | |||
Assert.assertEquals(5, container.size()); | |||
} | |||
@Test | |||
public void modifyRowThroughContainer_shouldSucceed() throws SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
// In this test the primary key is used as a version column | |||
tQuery.setVersionColumn("ID"); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
container.setAutoCommit(false); | |||
/* Check that the container size is correct and there is no 'Viljami' */ | |||
Assert.assertEquals(4, container.size()); | |||
List<Filter> filters = new ArrayList<Filter>(); | |||
filters.add(new Equal("NAME", "Viljami")); | |||
tQuery.setFilters(filters); | |||
Assert.assertEquals(0, tQuery.getCount()); | |||
tQuery.setFilters(null); | |||
/* Fetch first item, modify and commit */ | |||
Object item = container.getItem(container.getItemIds().iterator() | |||
.next()); | |||
Assert.assertNotNull(item); | |||
RowItem ri = (RowItem) item; | |||
Assert.assertNotNull(ri.getItemProperty("NAME")); | |||
ri.getItemProperty("NAME").setValue("Viljami"); | |||
container.commit(); | |||
// Check that the size is still correct and only 1 'Viljami' is found | |||
Assert.assertEquals(4, tQuery.getCount()); | |||
Assert.assertEquals(4, container.size()); | |||
tQuery.setFilters(filters); | |||
Assert.assertEquals(1, tQuery.getCount()); | |||
} | |||
@Test | |||
public void storeRow_noVersionColumn_shouldSucceed() | |||
throws UnsupportedOperationException, SQLException { | |||
TableQuery tQuery = new TableQuery("people", connectionPool, | |||
AllTests.sqlGen); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
Object id = container.addItem(); | |||
RowItem row = (RowItem) container.getItem(id); | |||
row.getItemProperty("NAME").setValue("R2D2"); | |||
row.getItemProperty("AGE").setValue(123); | |||
tQuery.beginTransaction(); | |||
tQuery.storeRow(row); | |||
tQuery.commit(); | |||
Connection conn = connectionPool.reserveConnection(); | |||
PreparedStatement stmt = conn | |||
.prepareStatement("SELECT * FROM PEOPLE WHERE \"NAME\" = ?"); | |||
stmt.setString(1, "R2D2"); | |||
ResultSet rs = stmt.executeQuery(); | |||
Assert.assertTrue(rs.next()); | |||
rs.close(); | |||
stmt.close(); | |||
connectionPool.releaseConnection(conn); | |||
} | |||
@Test | |||
public void storeRow_versionSetAndEqualToDBValue_shouldSucceed() | |||
throws SQLException { | |||
DataGenerator.addVersionedData(connectionPool); | |||
TableQuery tQuery = new TableQuery("versioned", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.setVersionColumn("VERSION"); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
RowItem row = (RowItem) container.getItem(container.firstItemId()); | |||
Assert.assertEquals("Junk", row.getItemProperty("TEXT").getValue()); | |||
row.getItemProperty("TEXT").setValue("asdf"); | |||
container.commit(); | |||
Connection conn = connectionPool.reserveConnection(); | |||
PreparedStatement stmt = conn | |||
.prepareStatement("SELECT * FROM VERSIONED WHERE \"TEXT\" = ?"); | |||
stmt.setString(1, "asdf"); | |||
ResultSet rs = stmt.executeQuery(); | |||
Assert.assertTrue(rs.next()); | |||
rs.close(); | |||
stmt.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
} | |||
@Test(expected = OptimisticLockException.class) | |||
public void storeRow_versionSetAndLessThanDBValue_shouldThrowException() | |||
throws SQLException { | |||
if (AllTests.db == DB.HSQLDB) { | |||
throw new OptimisticLockException( | |||
"HSQLDB doesn't support row versioning for optimistic locking - don't run this test.", | |||
null); | |||
} | |||
DataGenerator.addVersionedData(connectionPool); | |||
TableQuery tQuery = new TableQuery("versioned", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.setVersionColumn("VERSION"); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
RowItem row = (RowItem) container.getItem(container.firstItemId()); | |||
Assert.assertEquals("Junk", row.getItemProperty("TEXT").getValue()); | |||
row.getItemProperty("TEXT").setValue("asdf"); | |||
// Update the version using another connection. | |||
Connection conn = connectionPool.reserveConnection(); | |||
PreparedStatement stmt = conn | |||
.prepareStatement("UPDATE VERSIONED SET \"TEXT\" = ? WHERE \"ID\" = ?"); | |||
stmt.setString(1, "foo"); | |||
stmt.setObject(2, row.getItemProperty("ID").getValue()); | |||
stmt.executeUpdate(); | |||
stmt.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
container.commit(); | |||
} | |||
@Test | |||
public void removeRow_versionSetAndEqualToDBValue_shouldSucceed() | |||
throws SQLException { | |||
DataGenerator.addVersionedData(connectionPool); | |||
TableQuery tQuery = new TableQuery("versioned", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.setVersionColumn("VERSION"); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
RowItem row = (RowItem) container.getItem(container.firstItemId()); | |||
Assert.assertEquals("Junk", row.getItemProperty("TEXT").getValue()); | |||
container.removeItem(container.firstItemId()); | |||
container.commit(); | |||
Connection conn = connectionPool.reserveConnection(); | |||
PreparedStatement stmt = conn | |||
.prepareStatement("SELECT * FROM VERSIONED WHERE \"TEXT\" = ?"); | |||
stmt.setString(1, "Junk"); | |||
ResultSet rs = stmt.executeQuery(); | |||
Assert.assertFalse(rs.next()); | |||
rs.close(); | |||
stmt.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
} | |||
@Test(expected = OptimisticLockException.class) | |||
public void removeRow_versionSetAndLessThanDBValue_shouldThrowException() | |||
throws SQLException { | |||
if (AllTests.db == AllTests.DB.HSQLDB) { | |||
// HSQLDB doesn't support versioning, so this is to make the test | |||
// green. | |||
throw new OptimisticLockException(null); | |||
} | |||
DataGenerator.addVersionedData(connectionPool); | |||
TableQuery tQuery = new TableQuery("versioned", connectionPool, | |||
AllTests.sqlGen); | |||
tQuery.setVersionColumn("VERSION"); | |||
SQLContainer container = new SQLContainer(tQuery); | |||
RowItem row = (RowItem) container.getItem(container.firstItemId()); | |||
Assert.assertEquals("Junk", row.getItemProperty("TEXT").getValue()); | |||
// Update the version using another connection. | |||
Connection conn = connectionPool.reserveConnection(); | |||
PreparedStatement stmt = conn | |||
.prepareStatement("UPDATE VERSIONED SET \"TEXT\" = ? WHERE \"ID\" = ?"); | |||
stmt.setString(1, "asdf"); | |||
stmt.setObject(2, row.getItemProperty("ID").getValue()); | |||
stmt.executeUpdate(); | |||
stmt.close(); | |||
conn.commit(); | |||
connectionPool.releaseConnection(conn); | |||
container.removeItem(container.firstItemId()); | |||
container.commit(); | |||
} | |||
} |