package com.raizlabs.android.dbflow.sql.language;
import android.content.ContentValues;
import android.support.annotation.NonNull;
import com.raizlabs.android.dbflow.annotation.ConflictAction;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.sql.Query;
import com.raizlabs.android.dbflow.sql.QueryBuilder;
import com.raizlabs.android.dbflow.sql.language.property.IProperty;
import com.raizlabs.android.dbflow.structure.BaseModel;
import com.raizlabs.android.dbflow.structure.ModelAdapter;
import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Description: The SQLite INSERT command
*/
public class Insert<TModel> extends BaseQueriable<TModel> implements Query {
/**
* The columns to specify in this query (optional)
*/
private IProperty[] columns;
/**
* The values to specify in this query
*/
private List<Collection<Object>> valuesList;
/**
* The conflict algorithm to use to resolve inserts.
*/
private ConflictAction conflictAction = ConflictAction.NONE;
private From<?> selectFrom;
/**
* Constructs a new INSERT command
*
* @param table The table to insert into
*/
public Insert(Class<TModel> table) {
super(table);
}
/**
* The optional columns to specify. If specified, the values length must correspond to these columns, and
* each column has a 1-1 relationship to the values.
*
* @param columns The columns to use
*/
@NonNull
public Insert<TModel> columns(String... columns) {
this.columns = new IProperty[columns.length];
ModelAdapter<TModel> modelClassModelAdapter = FlowManager.getModelAdapter(getTable());
for (int i = 0; i < columns.length; i++) {
String column = columns[i];
this.columns[i] = modelClassModelAdapter.getProperty(column);
}
return this;
}
@NonNull
public Insert<TModel> columns(IProperty... properties) {
this.columns = new IProperty[properties.length];
for (int i = 0; i < properties.length; i++) {
columns[i] = properties[i];
}
return this;
}
@NonNull
public Insert<TModel> columns(@NonNull List<IProperty> properties) {
return columns(properties.toArray(new IProperty[properties.size()]));
}
/**
* @return Appends a list of columns to this INSERT statement from the associated {@link TModel}.
*/
@NonNull
public Insert<TModel> asColumns() {
columns(FlowManager.getModelAdapter(getTable()).getAllColumnProperties());
return this;
}
/**
* @return Appends a list of columns to this INSERT and ? as the values.
*/
@NonNull
public Insert<TModel> asColumnValues() {
asColumns();
if (columns != null) {
List<Object> values = new ArrayList<>();
for (int i = 0; i < columns.length; i++) {
values.add("?");
}
valuesList.add(values);
}
return this;
}
/**
* The required values to specify. It must be non-empty and match the length of the columns when
* a set of columns are specified.
*
* @param values The non type-converted values
*/
@NonNull
public Insert<TModel> values(Object... values) {
if (this.valuesList == null) {
this.valuesList = new ArrayList<>();
}
this.valuesList.add(Arrays.asList(values));
return this;
}
/**
* The required values to specify. It must be non-empty and match the length of the columns when
* a set of columns are specified.
*
* @param values The non type-converted values
*/
@NonNull
public Insert<TModel> values(Collection<Object> values) {
if (this.valuesList == null) {
this.valuesList = new ArrayList<>();
}
this.valuesList.add(values);
return this;
}
/**
* Uses the {@link Operator} pairs to fill this insert query.
*
* @param conditions The conditions that we use to fill the columns and values of this INSERT
*/
@NonNull
public Insert<TModel> columnValues(SQLOperator... conditions) {
String[] columns = new String[conditions.length];
Object[] values = new Object[conditions.length];
for (int i = 0; i < conditions.length; i++) {
SQLOperator condition = conditions[i];
columns[i] = condition.columnName();
values[i] = condition.value();
}
return columns(columns).values(values);
}
/**
* Uses the {@link Operator} pairs to fill this insert query.
*
* @param operatorGroup The OperatorGroup to use
*/
@NonNull
public Insert<TModel> columnValues(OperatorGroup operatorGroup) {
int size = operatorGroup.size();
String[] columns = new String[size];
Object[] values = new Object[size];
for (int i = 0; i < size; i++) {
SQLOperator condition = operatorGroup.getConditions().get(i);
columns[i] = condition.columnName();
values[i] = condition.value();
}
return columns(columns).values(values);
}
@NonNull
public Insert<TModel> columnValues(ContentValues contentValues) {
java.util.Set<Map.Entry<String, Object>> entries = contentValues.valueSet();
int count = 0;
String[] columns = new String[contentValues.size()];
Object[] values = new Object[contentValues.size()];
for (Map.Entry<String, Object> entry : entries) {
String key = entry.getKey();
columns[count] = key;
values[count] = contentValues.get(key);
count++;
}
return columns(columns).values(values);
}
/**
* Appends the specified {@link From}, which comes from a {@link Select} statement.
*
* @param selectFrom The from that is continuation of {@link Select}.
*/
@NonNull
public Insert<TModel> select(From<?> selectFrom) {
this.selectFrom = selectFrom;
return this;
}
/**
* Specifies the optional OR method to use for this insert query
*
* @param action The conflict action to use
* @return
*/
@NonNull
public Insert<TModel> or(ConflictAction action) {
conflictAction = action;
return this;
}
/**
* Specifies OR REPLACE, which will either insert if row does not exist, or replace the value if it does.
*
* @return
*/
@NonNull
public Insert<TModel> orReplace() {
return or(ConflictAction.REPLACE);
}
/**
* Specifies OR ROLLBACK, which will cancel the current transaction or ABORT the current statement.
*
* @return
*/
@NonNull
public Insert<TModel> orRollback() {
return or(ConflictAction.ROLLBACK);
}
/**
* Specifies OR ABORT, which will cancel the current INSERT, but all other operations will be preserved in
* the current transaction.
*
* @return
*/
@NonNull
public Insert<TModel> orAbort() {
return or(ConflictAction.ABORT);
}
/**
* Specifies OR FAIL, which does not back out of the previous statements. Anything else in the current
* transaction will fail.
*
* @return
*/
@NonNull
public Insert<TModel> orFail() {
return or(ConflictAction.FAIL);
}
/**
* Specifies OR IGNORE, which ignores any kind of error and proceeds as normal.
*
* @return
*/
@NonNull
public Insert<TModel> orIgnore() {
return or(ConflictAction.IGNORE);
}
@Override
public long executeUpdateDelete(DatabaseWrapper databaseWrapper) {
throw new IllegalStateException("Cannot call executeUpdateDelete() from an Insert");
}
@Override
public long executeUpdateDelete() {
throw new IllegalStateException("Cannot call executeUpdateDelete() from an Insert");
}
@Override
public String getQuery() {
QueryBuilder queryBuilder = new QueryBuilder("INSERT ");
if (conflictAction != null && !conflictAction.equals(ConflictAction.NONE)) {
queryBuilder.append("OR").appendSpaceSeparated(conflictAction);
}
queryBuilder.append("INTO")
.appendSpace()
.append(FlowManager.getTableName(getTable()));
if (columns != null) {
queryBuilder.append("(")
.appendArray((Object[]) columns)
.append(")");
}
// append FROM, which overrides values
if (selectFrom != null) {
queryBuilder.appendSpace().append(selectFrom.getQuery());
} else {
if (valuesList == null || valuesList.size() < 1) {
throw new IllegalStateException("The insert of " + FlowManager.getTableName(getTable()) + " should have" +
"at least one value specified for the insert");
} else if (columns != null) {
for (Collection<Object> values : valuesList) {
if (values.size() != columns.length) {
throw new IllegalStateException("The Insert of " + FlowManager.getTableName(getTable()) + " when specifying" +
"columns needs to have the same amount of values and columns");
}
}
}
queryBuilder.append(" VALUES(");
for (int i = 0; i < valuesList.size(); i++) {
if (i > 0) {
queryBuilder.append(",(");
}
queryBuilder.append(BaseOperator.joinArguments(", ", valuesList.get(i))).append(")");
}
}
return queryBuilder.getQuery();
}
@Override
public BaseModel.Action getPrimaryAction() {
return BaseModel.Action.INSERT;
}
}