> primaryKeys = rowPart.getPrimaryKeys();
for (QueryableDatatype> primaryKey : primaryKeys) {
if (primaryKey != null) {
String rowPartStr = primaryKey.toSQLString(getReadyDatabase().getDefinition());
ps.print(" " + rowPart.getPrimaryKeyColumnNames() + ": " + rowPartStr);
}
}
}
}
ps.println();
}
}
/**
* Remove all tables from the query and discard any results or state.
*
*
* Clears all the settings and collections within this instance and set it
* back to a blank state
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance.
*/
public DBQuery clear() {
details.clear();
return this;
}
/**
* Count the rows on the database without retrieving the rows.
*
*
* Either: counts the results already retrieved, or creates a
* {@link #getSQLForCount() count query} for this instance and retrieves the
* number of rows that would have been returned had
* {@link #getAllRows() getAllRows()} been called.
*
*
Support DBvolution at
* Patreon
*
* @return the number of rows that have or will be retrieved. Database
* exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public Long count() throws SQLException {
if (needsResults(details.getOptions())) {
this.details.setQueryType(QueryType.COUNT);
database.executeDBQuery(details);
return details.getCount();
} else {
return (long) details.getResults().size();
}
}
/**
* Test whether this DBQuery will create a query without limitations.
*
*
* Checks this instance for criteria and conditions and returns FALSE if at
* least one constraint has been placed on the query.
*
*
* This helps avoid the common mistake of accidentally retrieving all the rows
* of the tables by forgetting to add criteria.
*
*
* No attempt to compare the length of the query results with the length of
* the table is made: if your criteria selects all the row of the tables this
* method will still return FALSE.
*
*
Support DBvolution at
* Patreon
*
* @return TRUE if the DBQuery will retrieve all the rows of the tables, FALSE
* otherwise
*/
public boolean willCreateBlankQuery() {
return details.willCreateBlankQuery(database);
}
/**
* Limit the query to only returning a certain number of rows.
*
*
* Implements support of the LIMIT and TOP operators of many databases. Also
* sets the "page" length for retrieving rows by pages.
*
*
* Only the specified number of rows will be returned from the database and
* DBvolution.
*
*
* Only positive limits are permitted: negative numbers will be converted to
* zero(0). To remove the row limit use {@link #clearRowLimit() }.
*
* @param maximumNumberOfRowsReturned the require limit to the number of rows
* returned
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
* @see #clearRowLimit()
*/
public DBQuery setRowLimit(int maximumNumberOfRowsReturned) {
int limit = maximumNumberOfRowsReturned;
if (maximumNumberOfRowsReturned < 0) {
limit = 0;
}
details.getOptions().setRowLimit(limit);
blankResults();
return this;
}
/**
* Clear the row limit on this DBQuery and return it to retrieving all rows.
*
*
* Also resets the retrieved results so that the database will be re-queried.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
* @see #setRowLimit(int)
*/
public DBQuery clearRowLimit() {
details.getOptions().setRowLimit(-1);
blankResults();
return this;
}
/**
* Sets the sort order of properties (field and/or method) by the given
* property object references.
*
*
* For example the following code snippet will sort by just the name column:
*
* Customer customer = ...;
* query.setSortOrder(customer.column(customer.name));
*
*
*
* Where possible DBvolution sorts NULL values as the least significant value,
* for example "NULL, 1, 2, 3, 4..." not "... 4, 5, 6, NULL".
*
* @param sortColumns a list of columns to sort the query by.
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery setSortOrder(ColumnProvider... sortColumns) {
blankResults();
details.setSortOrder(sortColumns);
return this;
}
/**
* Adds the properties (field and/or method) to the end of the sort order.
*
*
* For example the following code snippet will add the name column at the end
* of the sort order after district:
*
* Customer customer = ...;
* query.setSortOrder(customer.column(customer.district));
* query.addToSortOrder(customer.column(customer.name));
*
*
*
* Note that the above example is equivalent to:
*
* Customer customer = ...;
* query.setSortOrder(customer.column(customer.district), customer.column(customer.name));
*
*
* @param sortColumns a list of columns to sort the query by.
* Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addToSortOrder(ColumnProvider... sortColumns) {
details.addToSortOrder(sortColumns);
return this;
}
/**
* Adds the properties (field and/or method) to the end of the sort order.
*
*
* For example the following code snippet will add the name column at the end
* of the sort order after district:
*
* Customer customer = ...;
* query.setSortOrder(customer.column(customer.district));
* query.addToSortOrder(customer.column(customer.name));
*
*
*
* Note that the above example is equivalent to:
*
* Customer customer = ...;
* query.setSortOrder(customer.column(customer.district), customer.column(customer.name));
*
*
* @param sortColumns a list of columns to sort the query by.
* Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addToSortOrder(DBExpression... sortColumns) {
for (DBExpression dBExpression : sortColumns) {
if (dBExpression instanceof ColumnProvider) {
this.addToSortOrder((ColumnProvider) dBExpression);
}
}
return this;
}
/**
* Remove all sorting that has been set on this DBQuery
*
* Support DBvolution at
* Patreon
*
* @return this DBQuery instance
*/
public DBQuery clearSortOrder() {
details.clearSortOrder();
return this;
}
/**
* Change the Default Setting of Disallowing Blank Queries
*
*
* A common mistake is creating a query without supplying criteria and
* accidently retrieving a huge number of rows.
*
*
* DBvolution detects this situation and, by default, throws a
* {@link nz.co.gregs.dbvolution.exceptions.AccidentalBlankQueryException AccidentalBlankQueryException}
* when it happens.
*
*
* To change this behaviour, and allow blank queries, call
* {@code setBlankQueriesAllowed(true)}.
*
* @param allow - TRUE to allow blank queries, FALSE to return it to the
* default setting.
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery setBlankQueryAllowed(boolean allow) {
this.details.getOptions().setBlankQueryAllowed(allow);
return this;
}
/**
* Change the Default Setting of Disallowing Accidental Cartesian Joins
*
*
* A common mistake is to create a query without connecting all the tables in
* the query and accident retrieve a huge number of rows.
*
*
* DBvolution detects this situation and, by default, throws a
* {@link nz.co.gregs.dbvolution.exceptions.AccidentalCartesianJoinException AccidentalCartesianJoinException}
* when it happens.
*
*
* To change this behaviour, and allow cartesian joins, call
* {@code setCartesianJoinsAllowed(true)}.
*
* @param allow - TRUE to allow cartesian joins, FALSE to return it to the
* default setting.
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery setCartesianJoinsAllowed(boolean allow) {
this.details.getOptions().setCartesianJoinAllowed(allow);
return this;
}
/**
* Constructs the SQL for this DBQuery and executes it on the database,
* returning the rows found.
*
*
* Like {@link #getAllRows() getAllRows()} this method retrieves all the rows
* for this DBQuery. However it checks the number of rows retrieved and throws
* a {@link UnexpectedNumberOfRowsException} if the number of rows retrieved
* differs from the expected number.
*
*
* Adds all required DBRows as inner join tables and all optional DBRow as
* outer join tables.
*
* Uses the defined
* {@link nz.co.gregs.dbvolution.annotations.DBForeignKey foreign keys} on the
* DBRow and multi-table conditions to connect the tables. Foreign keys that
* have been
* {@link nz.co.gregs.dbvolution.DBRow#ignoreForeignKey(java.lang.Object) ignored}
* are not used.
*
* Criteria such as
* {@link DBNumber#permittedValues(java.lang.Number...) permitted values}
* defined on the fields of the DBRow examples are added as part of the WHERE
* clause.
*
*
* Similarly conditions added to the DBQuery using
* {@link DBQuery#addCondition(nz.co.gregs.dbvolution.expressions.BooleanExpression) addCondition}
* are added.
*
* @param expectedRows - the number of rows expected to be retrieved
*
Support DBvolution at
* Patreon
* @return A List of DBQueryRows containing all the DBRow instances aligned
* with their related instances.
*
*
* Database exceptions may be thrown.
*
* @throws nz.co.gregs.dbvolution.exceptions.UnexpectedNumberOfRowsException
* @throws java.sql.SQLException
*/
public List getAllRows(long expectedRows) throws UnexpectedNumberOfRowsException, SQLException {
List allRows = getAllRows();
if (allRows.size() != expectedRows) {
throw new UnexpectedNumberOfRowsException(expectedRows, allRows.size());
} else {
return allRows;
}
}
/**
* Returns the current setting for ANSI join syntax.
*
*
* Indicates whether or not this query will use JOIN in the FROM clause or
* treat foreign keys as a constraint in the WHERE clause.
*
*
* N.B. Optional (outer) tables are only supported with ANSI syntax.
*
*
Support DBvolution at
* Patreon
*
* @return the useANSISyntax flag
*/
public boolean isUseANSISyntax() {
return details.getOptions().isUseANSISyntax();
}
/**
* Sets whether this DBQuery will use ANSI syntax.
*
*
* The default is to use ANSI syntax.
*
*
* You should probably use ANSI syntax.
*
*
* ANSI syntax has the foreign key and added relationships defined in the FROM
* clause with the JOIN operator. Pre-ANSI syntax treated the foreign keys and
* other relationships as part of the WHERE clause.
*
*
* ANSI syntax supports OUTER joins with a standard syntax, and DBvolution
* supports OUTER thru the ANSI syntax.
*
* @param useANSISyntax the useANSISyntax flag to set
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery setUseANSISyntax(boolean useANSISyntax) {
this.details.getOptions().setUseANSISyntax(useANSISyntax);
return this;
}
/**
* Creates a list of all DBRow subclasses that reference the DBRows within
* this query with foreign keys.
*
*
* Similar to {@link #getReferencedTables() } but where this class is being
* referenced by the external DBRow subclass.
*
*
* That is to say: where A is a DBRow in this query, returns a List of B such
* that B => A
*
*
Support DBvolution at
* Patreon
*
* @return a list of classes that have a {@code @DBForeignKey} reference to
* this class
* @see #getReferencedTables()
* @see DBRow#getRelatedTables()
* @see DBRow#getReferencedTables()
*/
public SortedSet getRelatedTables() throws UnableToInstantiateDBRowSubclassException {
SortedSet> resultClasses;
resultClasses = new TreeSet<>(new DBRowClassNameComparator());
SortedSet result = new TreeSet<>(new DBRowNameComparator());
for (DBRow table : details.getAllQueryTables()) {
SortedSet> allRelatedTables = table.getRelatedTables();
for (Class extends DBRow> connectedTable : allRelatedTables) {
try {
if (resultClasses.add(connectedTable)) {
result.add(connectedTable.newInstance());
}
} catch (IllegalAccessException | InstantiationException ex) {
throw new UnableToInstantiateDBRowSubclassException(connectedTable, ex);
}
}
}
return result;
}
/**
* Returns all the DBRow subclasses referenced by the DBrows within this query
* with foreign keys
*
*
* Similar to {@link #getAllConnectedTables() } but where this class directly
* references the external DBRow subclass with an {@code @DBForeignKey}
* annotation.
*
*
* That is to say: where A is A DBRow in this class, returns a List of B such
* that A => B
*
*
Support DBvolution at
* Patreon
*
* @return A list of DBRow subclasses referenced with {@code @DBForeignKey}
* @see #getRelatedTables()
* @see DBRow#getReferencedTables()
* @see DBRow#getRelatedTables()
*
*/
@SuppressWarnings("unchecked")
public SortedSet getReferencedTables() {
SortedSet result = new TreeSet<>(new DBRowNameComparator());
for (DBRow table : details.getAllQueryTables()) {
Set> allRelatedTables = table.getReferencedTables();
for (Class extends DBRow> connectedTable : allRelatedTables) {
try {
result.add(connectedTable.newInstance());
} catch (InstantiationException | IllegalAccessException ex) {
throw new UnableToInstantiateDBRowSubclassException(connectedTable, ex);
}
}
}
return result;
}
/**
* Returns all the DBRow subclasses used in this query.
*
* Support DBvolution at
* Patreon
*
* @return A list of DBRow subclasses included in this query.
* @see #getRelatedTables()
* @see #getReferencedTables()
* @see DBRow#getReferencedTables()
* @see DBRow#getRelatedTables()
*
*/
protected List getAllQueryTables() {
return details.getAllQueryTables();
}
/**
* Creates a list of all DBRow subclasses that are connected to this query.
*
*
* Uses {@link #getReferencedTables() } and {@link #getRelatedTables() } to
* produce a complete list of tables connected by foreign keys to the DBRow
* classes within this query.
*
*
* That is to say: where A is a DBRow in this query, returns a List of B such
* that B => A or A => B
*
*
Support DBvolution at
* Patreon
*
* @return a list of classes that have a {@code @DBForeignKey} reference to or
* from this class
* @see #getRelatedTables()
* @see #getReferencedTables()
* @see DBRow#getAllConnectedTables()
* @see DBRow#getReferencedTables()
* @see DBRow#getRelatedTables()
*
*/
public Set getAllConnectedTables() {
final Set result = getReferencedTables();
result.addAll(getRelatedTables());
return result;
}
/**
* Search the classpath and add any DBRow classes that are connected to the
* DBRows within this DBQuery
*
*
* This method automatically enlarges the query by finding all associated
* DBRow classes and adding them to the query.
*
*
* In a sense this expands the query out by one level of indirection.
*
*
* N.B. for any realistic database, repeatedly calling this method will
* quickly make the query impossibly large.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
* @throws UnableToInstantiateDBRowSubclassException
*/
public DBQuery addAllConnectedTables() throws UnableToInstantiateDBRowSubclassException {
List tablesToAdd = new ArrayList<>();
for (DBRow table : details.getAllQueryTables()) {
Set> allConnectedTables = table.getAllConnectedTables();
for (Class extends DBRow> connectedTable : allConnectedTables) {
try {
tablesToAdd.add(connectedTable.newInstance());
} catch (InstantiationException | IllegalAccessException ex) {
throw new UnableToInstantiateDBRowSubclassException(connectedTable, ex);
}
}
}
add(tablesToAdd.toArray(new DBRow[]{}));
return this;
}
/**
* Search the classpath and add any DBRow classes that are connected to the
* DBRows within this DBQuery
*
*
* This method automatically enlarges the query by finding all associated
* DBRow classes and adding them to the query.
*
*
* In a sense this expands the query out by one level of indirection.
*
*
* N.B. for any realistic database, repeatedly calling this method will
* quickly make the query impossibly large.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
* @throws UnableToInstantiateDBRowSubclassException
*/
public DBQuery addAllConnectedBaseTables() throws UnableToInstantiateDBRowSubclassException {
List tablesToAdd = new ArrayList<>();
for (DBRow table : details.getAllQueryTables()) {
Set> allConnectedTables = table.getAllConnectedBaseTables();
for (Class extends DBRow> connectedTable : allConnectedTables) {
try {
tablesToAdd.add(connectedTable.newInstance());
} catch (InstantiationException | IllegalAccessException ex) {
throw new UnableToInstantiateDBRowSubclassException(connectedTable, ex);
}
}
}
add(tablesToAdd.toArray(new DBRow[]{}));
return this;
}
/**
* Search the classpath and add, as optional, any DBRow classes that reference
* the DBRows within this DBQuery
*
*
* This method automatically enlarges the query by finding all associated
* DBRow classes and adding them to the query as optional tables.
*
*
* In a sense this expands the query out by one level of indirection.
*
*
* N.B. for any realistic database, repeatedly calling this method will
* quickly make the query impossibly large.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
* @throws UnableToInstantiateDBRowSubclassException
*/
public DBQuery addAllConnectedTablesAsOptional() throws UnableToInstantiateDBRowSubclassException {
Set tablesToAdd = new HashSet<>();
List> alreadyAddedClasses = new ArrayList<>();
for (DBRow table : details.getAllQueryTables()) {
@SuppressWarnings("unchecked")
Class aClass = (Class) table.getClass();
alreadyAddedClasses.add(aClass);
}
for (DBRow table : details.getAllQueryTables()) {
Set> allRelatedTables = table.getAllConnectedTables();
for (Class extends DBRow> relatedTable : allRelatedTables) {
DBRow newInstance;
try {
newInstance = relatedTable.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new UnableToInstantiateDBRowSubclassException(relatedTable, ex);
}
@SuppressWarnings("unchecked")
final Class newInstanceClass = (Class) newInstance.getClass();
if (!alreadyAddedClasses.contains(newInstanceClass)) {
tablesToAdd.add(newInstance);
alreadyAddedClasses.add(newInstanceClass);
}
}
}
addOptional(tablesToAdd.toArray(new DBRow[]{}));
return this;
}
/**
* Search the classpath and add, as optional, any DBRow classes that reference
* the DBRows within this DBQuery
*
*
* This method automatically enlarges the query by finding all associated
* DBRow classes and adding them to the query as optional tables.
*
*
* In a sense this expands the query out by one level of indirection.
*
*
* N.B. for any realistic database, repeatedly calling this method will
* quickly make the query impossibly large.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
* @throws UnableToInstantiateDBRowSubclassException
*/
public DBQuery addAllConnectedBaseTablesAsOptional() throws UnableToInstantiateDBRowSubclassException {
Set tablesToAdd = new HashSet<>();
List> alreadyAddedClasses = new ArrayList<>();
for (DBRow table : details.getAllQueryTables()) {
@SuppressWarnings("unchecked")
Class aClass = (Class) table.getClass();
alreadyAddedClasses.add(aClass);
}
for (DBRow table : details.getAllQueryTables()) {
Set> allRelatedTables = table.getAllConnectedBaseTables();
for (Class extends DBRow> relatedTable : allRelatedTables) {
// DBRow newInstance = relatedTable.newInstance();
DBRow newInstance;
try {
newInstance = relatedTable.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new UnableToInstantiateDBRowSubclassException(relatedTable, ex);
}
@SuppressWarnings("unchecked")
final Class newInstanceClass = (Class) newInstance.getClass();
if (!alreadyAddedClasses.contains(newInstanceClass)) {
tablesToAdd.add(newInstance);
alreadyAddedClasses.add(newInstanceClass);
}
}
}
addOptional(tablesToAdd.toArray(new DBRow[]{}));
return this;
}
/**
* Adds all the connected tables as branches, rather than a mesh.
*
*
* Adding connected tables means adding their connections as well. However
* sometimes you just want the tables added without connecting them to all the
* other tables correctly.
*
*
* This method adds all the connected tables as if they were only connected to
* the core tables and had no other relationships.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
* @throws UnableToInstantiateDBRowSubclassException
*/
public DBQuery addAllConnectedTablesAsOptionalWithoutInternalRelations() throws UnableToInstantiateDBRowSubclassException {
Set tablesToAdd = new HashSet<>();
List> alreadyAddedClasses = new ArrayList<>();
final List allQueryTables = details.getAllQueryTables();
DBRow[] originalTables = allQueryTables.toArray(new DBRow[]{});
for (DBRow table : allQueryTables) {
@SuppressWarnings("unchecked")
Class aClass = (Class) table.getClass();
alreadyAddedClasses.add(aClass);
}
for (DBRow table : allQueryTables) {
Set> allRelatedTables = table.getAllConnectedTables();
for (Class extends DBRow> relatedTable : allRelatedTables) {
// DBRow newInstance = relatedTable.newInstance();
DBRow newInstance;
try {
newInstance = relatedTable.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new UnableToInstantiateDBRowSubclassException(relatedTable, ex);
}
@SuppressWarnings("unchecked")
final Class newInstanceClass = (Class) newInstance.getClass();
if (!alreadyAddedClasses.contains(newInstanceClass)) {
newInstance.ignoreAllForeignKeysExceptFKsTo(originalTables);
tablesToAdd.add(newInstance);
alreadyAddedClasses.add(newInstanceClass);
}
}
}
addOptional(tablesToAdd.toArray(new DBRow[]{}));
return this;
}
/**
* Provides all the DBQueryRows that the instance provided is part of.
*
*
* This method returns the subset of this DBQuery's results that include the
* provided instance.
*
*
* Slicing the results like this allows you to get a list of, for instance,
* status table DBRows and then process the DBQueryRows that have each status
* DBRow as a block.
*
* @param instance the DBRow instance you are interested in.
*
Support DBvolution at
* Patreon
* @return A list of DBQueryRow instances that relate to the exemplar 1
* Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public List getAllRowsContaining(DBRow instance) throws SQLException {
final QueryOptions options = details.getOptions();
if (this.needsResults(options)) {
details.setQueryType(QueryType.SELECT);
database.executeDBQuery(details);
}
List returnList = new ArrayList<>();
for (DBQueryRow row : details.getResults()) {
if (row.get(instance) == instance) {
returnList.add(row);
}
}
return returnList;
}
/**
* Retrieves that DBQueryRows for the page supplied.
*
*
* DBvolution supports paging through this method. Use {@link #setRowLimit(int)
* } to set the page size and then call this method with the desired page
* number.
*
*
* This method is zero-based so the first page is getPage(0).
*
*
* Convenience method for {@link #getAllRowsForPage(java.lang.Integer) }.
*
* @param pageNumber pageNumber
*
Support DBvolution at
* Patreon
* @return a list of the DBQueryRows for the selected page. 1 Database
* exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public List getPage(Integer pageNumber) throws SQLException {
return getAllRowsForPage(pageNumber);
}
/**
* Retrieves that DBQueryRows for the page supplied.
*
*
* DBvolution supports paging through this method. Use {@link #setRowLimit(int)
* } to set the page size and then call this method with the desired page
* number.
*
*
* This method is zero-based so the first page is getAllRowsForPage(0).
*
* @param pageNumber pageNumber
*
Support DBvolution at
* Patreon
* @return a list of the DBQueryRows for the selected page. 1 Database
* exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
public List getAllRowsForPage(Integer pageNumber) throws SQLException {
final QueryOptions options = details.getOptions();
details.setQueryType(QueryType.ROWSFORPAGE);
details.setResultsPageIndex(pageNumber);
if (this.needsResults(options)) {
database.executeDBQuery(details);
}
return details.getCurrentPage();
}
/**
* Use this method to add complex conditions to the DBQuery.
*
*
* This method takes a BooleanExpression and adds it to the where clause of
* the Query
*
*
* The easiest way to get a BooleanExpression is the DBRow.column() method and
* then apply the functions you require until you get a BooleanExpression
* back.
*
*
* StringExpression, NumberExpression, DateExpression, and BooleanExpression
* all provide methods that will help. In particular they have the value()
* method to convert base Java types to expressions.
*
*
* Standard uses of this method are:
*
* addCondition(myRow.column(myRow.myColumn).like("%THis%"));
* addCondition(myRow.column(myRow.myNumber).cos().greaterThan(0.5));
* addCondition(StringExpression.value("THis").like(myRwo.column(myRow.myColumn)));
* addCondition(BooleanExpression.anyOf(
* myRow.column(myRow.myColumn).between("That", "This"),
* myRow.column(myRow.myColumn).is("Something"))
* );
*
*
* @param condition a boolean expression that defines a require limit on the
* results of the query
* Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addCondition(BooleanExpression condition) {
if (condition.isAggregator()) {
details.setHavingColumns(condition);
details.setGroupByRequiredByAggregator(true);
} else {
details.getConditions().add(condition);
}
blankResults();
return this;
}
/**
* Use this method to add complex conditions to the DBQuery.
*
*
* This method takes BooleanExpressions and adds them to the where clause of
* the Query
*
*
* The easiest way to get a BooleanExpression is the DBRow.column() method and
* then apply the functions you require until you get a BooleanExpression
* back.
*
*
* StringExpression, NumberExpression, DateExpression, and BooleanExpression
* all provide methods that will help. In particular they have the value()
* method to convert base Java types to expressions.
*
*
* Standard uses of this method are:
*
* addConditions(myRow.column(myRow.myColumn).like("%THis%"));
* addConditions(myRow.column(myRow.myNumber).cos().greaterThan(0.5));
* addConditions(StringExpression.value("THis").like(myRwo.column(myRow.myColumn)));
* addConditions(BooleanExpression.anyOf(
* myRow.columns(myRow.myColumn).between("That", "This"),
* myRow.columns(myRow.myColumn).is("Something"))
* );
*
*
* @param conditions boolean expressions that define required limits on the
* results of the query
* Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addConditions(BooleanExpression... conditions) {
for (BooleanExpression condition : conditions) {
addCondition(condition);
}
return this;
}
/**
* Use this method to add complex conditions to the DBQuery.
*
*
* This method takes BooleanExpressions and adds them to the where clause of
* the Query
*
*
* The easiest way to get a BooleanExpression is the DBRow.column() method and
* then apply the functions you require until you get a BooleanExpression
* back.
*
*
* StringExpression, NumberExpression, DateExpression, and BooleanExpression
* all provide methods that will help. In particular they have the value()
* method to convert base Java types to expressions.
*
*
* Standard uses of this method are:
*
* addConditions(myRow.column(myRow.myColumn).like("%THis%"));
* addConditions(myRow.column(myRow.myNumber).cos().greaterThan(0.5));
* addConditions(StringExpression.value("THis").like(myRwo.column(myRow.myColumn)));
* addConditions(BooleanExpression.anyOf(
* myRow.columns(myRow.myColumn).between("That", "This"),
* myRow.columns(myRow.myColumn).is("Something"))
* );
*
*
* @param conditions boolean expressions that define required limits on the
* results of the query
* Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addConditions(Collection conditions) {
for (BooleanExpression condition : conditions) {
addCondition(condition);
}
return this;
}
/**
* Remove all conditions from this query.
*
* @see #addCondition(nz.co.gregs.dbvolution.expressions.BooleanExpression)
* Support DBvolution at
* Patreon
* @return this DBQuery object
*/
public DBQuery clearConditions() {
details.getConditions().clear();
blankResults();
return this;
}
/**
* Set the query to return rows that match any conditions
*
*
* This means that all permitted*, excluded*, and comparisons are optional for
* any rows and rows will be returned if they match any of the conditions.
*
*
* The conditions will be connected by OR in the SQL.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
*/
public DBQuery setToMatchAnyCondition() {
details.getOptions().setMatchAnyConditions();
blankResults();
return this;
}
/**
* Set the query to return rows that match any relationship.
*
*
* This means that all foreign keys and ad hoc relationships are optional for
* all tables and rows will be returned if they match one of the
* relationships.
*
*
* The relationships will be connected by OR in the SQL.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
*/
public DBQuery setToMatchAnyRelationship() {
details.getOptions().setMatchAnyRelationship();
blankResults();
return this;
}
/**
* Set the query to return rows that match all relationships.
*
*
* This means that all foreign keys and ad hoc relationships are required for
* all tables and rows will be returned if they match all of the
* relationships.
*
*
* The relationships will be connected by AND in the SQL.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
*/
public DBQuery setToMatchAllRelationships() {
details.getOptions().setMatchAllRelationships();
blankResults();
return this;
}
/**
* Set the query to only return rows that match all conditions
*
*
* This is the default state
*
*
* This means that all permitted*, excluded*, and comparisons are required for
* any rows and the conditions will be connected by AND.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery instance
*/
public DBQuery setToMatchAllConditions() {
details.getOptions().setMatchAllConditions();
blankResults();
return this;
}
/**
* Automatically adds the example as a required table if it has criteria, or
* as an optional table otherwise.
*
*
* Any DBRow example passed to this method that has criteria specified on it,
* however vague, will become a required table on the query.
*
*
* Any DBRow example that has no criteria, i.e. where {@link DBRow#willCreateBlankQuery(nz.co.gregs.dbvolution.databases.definitions.DBDefinition)
* } is TRUE, will be added as an optional table.
*
*
* Warning: not specifying a required table will result in a FULL OUTER join
* which some database don't handle. You may want to test that the query is
* not blank after adding all your tables.
*
* @param exampleWithOrWithoutCriteria an example DBRow that should be added
* to the query as a required or optional table as appropriate.
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addOptionalIfNonspecific(DBRow exampleWithOrWithoutCriteria) {
if (exampleWithOrWithoutCriteria.willCreateBlankQuery(getReadyDatabase().getDefinition())) {
addOptional(exampleWithOrWithoutCriteria);
} else {
add(exampleWithOrWithoutCriteria);
}
return this;
}
/**
* Automatically adds the examples as required tables if they have criteria,
* or as an optional tables otherwise.
*
*
* Any DBRow example passed to this method that has criteria specified on it,
* however vague, will become a required table on the query.
*
*
* Any DBRow example that has no criteria, i.e. where {@link DBRow#willCreateBlankQuery(nz.co.gregs.dbvolution.databases.definitions.DBDefinition)
* } is TRUE, will be added as an optional table.
*
*
* Warning: not specifying a required table will result in a FULL OUTER join
* which some database don't handle. You may want to test that the query is
* not blank after adding all your tables.
*
* @param examplesWithOrWithoutCriteria Example DBRow objects that should be
* added to the query as a optional or required table as appropriate.
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addOptionalIfNonspecific(DBRow... examplesWithOrWithoutCriteria) {
for (DBRow dBRow : examplesWithOrWithoutCriteria) {
this.addOptionalIfNonspecific(dBRow);
}
return this;
}
/**
* Used by DBReport to add columns to the SELECT clause
*
* @param identifyingObject identifyingObject
* @param expressionToAdd expressionToAdd
* Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addExpressionColumn(Object identifyingObject, QueryableDatatype> expressionToAdd) {
details.getExpressionColumns().put(identifyingObject, expressionToAdd);
blankResults();
return this;
}
/**
* Used by DBReport to add columns to the GROUP BY clause.
*
* @param identifyingObject identifyingObject
* @param expressionToAdd expressionToAdd
* Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
protected DBQuery addGroupByColumn(Object identifyingObject, DBExpression expressionToAdd) {
details.getDBReportGroupByColumns().put(identifyingObject, expressionToAdd);
return this;
}
/**
* Clears the results and prepare the query to be re-run.
*
*/
protected void refreshQuery() {
blankResults();
}
void setRawSQL(String rawQuery) {
if (rawQuery == null) {
details.setRawSQLClause("");
} else {
details.setRawSQLClause(" " + rawQuery + " ");
}
}
/**
* Adds Extra Examples to the Query.
*
*
* The included DBRow instances will be used to add extra criteria as though
* they were an added table.
*
*
* Only useful for DBReports or queries that have been
* {@link DBQuery#setToMatchAnyCondition() set to match any of the conditions}.
*
*
* They will NOT be added as tables however, for that use
* {@link #add(nz.co.gregs.dbvolution.DBRow...) add and related methods}.
*
* @param extraExamples
*
Support DBvolution at
* Patreon
* @return this DBQuery with the extra examples added
*/
public DBQuery addExtraExamples(DBRow... extraExamples) {
this.details.getExtraExamples().addAll(Arrays.asList(extraExamples));
blankResults();
return this;
}
private void blankResults() {
details.setResults(null);
details.setResultSQL(null);
queryGraph = null;
}
/**
* Show the Graph window of the current QueryGraph.
*
*
* A pictorial representation to help you with diagnosing the issues with
* queries and to visualize what is actually being used by DBvolution.
*
*
* Internally DBvolution uses a graph to design the query that will be used.
* This graph is helpful for visualizing the underlying query, more so than an
* SQL query dump. So this method will display the query graph of this query
* at this time. The graph cannot be altered through the window but it can be
* moved to help show the parts of the graph. You can manipulate the query
* graph by
* {@link DBQuery#add(nz.co.gregs.dbvolution.DBRow[]) adding tables}, {@link DBQuery#addCondition(nz.co.gregs.dbvolution.expressions.BooleanExpression) using expressions that connect tables},
* or
* {@link DBRow#ignoreForeignKey(java.lang.Object) ignoring inappropriate foreign keys}.
*
*/
public void displayQueryGraph() {
initialiseQueryGraph();
Graph jungGraph = queryGraph.getJungGraph();
FRLayout layout = new FRLayout<>(jungGraph);
layout.setSize(new Dimension(550, 400));
VisualizationViewer vv = new VisualizationViewer<>(layout);
vv.setPreferredSize(new Dimension(600, 480));
DefaultModalGraphMouse gm = new DefaultModalGraphMouse<>();
gm.setMode(ModalGraphMouse.Mode.PICKING);
vv.setGraphMouse(gm);
RenderContext renderContext = vv.getRenderContext();
renderContext.setEdgeLabelTransformer(new QueryGraphEdgeLabelTransformer(this));
renderContext.setVertexLabelTransformer(new ToStringLabeller());
renderContext.setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.BLUE, false));
renderContext.setVertexFillPaintTransformer(new QueryGraphVertexFillPaintTransformer());
renderContext.setEdgeStrokeTransformer(new QueryGraphEdgeStrokeTransformer(this));
queryGraphFrame = new JFrame("DBQuery Graph");
queryGraphFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
queryGraphFrame.setResizable(true);
queryGraphFrame.getContentPane().add(vv);
queryGraphFrame.pack();
queryGraphFrame.setVisible(true);
}
private void initialiseQueryGraph() {
if (queryGraph == null) {
queryGraph = new QueryGraph(details.getRequiredQueryTables(), getConditions());
queryGraph.addOptionalAndConnectToRelevant(details.getOptionalQueryTables(), getConditions());
} else {
queryGraph.clear();
queryGraph.addAndConnectToRelevant(details.getRequiredQueryTables(), getConditions());
queryGraph.addOptionalAndConnectToRelevant(details.getOptionalQueryTables(), getConditions());
}
}
/**
* Hides and disposes of the QueryGraph window.
*
*
* After calling {@link #displayQueryGraph() }, you should call this method to
* close the window automatically.
*
*
* If the window has closed already, this method has no effect.
*
*/
public void closeQueryGraph() {
if (queryGraphFrame != null) {
queryGraphFrame.setVisible(false);
queryGraphFrame.dispose();
}
}
/**
*
Support DBvolution at
* Patreon
*
* @return the conditions
*/
private List getConditions() {
return details.getConditions();
}
/**
* Returns the unique values for the column in the database.
*
*
* Creates a query that finds the distinct values that are used in the
* field/column supplied.
*
*
* Some tables use repeated values instead of foreign keys or do not use all
* of the possible values of a foreign key. This method makes it easy to find
* the distinct or unique values that are used.
*
* @param fieldsOfProvidedRows - the field/column that you need data for.
*
Support DBvolution at
* Patreon
* @return a list of DBQQueryRows with distinct combinations of values used in
* the columns. 1 Database exceptions may be thrown
* @throws java.sql.SQLException java.sql.SQLException
*/
@SuppressWarnings({"unchecked", "empty-statement"})
public List getDistinctCombinationsOfColumnValues(Object... fieldsOfProvidedRows) throws AccidentalBlankQueryException, SQLException {
List returnList = new ArrayList<>();
DBQuery distinctQuery = database.getDBQuery();
for (DBRow row : details.getRequiredQueryTables()) {
final DBRow copyDBRow = DBRow.copyDBRow(row);
copyDBRow.removeAllFieldsFromResults();
distinctQuery.add(copyDBRow);
}
for (DBRow row : details.getOptionalQueryTables()) {
final DBRow copyDBRow = DBRow.copyDBRow(row);
copyDBRow.removeAllFieldsFromResults();
distinctQuery.add(copyDBRow);
}
for (Object fieldOfProvidedRow : fieldsOfProvidedRows) {
PropertyWrapper fieldProp = null;
for (DBRow row : details.getAllQueryTables()) {
fieldProp = row.getPropertyWrapperOf(fieldOfProvidedRow);
if (fieldProp != null) {
break;
}
}
if (fieldProp == null) {
throw new nz.co.gregs.dbvolution.exceptions.IncorrectRowProviderInstanceSuppliedException();
} else {
final PropertyWrapperDefinition fieldDefn = fieldProp.getPropertyWrapperDefinition();
DBRow fieldRow = null;
Object thisQDT = null;
for (DBRow row : distinctQuery.details.getAllQueryTables()) {
try {
thisQDT = fieldDefn.rawJavaValue(row);
} catch (FailedToSetPropertyValueOnRowDefinition ex) {
;// not worried about it
}
if (thisQDT != null) {
fieldRow = row;
break;
}
}
if (thisQDT != null && fieldRow != null) {
fieldRow.addReturnFields(thisQDT);
distinctQuery.setBlankQueryAllowed(true);
final ColumnProvider column = fieldRow.column(fieldDefn.getQueryableDatatype(fieldRow));
distinctQuery.addToSortOrder(column);
distinctQuery.addGroupByColumn(fieldRow, column.getColumn().asExpression());
returnList = distinctQuery.getAllRows();
} else {
throw new DBRuntimeException("Unable To Find Columns Specified");
}
}
}
return returnList;
}
/**
* Return a list of all tables, required or optional, used in this query.
*
* Support DBvolution at
* Patreon
*
* @return all DBRows used in this DBQuery
*/
public List getAllTables() {
ArrayList arrayList = new ArrayList<>();
arrayList.addAll(details.getAllQueryTables());
return arrayList;
}
/**
* Return a list of all the required tables used in this query.
*
* Support DBvolution at
* Patreon
*
* @return all DBRows required by this DBQuery
*/
public List getRequiredTables() {
ArrayList arrayList = new ArrayList<>();
arrayList.addAll(details.getRequiredQueryTables());
return arrayList;
}
/**
* Return a list of all the optional tables used in this query.
*
* Support DBvolution at
* Patreon
*
* @return all DBRows optionally returned by this DBQuery
*/
public List getOptionalTables() {
ArrayList arrayList = new ArrayList<>();
arrayList.addAll(details.getOptionalQueryTables());
return arrayList;
}
/**
* DBQuery and DBtable are 2 of the few classes that rely on knowing the
* database they work on.
*
*
* This method allows you to retrieve the database used when you execute this
* query.
*
*
Support DBvolution at
* Patreon
*
* @return the database used during execution of this query.
*/
public DBDatabase getDatabase() {
return database;
}
/**
* DBQuery and DBtable are 2 of the few classes that rely on knowing the
* database they work on.
*
*
* This method allows you to retrieve the database used when you execute this
* query.
*
*
Support DBvolution at
* Patreon
*
* @return the database used during execution of this query.
*/
public DBDefinition getDatabaseDefinition() {
return database.getDefinition();
}
/**
* Add tables that will be used in the query but are already part of an outer
* query and need not be explicitly added to the SQL.
*
*
* Used during recursive queries. If you are not manually constructing a
* recursive query do NOT use this method.
*
*
* Also used by the {@link ExistsExpression}.
*
* @param tables tables
*
Support DBvolution at
* Patreon
* @return this DBQuery object.
*/
public DBQuery addAssumedTables(List tables) {
return addAssumedTables(tables.toArray(new DBRow[]{}));
}
/**
* Add tables that will be used in the query but are already part of an outer
* query and need not be explicitly added to the SQL.
*
*
* Used during recursive queries. If you are not manually constructing a
* recursive query do NOT use this method.
*
*
* Also used by the {@link ExistsExpression}.
*
* @param tables tables
*
Support DBvolution at
* Patreon
* @return this DBQuery object.
*/
public DBQuery addAssumedTables(DBRow... tables) {
for (DBRow table : tables) {
details.getAssumedQueryTables().add(table);
details.getAllQueryTables().add(table);
blankResults();
}
return this;
}
/**
* Adds optional tables to this query
*
*
* This method adds optional (OUTER) tables to the query.
*
*
* The query will return an instance of these DBRows for each row found,
* though it may be a null instance as there was no matching row in the
* database.
*
*
* Criteria (permitted and excluded values) specified in the supplied instance
* will be added to the query.
*
* @param optionalQueryTables a list of DBRow objects that defines optional
* tables and criteria
*
*
Support DBvolution at
* Patreon
* @return this DBQuery instance
*/
public DBQuery addOptional(List optionalQueryTables) {
for (DBRow optionalQueryTable : optionalQueryTables) {
this.addOptional(optionalQueryTable);
}
return this;
}
/**
* Ignores the foreign key of the column provided.
*
* Similar to {@link DBRow#ignoreForeignKey(java.lang.Object) } but uses a
* ColumnProvider which is portable between instances of DBRow.
*
* For example the following code snippet will ignore the foreign key provided
* by a different instance of Customer:
*
* Customer customer = new Customer();
* IntegerColumn addressColumn = customer.column(customer.fkAddress);
* Customer cust2 = new Customer();
* cust2.ignoreForeignKey(addressColumn);
*
*
* @param foreignKeyToFollow the foreign key to ignore
* Support DBvolution at
* Patreon
* @return This DBQuery object
*/
public DBQuery ignoreForeignKey(ColumnProvider foreignKeyToFollow) {
Set tablesInvolved = foreignKeyToFollow.getColumn().getTablesInvolved();
for (DBRow fkTable : tablesInvolved) {
for (DBRow table : details.getAllQueryTables()) {
if (fkTable.getClass().equals(table.getClass())) {
table.ignoreForeignKey(foreignKeyToFollow);
}
}
}
return this;
}
/**
* Changes the default timeout for this query.
*
*
* DBvolution defaults to a timeout of 10000milliseconds (10 seconds) to avoid
* eternal queries.
*
*
* Use this method If you require a longer running query.
*
* @param milliseconds
*
Support DBvolution at
* Patreon
* @return this query.
*/
public synchronized DBQuery setTimeoutInMilliseconds(Integer milliseconds) {
details.setTimeoutInMilliseconds(milliseconds);
return this;
}
/**
* Completely removes the timeout from this query.
*
*
* DBvolution defaults to a timeout of 10000milliseconds (10 seconds) to avoid
* eternal queries.
*
*
* Use this method if you expect an extremely long query.
*
*
Support DBvolution at
* Patreon
*
* @return this DBQuery object
*/
public synchronized DBQuery clearTimeout() {
details.setTimeoutInMilliseconds(null);
return this;
}
/**
* Tags all the fields in the DBQuery so that they are not retrieved in the
* query.
*
*
* All fields will be excluded from the SQL and the returned rows will be
* effectively a NULL row, however tables and fields will still be used in the
* query to set conditions.
*
* @return this query object
*/
public DBQuery setReturnFieldsToNone() {
for (DBRow table : this.getAllTables()) {
table.setReturnFieldsToNone();
}
return this;
}
public DBQuery setReturnFields(ColumnProvider... columns) {
setReturnFieldsToNone();
List allQueryTables = this.details.getAllQueryTables();
for (ColumnProvider provider : columns) {
if (provider instanceof QueryColumn) {
// QueryColumn,?,?> qc = (QueryColumn)provider;
// qc.setReturnField(true);
} else {
final AbstractColumn column = provider.getColumn();
DBRow table = column.getInstanceOfRow();
for (DBRow allQueryTable : allQueryTables) {
if (allQueryTable.getClass().equals(table.getClass())) {
Object appropriateFieldFromRow = column.getAppropriateFieldFromRow(allQueryTable);
allQueryTable.addReturnFields(appropriateFieldFromRow);
}
}
}
}
return this;
}
@SuppressWarnings("unchecked")
public ColumnProvider column(QueryableDatatype> qdt) {
List tables = getAllQueryTables();
for (DBRow table : tables) {
try{
return table.column(qdt);
}catch (IncorrectRowProviderInstanceSuppliedException exp){;}
}
Map> expressionColumns = details.getExpressionColumns();
for (QueryableDatatype> entry : expressionColumns.values()) {
if (entry.equals(qdt)){
return new QueryColumn(this, entry);
}
}
throw new IncorrectRowProviderInstanceSuppliedException("the object provided could not be found in the table or expressions used in this query, please supply a QDT used by the tables or adde to the query as an expression column.");
}
/**
* Sets the query to retrieve that DBQueryRows for the page supplied.
*
*
* DBvolution supports paging through this method. Use {@link #setRowLimit(int)
* } to set the page size and then call this method with the desired page
* number.
*
*
* This method is zero-based so the first page is getAllRowsForPage(0).
*
* @param pageNumberZeroBased pageNumber
*
Support DBvolution at
* Patreon
* @return a list of the DBQueryRows for the selected page. 1 Database
* exceptions may be thrown
*/
public DBQuery setPageRequired(int pageNumberZeroBased) {
details.setQueryType(QueryType.ROWSFORPAGE);
details.getOptions().setPageIndex(pageNumberZeroBased);
return this;
}
}