/*
* @copyright 2012 Philip Warner
* @license GNU General Public License
*
* This file is part of Book Catalogue.
*
* Book Catalogue is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Book Catalogue is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>.
*/
package com.eleybourn.bookcatalogue.database;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map.Entry;
import com.eleybourn.bookcatalogue.database.DbSync.SynchronizedDb;
import com.eleybourn.bookcatalogue.database.DbSync.SynchronizedStatement;
/**
* Utilities and classes to make defining databases a little easier and provide synchronization across threads.
*
* @author Philip Warner
*/
public class DbUtils {
/**
* Class to store domain name and definition.
*
* @author Philip Warner
*/
public static class DomainDefinition {
public String name;
public String type;
public String extra;
public String constraint;
public DomainDefinition(String name, String type, String extra, String constraint) {
this.name = name;
this.type = type;
this.extra = extra;
this.constraint = constraint;
}
/** useful for using the DomainDefinition in place of a domain name */
@Override
public String toString() {
return name;
}
/** Get the SQL used to define this domain */
public String getDefinition(boolean withConstraints) {
String s = name + " " + type + " " + extra;
if (withConstraints)
s += " " + constraint;
return s;
}
}
/**
* Class used to build complex joins. Maintaing context and uses foreign keys
* to automatically build standard joins.
*
* @author Philip Warner
*/
public static class JoinContext {
/** Last table added to join */
TableDefinition currentTable;
/** Text of join statement */
final StringBuilder sql;
/**
* Constructor.
*
* @param table Table that starts join
*/
public JoinContext(TableDefinition table) {
currentTable = table;
sql = new StringBuilder();
}
/**
* Add a new table to the join, connecting it to previous table using foreign keys
*
* @param to New table to add
*
* @return Join object (for chaining)
*/
public JoinContext join(TableDefinition to) {
sql.append(currentTable.join(to));
sql.append('\n');
currentTable = to;
return this;
}
/**
* Add a new table to the join, connecting it to 'from' using foreign keys
*
* @param from Parent table in join
* @param to New table to join
*
* @return Join object (for chaining)
*/
public JoinContext join(TableDefinition from, TableDefinition to) {
sql.append(from.join(to));
sql.append('\n');
currentTable = to;
return this;
}
/**
* Same as 'join', but do a 'left outer' join.
*
* @param to New table to add.
*
* @return Join object (for chaining)
*/
public JoinContext leftOuterJoin(TableDefinition to) {
sql.append(" left outer ");
return join(to);
}
/**
* Same as 'join', but do a 'left outer' join.
*
* @param from Parent table in join
* @param to New table to join
*
* @return Join object (for chaining)
*/
public JoinContext leftOuterJoin(TableDefinition from, TableDefinition to) {
sql.append(" left outer ");
return join(from, to);
}
/**
* Begin building the join using the current table.
*
* @return Join object (for chaining)
*/
public JoinContext start() {
sql.append( currentTable.getName() + " " + currentTable.getAlias() );
return this;
}
/**
* Append arbitrary text to the generated SQL. Useful for adding extra conditions
* to a join clause.
*
* @param sql Extra SQL to append
*
* @return Join object (for chaining)
*/
public JoinContext append(String sql) {
this.sql.append(sql);
return this;
}
/**
* Get the current SQL
*/
@Override
public String toString() {
return sql.toString();
}
}
/**
* Class to store table name and a list of domain definitions.
*
* @author Philip Warner
*/
public static class TableDefinition {
public enum TableTypes { Standard, Temporary, FTS3, FTS4 };
/** Table name */
private String mName;
/** Table alias */
private String mAlias;
/** List of domains in this table */
private ArrayList<DomainDefinition> mDomains = new ArrayList<DomainDefinition>();
/** Used for checking if a domain has already been added */
private HashSet<DomainDefinition> mDomainCheck = new HashSet<DomainDefinition>();
/** Used for checking if a domain NAME has already been added */
private Hashtable<String, DomainDefinition> mDomainNameCheck = new Hashtable<String, DomainDefinition>();
/** List of domains forming primary key */
private ArrayList<DomainDefinition> mPrimaryKey = new ArrayList<DomainDefinition>();
/** List of parent tables (tables referred to by foreign keys on this table) */
private Hashtable<TableDefinition, FkReference> mParents = new Hashtable<TableDefinition, FkReference>();
/** List of child tables (tables referring to by foreign keys to this table) */
private Hashtable<TableDefinition, FkReference> mChildren = new Hashtable<TableDefinition, FkReference>();
/** List of index definitions for this table */
Hashtable<String, IndexDefinition> mIndexes = new Hashtable<String, IndexDefinition>();
/** Flag indicating table is temporary */
private TableTypes mType = TableTypes.Standard;
/**
* Accessor. Return list of domains.
*
* @return
*/
public ArrayList<DomainDefinition> getDomains() {
return mDomains;
}
/**
* Remove all references and resources used by this table.
*/
public void close() {
mDomains.clear();
mDomainCheck.clear();
mDomainNameCheck.clear();
mPrimaryKey.clear();
mIndexes.clear();
// Need to make local copies to avoid 'collection modified' errors
ArrayList<TableDefinition> tmpParents = new ArrayList<TableDefinition>();
for(Entry<TableDefinition, FkReference> fkEntry : mParents.entrySet()) {
FkReference fk = fkEntry.getValue();
tmpParents.add(fk.parent);
}
for(TableDefinition parent: tmpParents) {
removeReference(parent);
}
// Need to make local copies to avoid 'collection modified' errors
ArrayList<TableDefinition> tmpChildren = new ArrayList<TableDefinition>();
for(Entry<TableDefinition, FkReference> fkEntry : mChildren.entrySet()) {
FkReference fk = fkEntry.getValue();
tmpChildren.add(fk.child);
}
for(TableDefinition child: tmpChildren) {
child.removeReference(this);
}
}
/**
* Make a copy of this table.
*/
public TableDefinition clone() {
TableDefinition newTbl = new TableDefinition();
newTbl.setName(mName);
newTbl.setAlias(mAlias);
newTbl.addDomains(mDomains);
newTbl.setPrimaryKey(mPrimaryKey);
newTbl.setType(mType);
for(Entry<TableDefinition, FkReference> fkEntry : mParents.entrySet()) {
FkReference fk = fkEntry.getValue();
newTbl.addReference(fk.parent, fk.domains);
}
for(Entry<TableDefinition, FkReference> fkEntry : mChildren.entrySet()) {
FkReference fk = fkEntry.getValue();
fk.child.addReference(newTbl, fk.domains);
}
for(Entry<String, IndexDefinition> e: mIndexes.entrySet()) {
IndexDefinition i = e.getValue();
newTbl.addIndex(e.getKey(), i.getUnique(), i.getDomains());
}
return newTbl;
}
/**
* Accessor. Get indexes on this table.
*
* @return
*/
public Collection<IndexDefinition> getIndexes() {
return mIndexes.values();
}
/**
* Class used to represent a foreign key reference
*
* @author Philip Warner
*/
private class FkReference {
/** Owner of primary key in FK reference */
TableDefinition parent;
/** Table owning FK */
TableDefinition child;
/** Domains in the FK that reference the parent PK */
ArrayList<DomainDefinition> domains;
/**
* Constructor.
*
* @param parent Parent table (one with PK that FK references)
* @param child Child table (owner of the FK)
* @param domains Domains in child table that reference PK in parent
*/
FkReference(TableDefinition parent, TableDefinition child, DomainDefinition...domains) {
this.domains = new ArrayList<DomainDefinition>();
for(DomainDefinition d: domains)
this.domains.add(d);
this.parent = parent;
this.child = child;
}
/**
* Constructor
*
* @param parent Parent table (one with PK that FK references)
* @param child Child table (owner of the FK)
* @param domains Domains in child table that reference PK in parent
*/
FkReference(TableDefinition parent, TableDefinition child, ArrayList<DomainDefinition> domains) {
this.domains = new ArrayList<DomainDefinition>();
for(DomainDefinition d: domains)
this.domains.add(d);
this.parent = parent;
this.child = child;
}
/**
* Get an SQL fragment that matches the PK of the parent to the FK of the child.
* eg. 'org.id = emp.organization_id' (but handles multi-domain keys)
*
* @return SQL fragment
*/
public String getPredicate() {
ArrayList<DomainDefinition> pk = parent.getPrimaryKey();
StringBuilder sql = new StringBuilder();
for(int i = 0; i < pk.size(); i++) {
if (i > 0)
sql.append(" and ");
sql.append(parent.getAlias());
sql.append(".");
sql.append(pk.get(i).name);
sql.append(" = ");
sql.append(child.getAlias());
sql.append(".");
sql.append(domains.get(i).name);
}
return sql.toString();
}
}
/**
* Constructor
*
* @param name Table name
* @param domains List of domains in table
*/
public TableDefinition(String name, DomainDefinition... domains) {
this.mName = name;
this.mAlias = name;
this.mDomains = new ArrayList<DomainDefinition>();
for(DomainDefinition d: domains)
this.mDomains.add(d);
}
/**
* Constructor (empty table)
*/
public TableDefinition() {
this.mName = "";
this.mDomains = new ArrayList<DomainDefinition>();
}
/**
* Accessor. Get the table name.
* @return
*/
public String getName() {
return mName;
}
/**
* Accessor. Get the table alias, or if blank, return the table name.
*
* @return Alias
*/
public String getAlias() {
if (mAlias == null || mAlias.equals(""))
return getName();
else
return mAlias;
}
/**
* Utility routine to return <table-alias>.<domain-name>.
*
* @param d Domain
*
* @return SQL fragment
*/
public String dot(DomainDefinition d) {
return getAlias() + "." + d.name;
}
/**
* Utility routine to return [table-alias].[domain-name] as [domain_name]; this format
* is useful in older SQLite installations that add make the alias part of the output
* column name.
*
* @param d Domain
*
* @return SQL fragment
*/
public String dotAs(DomainDefinition d) {
return getAlias() + "." + d.name + " as " + d.name;
}
/**
* Utility routine to return [table-alias].[domain-name] as [asDomain]; this format
* is useful when multiple differing versions of a domain are retrieved.
*
* @param d Domain
*
* @return SQL fragment
*/
public String dotAs(DomainDefinition d, DomainDefinition asDomain) {
return getAlias() + "." + d.name + " as " + asDomain.name;
}
/**
* Utility routine to return <table-alias>.<name>.
*
* @param s Domain name
*
* @return SQL fragment
*/
public String dot(String s) {
return getAlias() + "." + s;
}
/**
* Set the table name. Useful for cloned tables.
*
* @param newName New table name
*
* @return TableDefinition (for chaining)
*/
public TableDefinition setName(String newName) {
this.mName = newName;
return this;
}
/**
* Set the table alias. Useful for cloned tables.
*
* @param newAlias New table alias
*
* @return TableDefinition (for chaining)
*/
public TableDefinition setAlias(String newAlias) {
mAlias = newAlias;
return this;
}
/**
* Set the primary key domains
*
* @param domains List of domains in PK
*
* @return TableDefinition (for chaining)
*/
public TableDefinition setPrimaryKey(DomainDefinition...domains) {
mPrimaryKey.clear();
for(DomainDefinition d: domains)
mPrimaryKey.add(d);
return this;
}
/**
* Set the primary key domains
*
* @param domains List of domains in PK
*
* @return TableDefinition (for chaining)
*/
public TableDefinition setPrimaryKey(ArrayList<DomainDefinition> domains) {
mPrimaryKey.clear();
for(DomainDefinition d: domains)
mPrimaryKey.add(d);
return this;
}
/**
* Common code to add a foreign key (FK) references to another (parent) table.
*
* @param fk The FK object
*
* @return TableDefinition (for chaining)
*/
private TableDefinition addReference(FkReference fk) {
if (fk.child != this)
throw new RuntimeException("Foreign key does not include this table as child");
mParents.put(fk.parent, fk);
fk.parent.addChild(this, fk);
return this;
}
/**
* Add a foreign key (FK) references to another (parent) table.
*
* @param parent The referenced table
* @param domains Domains in this table that reference Primary Key (PK) in parent
*
* @return TableDefinition (for chaining)
*/
public TableDefinition addReference(TableDefinition parent, DomainDefinition...domains) {
FkReference fk = new FkReference(parent, this, domains);
return addReference(fk);
}
/**
* Add a foreign key (FK) references to another (parent) table.
*
* @param parent The referenced table
* @param domains Domains in this table that reference Primary Key (PK) in parent
*
* @return TableDefinition (for chaining)
*/
public TableDefinition addReference(TableDefinition parent, ArrayList<DomainDefinition> domains) {
FkReference fk = new FkReference(parent, this, domains);
return addReference(fk);
}
/**
* Remove FK reference to parent table
*
* @param parent The referenced Table
*
* @return TableDefinition (for chaining)
*/
public TableDefinition removeReference(TableDefinition parent) {
mParents.remove(parent);
parent.removeChild(this);
return this;
}
/**
* Add a child table reference to this table.
*
* @param child Child table
* @param fk FK object
*
* @return TableDefinition (for chaining)
*/
private TableDefinition addChild(TableDefinition child, FkReference fk) {
if (!mChildren.containsKey(child))
mChildren.put(child, fk);
return this;
}
/**
* Remove a child FK reference from this table.
*
* @param child Child table
*
* @return TableDefinition (for chaining)
*/
private TableDefinition removeChild(TableDefinition child) {
mChildren.remove(child);
return this;
}
/**
* Add a domain to this table
*
* @param domain Domain object to add
*
* @return TableDefinition (for chaining)
*/
public TableDefinition addDomain(DomainDefinition domain) {
// Make sure it's not already in the table
if (mDomainCheck.contains(domain))
return this;
// Make sure one with same name is not already in table
if (mDomainNameCheck.contains(domain.name.toLowerCase()))
throw new RuntimeException("A domain with that name has already been added");
// Add it
mDomains.add(domain);
mDomainCheck.add(domain);
mDomainNameCheck.put(domain.name, domain);
return this;
}
/**
* Add a list of domains to this table.
*
* @param domains List of domains to add
*
* @return TableDefinition (for chaining)
*/
public TableDefinition addDomains(DomainDefinition... domains) {
for(DomainDefinition d: domains)
addDomain(d);
return this;
}
/**
* Add a list of domains to this table.
*
* @param domains List of domains to add
*
* @return TableDefinition (for chaining)
*/
public TableDefinition addDomains(ArrayList<DomainDefinition> domains) {
for(DomainDefinition d: domains)
addDomain(d);
return this;
}
/**
* Add an index to this table
*
* @param localKey Local name, unique for this table, to give this index. Alphanumeric Only.
* @param unique FLag indicating index is UNIQUE
* @param domains List of domains index
*
* @return TableDefinition (for chaining)
*/
public TableDefinition addIndex(String localKey, boolean unique, DomainDefinition...domains) {
// Make sure not already defined
if (mIndexes.containsKey(localKey))
throw new RuntimeException("Index with local name '" + localKey + "' already defined");
// Construct the full index name
String name = this.mName + "_IX" + (mIndexes.size()+1) + "_" + localKey;
mIndexes.put(localKey, new IndexDefinition(name, unique, this, domains));
return this;
}
/**
* Static method to drop the passed table, if it exists.
*
* @param db
* @param name
*/
public static void drop(SynchronizedDb db, String name) {
db.execSQL("Drop Table If Exists " + name);
}
/**
* Drop this table from the passed DB.
*
* @param db
* @return
*/
public TableDefinition drop(SynchronizedDb db) {
drop(db, mName);
return this;
}
/**
* Create this table.
*
* @param db Database in which to create table
* @param withConstraints Indicates if fields should have constraints applied
*
* @return TableDefinition (for chaining)
*/
public TableDefinition create(SynchronizedDb db, boolean withConstraints) {
db.execSQL(this.getSql(mName, withConstraints, false));
return this;
}
/**
* Create this table and related objects (indices).
*
* @param db Database in which to create table
* @param withConstraints Indicates if fields should have constraints applied
*
* @return TableDefinition (for chaining)
*/
public TableDefinition createAll(SynchronizedDb db, boolean withConstraints) {
db.execSQL(this.getSql(mName, withConstraints, false));
createIndices(db);
return this;
}
/**
* Create this table if it is not already present.
*
* @param db Database in which to create table
* @param withConstraints Indicates if fields should have constraints applied
*
* @return TableDefinition (for chaining)
*/
public TableDefinition createIfNecessary(SynchronizedDb db, boolean withConstraints) {
db.execSQL(this.getSql(mName, withConstraints, true));
return this;
}
/**
* Get a base INSERT statement for this table using the passed list of domains. Returns partial
* SQL of the form: 'INSERT into [table-name] ( [domain-list] )'.
*
* @param domains List of domains to use
*
* @return SQL fragment
*/
public String getInsert(DomainDefinition...domains) {
StringBuilder s = new StringBuilder("Insert Into ");
s.append(mName);
s.append(" (\n");
s.append(" ");
s.append(domains[0]);
for(int i = 1; i < domains.length; i++) {
s.append(",\n ");
s.append(domains[i].toString());
}
s.append(")");
return s.toString();
}
/**
* Get a base list of fields for this table using the passed list of domains. Returns partial
* SQL of the form: '[alias].[domain-1], ..., [alias].[domain-n]'.
*
* @param domains List of domains to use
*
* @return SQL fragment
*/
public String ref(DomainDefinition...domains) {
if (domains == null || domains.length == 0)
return "";
final String aliasDot = getAlias() + ".";
final StringBuilder s = new StringBuilder(aliasDot);
s.append(domains[0].name);
for(int i = 1; i < domains.length; i++) {
s.append(",\n");
s.append(aliasDot);
s.append(domains[i].name);
}
return s.toString();
}
/**
* Get a base UPDATE statement for this table using the passed list of domains. Returns partial
* SQL of the form: 'UPDATE [table-name] Set [domain-1] = ?, ..., [domain-n] = ?'.
*
* @param domains List of domains to use
*
* @return SQL fragment
*/
public String getUpdate(DomainDefinition...domains) {
StringBuilder s = new StringBuilder("Update ");
s.append(mName);
s.append(" Set\n");
s.append(" ");
s.append(domains[0]);
s.append(" = ?");
for(int i = 1; i < domains.length; i++) {
s.append(",\n ");
s.append(domains[i].toString());
s.append(" = ?");
}
s.append("\n");
return s.toString();
}
/**
* Get a base 'INSERT or REPLACE' statement for this table using the passed list of domains. Returns partial
* SQL of the form: 'INSERT or REPLACE INTO [table-name] ( [domain-1] ) Values (?, ..., ?)'.
*
* @param domains List of domains to use
*
* @return SQL fragment
*/
public String getInsertOrReplaceValues(DomainDefinition...domains) {
StringBuilder s = new StringBuilder("Insert or Replace Into ");
StringBuilder sPlaceholders = new StringBuilder("?");
s.append(mName);
s.append(" ( ");
s.append(domains[0]);
for(int i = 1; i < domains.length; i++) {
s.append(", ");
s.append(domains[i].toString());
sPlaceholders.append(", ?");
}
s.append(")\n values (");
s.append(sPlaceholders.toString());
s.append(")\n");
return s.toString();
}
/**
* Setter. Set flag indicating table is a TEMPORARY table.
*
* @param flag Flag
*
* @return TableDefinition (for chaining)
*/
public TableDefinition setType(TableTypes type) {
mType = type;
return this;
}
/** useful for using the TableDefinition in place of a table name */
@Override
public String toString() {
return mName;
}
/**
* Return the SQL that can be used to define this table.
*
* @param name Name to use for table
* @param withConstraints Flag indicating domian constraints should be applied
* @param ifNecessary Flag indicating if creation should not be done if table exists
*
* @return SQL to create table
*/
private String getSql(String name, boolean withConstraints, boolean ifNecessary) {
StringBuilder sql = new StringBuilder("Create ");
switch(mType) {
case Standard:
break;
case FTS3:
case FTS4:
sql.append("Virtual ");
break;
case Temporary:
sql.append("Temporary ");
break;
}
sql.append("Table ");
if (ifNecessary) {
if (mType == TableTypes.FTS3 || mType == TableTypes.FTS4)
throw new RuntimeException("'if not exists' can not be used when creating virtual tables");
sql.append("if not exists ");
}
sql.append(name);
if (mType == TableTypes.FTS3) {
sql.append(" USING fts3");
} else if (mType == TableTypes.FTS3) {
sql.append(" USING fts4");
}
sql.append(" (\n");
boolean first = true;
for(DomainDefinition d : mDomains) {
if (first) {
first = false;
} else {
sql.append(",\n");
}
sql.append(" ");
sql.append(d.getDefinition(withConstraints));
}
sql.append(")\n");
return sql.toString();
}
/**
* Utility code to return a join and condition from this table to another using foreign keys.
*
* @param to Table this table will be joined with
*
* @return SQL fragment (eg. 'join [to-name] [to-alias] On [pk/fk match]')
*/
public String join(TableDefinition to) {
return " join " + to.ref() + " On (" + fkMatch(to) + ")";
}
/**
* Return the FK condition that applies between this table and the 'to' table
*
* @param to Table that is other part of FK/PK
*
* @return SQL fragment (eg. <to-alias>.<to-pk> = <from-alias>.<from-pk>').
*/
public String fkMatch(TableDefinition to) {
FkReference fk;
if (mChildren.containsKey(to)) {
fk = mChildren.get(to);
} else {
fk = mParents.get(to);
}
if (fk == null)
throw new RuntimeException("No foreign key between '" + this.getName() + "' and '" + to.getName() + "'");
return fk.getPredicate();
}
/**
* Accessor. Get the domains forming the PK of this table.
*
* @return Domain List
*/
public ArrayList<DomainDefinition> getPrimaryKey() {
return mPrimaryKey;
}
/**
* Utility routine to return an SQL fragment of the form '<table-name> <table-alias>', eg. 'employees e'.
*
* @return SQL Fragment
*/
public String ref() {
return mName + " " + getAlias();
}
/**
* Create all indices defined for this table.
*
* @param db Database to use
*
* @return TableDefinition (for chaining)
*/
public TableDefinition createIndices(SynchronizedDb db) {
for (IndexDefinition i : getIndexes()) {
db.execSQL(i.getSql());
}
return this;
}
final static String mExistsSql = "Select (SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?) + "
+ "(SELECT count(*) FROM sqlite_temp_master WHERE type='table' AND name=?)";
/**
* Check if the table exists within the passed DB
*/
public boolean exists(SynchronizedDb db) {
SynchronizedStatement stmt = db.compileStatement(mExistsSql);
try {
stmt.bindString(1, getName());
stmt.bindString(2, getName());
return (stmt.simpleQueryForLong() > 0);
} finally {
stmt.close();
}
}
}
/**
* Class to store an index using a table name and a list of domian definitions.
*
* @author Philip Warner
*/
public static class IndexDefinition {
/** Full name of index */
private String mName;
/** Table to which index applies */
private TableDefinition mTable;
/** Domains in index */
private DomainDefinition[] mDomains;
/** Flag indicating index is unique */
private boolean mIsUnique;
/**
* Constructor.
*
* @param name name of index
* @param unique Flag indicating index is unique
* @param table Table to which index applies
* @param domains Domains in index
*/
IndexDefinition(String name, boolean unique, TableDefinition table, DomainDefinition...domains) {
this.mName = name;
this.mIsUnique = unique;
this.mTable = table;
this.mDomains = domains;
}
/**
* Accessor. Get UNIQUE flag.
*
* @return
*/
public boolean getUnique() {
return mIsUnique;
}
/**
* Accessor. Get list of domains in index.
*
* @return
*/
public DomainDefinition[] getDomains() {
return mDomains;
}
/**
* Drop the index, if it exists.
*
* @param db Database to use.
*
* @return IndexDefinition (for chaining)
*/
public IndexDefinition drop(SynchronizedDb db) {
db.execSQL("Drop Index If Exists " + mName);
return this;
}
/**
* Create the index.
*
* @param db Database to use.
*
* @return IndexDefinition (for chaining)
*/
public IndexDefinition create(SynchronizedDb db) {
db.execSQL(this.getSql());
return this;
}
/**
* Return the SQL used to define the index.
*
* @return SQL Fragment
*/
private String getSql() {
StringBuilder sql = new StringBuilder("Create ");
if (mIsUnique)
sql.append(" Unique");
sql.append(" Index ");
sql.append(mName);
sql.append(" on " + mTable.getName() + "(\n");
boolean first = true;
for(DomainDefinition d : mDomains) {
if (first) {
first = false;
} else {
sql.append(",\n");
}
sql.append(" ");
sql.append(d.name);
}
sql.append(")\n");
return sql.toString();
}
}
/**
* Given arrays of table and index definitions, create the database.
*
* @param db Blank database
* @param tables Table list
* @param indexes Index list
*/
public static void createTables(SynchronizedDb db, TableDefinition[] tables, boolean withConstraints) {
for (TableDefinition t : tables) {
t.create(db, withConstraints);
for (IndexDefinition i : t.getIndexes()) {
db.execSQL(i.getSql());
}
}
}
}