mirror of https://github.com/M66B/FairEmail.git
744 lines
28 KiB
Java
744 lines
28 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package androidx.room.util;
|
|
|
|
import android.database.Cursor;
|
|
import android.os.Build;
|
|
|
|
import androidx.annotation.IntDef;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.RestrictTo;
|
|
import androidx.room.ColumnInfo;
|
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
|
|
/**
|
|
* A data class that holds the information about a table.
|
|
* <p>
|
|
* It directly maps to the result of {@code PRAGMA table_info(<table_name>)}. Check the
|
|
* <a href="http://www.sqlite.org/pragma.html#pragma_table_info">PRAGMA table_info</a>
|
|
* documentation for more details.
|
|
* <p>
|
|
* Even though SQLite column names are case insensitive, this class uses case sensitive matching.
|
|
*
|
|
* @hide
|
|
*/
|
|
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
|
@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources",
|
|
"SimplifiableIfStatement"})
|
|
// if you change this class, you must change TableInfoValidationWriter.kt
|
|
public final class TableInfo {
|
|
|
|
/**
|
|
* Identifies from where the info object was created.
|
|
*/
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
@IntDef(value = {CREATED_FROM_UNKNOWN, CREATED_FROM_ENTITY, CREATED_FROM_DATABASE})
|
|
@interface CreatedFrom {
|
|
}
|
|
|
|
/**
|
|
* Identifier for when the info is created from an unknown source.
|
|
*/
|
|
public static final int CREATED_FROM_UNKNOWN = 0;
|
|
|
|
/**
|
|
* Identifier for when the info is created from an entity definition, such as generated code
|
|
* by the compiler or at runtime from a schema bundle, parsed from a schema JSON file.
|
|
*/
|
|
public static final int CREATED_FROM_ENTITY = 1;
|
|
|
|
/**
|
|
* Identifier for when the info is created from the database itself, reading information from a
|
|
* PRAGMA, such as table_info.
|
|
*/
|
|
public static final int CREATED_FROM_DATABASE = 2;
|
|
|
|
/**
|
|
* The table name.
|
|
*/
|
|
public final String name;
|
|
/**
|
|
* Unmodifiable map of columns keyed by column name.
|
|
*/
|
|
public final Map<String, Column> columns;
|
|
|
|
public final Set<ForeignKey> foreignKeys;
|
|
|
|
/**
|
|
* Sometimes, Index information is not available (older versions). If so, we skip their
|
|
* verification.
|
|
*/
|
|
@Nullable
|
|
public final Set<Index> indices;
|
|
|
|
@SuppressWarnings("unused")
|
|
public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys,
|
|
Set<Index> indices) {
|
|
this.name = name;
|
|
this.columns = Collections.unmodifiableMap(columns);
|
|
this.foreignKeys = Collections.unmodifiableSet(foreignKeys);
|
|
this.indices = indices == null ? null : Collections.unmodifiableSet(indices);
|
|
}
|
|
|
|
/**
|
|
* For backward compatibility with dbs created with older versions.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
|
|
this(name, columns, foreignKeys, Collections.<Index>emptySet());
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (!(o instanceof TableInfo)) return false;
|
|
|
|
TableInfo tableInfo = (TableInfo) o;
|
|
|
|
if (name != null ? !name.equals(tableInfo.name) : tableInfo.name != null) return false;
|
|
if (columns != null ? !columns.equals(tableInfo.columns) : tableInfo.columns != null) {
|
|
return false;
|
|
}
|
|
if (foreignKeys != null ? !foreignKeys.equals(tableInfo.foreignKeys)
|
|
: tableInfo.foreignKeys != null) {
|
|
return false;
|
|
}
|
|
if (indices == null || tableInfo.indices == null) {
|
|
// if one us is missing index information, seems like we couldn't acquire the
|
|
// information so we better skip.
|
|
return true;
|
|
}
|
|
return indices.equals(tableInfo.indices);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = name != null ? name.hashCode() : 0;
|
|
result = 31 * result + (columns != null ? columns.hashCode() : 0);
|
|
result = 31 * result + (foreignKeys != null ? foreignKeys.hashCode() : 0);
|
|
// skip index, it is not reliable for comparison.
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "TableInfo{"
|
|
+ "name='" + name + '\''
|
|
+ ", columns=" + columns
|
|
+ ", foreignKeys=" + foreignKeys
|
|
+ ", indices=" + indices
|
|
+ '}';
|
|
}
|
|
|
|
/**
|
|
* Reads the table information from the given database.
|
|
*
|
|
* @param database The database to read the information from.
|
|
* @param tableName The table name.
|
|
* @return A TableInfo containing the schema information for the provided table name.
|
|
*/
|
|
@SuppressWarnings("SameParameterValue")
|
|
public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
|
|
Map<String, Column> columns = readColumns(database, tableName);
|
|
Set<ForeignKey> foreignKeys = readForeignKeys(database, tableName);
|
|
Set<Index> indices = readIndices(database, tableName);
|
|
return new TableInfo(tableName, columns, foreignKeys, indices);
|
|
}
|
|
|
|
private static Set<ForeignKey> readForeignKeys(SupportSQLiteDatabase database,
|
|
String tableName) {
|
|
Set<ForeignKey> foreignKeys = new HashSet<>();
|
|
// this seems to return everything in order but it is not documented so better be safe
|
|
Cursor cursor = database.query("PRAGMA foreign_key_list(`" + tableName + "`)");
|
|
try {
|
|
final int idColumnIndex = cursor.getColumnIndex("id");
|
|
final int seqColumnIndex = cursor.getColumnIndex("seq");
|
|
final int tableColumnIndex = cursor.getColumnIndex("table");
|
|
final int onDeleteColumnIndex = cursor.getColumnIndex("on_delete");
|
|
final int onUpdateColumnIndex = cursor.getColumnIndex("on_update");
|
|
|
|
final List<ForeignKeyWithSequence> ordered = readForeignKeyFieldMappings(cursor);
|
|
final int count = cursor.getCount();
|
|
for (int position = 0; position < count; position++) {
|
|
cursor.moveToPosition(position);
|
|
final int seq = cursor.getInt(seqColumnIndex);
|
|
if (seq != 0) {
|
|
continue;
|
|
}
|
|
final int id = cursor.getInt(idColumnIndex);
|
|
List<String> myColumns = new ArrayList<>();
|
|
List<String> refColumns = new ArrayList<>();
|
|
for (ForeignKeyWithSequence key : ordered) {
|
|
if (key.mId == id) {
|
|
myColumns.add(key.mFrom);
|
|
refColumns.add(key.mTo);
|
|
}
|
|
}
|
|
foreignKeys.add(new ForeignKey(
|
|
cursor.getString(tableColumnIndex),
|
|
cursor.getString(onDeleteColumnIndex),
|
|
cursor.getString(onUpdateColumnIndex),
|
|
myColumns,
|
|
refColumns
|
|
));
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
return foreignKeys;
|
|
}
|
|
|
|
private static List<ForeignKeyWithSequence> readForeignKeyFieldMappings(Cursor cursor) {
|
|
final int idColumnIndex = cursor.getColumnIndex("id");
|
|
final int seqColumnIndex = cursor.getColumnIndex("seq");
|
|
final int fromColumnIndex = cursor.getColumnIndex("from");
|
|
final int toColumnIndex = cursor.getColumnIndex("to");
|
|
final int count = cursor.getCount();
|
|
List<ForeignKeyWithSequence> result = new ArrayList<>();
|
|
for (int i = 0; i < count; i++) {
|
|
cursor.moveToPosition(i);
|
|
result.add(new ForeignKeyWithSequence(
|
|
cursor.getInt(idColumnIndex),
|
|
cursor.getInt(seqColumnIndex),
|
|
cursor.getString(fromColumnIndex),
|
|
cursor.getString(toColumnIndex)
|
|
));
|
|
}
|
|
Collections.sort(result);
|
|
return result;
|
|
}
|
|
|
|
private static Map<String, Column> readColumns(SupportSQLiteDatabase database,
|
|
String tableName) {
|
|
|
|
Cursor cursor = database
|
|
.query("PRAGMA table_info(`" + tableName + "`)");
|
|
//noinspection TryFinallyCanBeTryWithResources
|
|
Map<String, Column> columns = new HashMap<>();
|
|
try {
|
|
if (cursor.getColumnCount() > 0) {
|
|
int nameIndex = cursor.getColumnIndex("name");
|
|
int typeIndex = cursor.getColumnIndex("type");
|
|
int notNullIndex = cursor.getColumnIndex("notnull");
|
|
int pkIndex = cursor.getColumnIndex("pk");
|
|
int defaultValueIndex = cursor.getColumnIndex("dflt_value");
|
|
|
|
while (cursor.moveToNext()) {
|
|
final String name = cursor.getString(nameIndex);
|
|
final String type = cursor.getString(typeIndex);
|
|
final boolean notNull = 0 != cursor.getInt(notNullIndex);
|
|
final int primaryKeyPosition = cursor.getInt(pkIndex);
|
|
final String defaultValue = cursor.getString(defaultValueIndex);
|
|
columns.put(name,
|
|
new Column(name, type, notNull, primaryKeyPosition, defaultValue,
|
|
CREATED_FROM_DATABASE));
|
|
}
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
return columns;
|
|
}
|
|
|
|
/**
|
|
* @return null if we cannot read the indices due to older sqlite implementations.
|
|
*/
|
|
@Nullable
|
|
private static Set<Index> readIndices(SupportSQLiteDatabase database, String tableName) {
|
|
Cursor cursor = database.query("PRAGMA index_list(`" + tableName + "`)");
|
|
try {
|
|
final int nameColumnIndex = cursor.getColumnIndex("name");
|
|
final int originColumnIndex = cursor.getColumnIndex("origin");
|
|
final int uniqueIndex = cursor.getColumnIndex("unique");
|
|
if (nameColumnIndex == -1 || originColumnIndex == -1 || uniqueIndex == -1) {
|
|
// we cannot read them so better not validate any index.
|
|
return null;
|
|
}
|
|
HashSet<Index> indices = new HashSet<>();
|
|
while (cursor.moveToNext()) {
|
|
String origin = cursor.getString(originColumnIndex);
|
|
if (!"c".equals(origin)) {
|
|
// Ignore auto-created indices
|
|
continue;
|
|
}
|
|
String name = cursor.getString(nameColumnIndex);
|
|
boolean unique = cursor.getInt(uniqueIndex) == 1;
|
|
Index index = readIndex(database, name, unique);
|
|
if (index == null) {
|
|
// we cannot read it properly so better not read it
|
|
return null;
|
|
}
|
|
indices.add(index);
|
|
}
|
|
return indices;
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return null if we cannot read the index due to older sqlite implementations.
|
|
*/
|
|
@Nullable
|
|
private static Index readIndex(SupportSQLiteDatabase database, String name, boolean unique) {
|
|
Cursor cursor = database.query("PRAGMA index_xinfo(`" + name + "`)");
|
|
try {
|
|
final int seqnoColumnIndex = cursor.getColumnIndex("seqno");
|
|
final int cidColumnIndex = cursor.getColumnIndex("cid");
|
|
final int nameColumnIndex = cursor.getColumnIndex("name");
|
|
final int descColumnIndex = cursor.getColumnIndex("desc");
|
|
if (seqnoColumnIndex == -1 || cidColumnIndex == -1
|
|
|| nameColumnIndex == -1 || descColumnIndex == -1) {
|
|
// we cannot read them so better not validate any index.
|
|
return null;
|
|
}
|
|
final TreeMap<Integer, String> columnsMap = new TreeMap<>();
|
|
final TreeMap<Integer, String> ordersMap = new TreeMap<>();
|
|
|
|
while (cursor.moveToNext()) {
|
|
int cid = cursor.getInt(cidColumnIndex);
|
|
if (cid < 0) {
|
|
// Ignore SQLite row ID
|
|
continue;
|
|
}
|
|
int seq = cursor.getInt(seqnoColumnIndex);
|
|
String columnName = cursor.getString(nameColumnIndex);
|
|
String order = cursor.getInt(descColumnIndex) > 0 ? "DESC" : "ASC";
|
|
|
|
columnsMap.put(seq, columnName);
|
|
ordersMap.put(seq, order);
|
|
}
|
|
final List<String> columns = new ArrayList<>(columnsMap.size());
|
|
columns.addAll(columnsMap.values());
|
|
final List<String> orders = new ArrayList<>(ordersMap.size());
|
|
orders.addAll(ordersMap.values());
|
|
return new Index(name, unique, columns, orders);
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds the information about a database column.
|
|
*/
|
|
@SuppressWarnings("WeakerAccess")
|
|
public static final class Column {
|
|
/**
|
|
* The column name.
|
|
*/
|
|
public final String name;
|
|
/**
|
|
* The column type affinity.
|
|
*/
|
|
public final String type;
|
|
/**
|
|
* The column type after it is normalized to one of the basic types according to
|
|
* https://www.sqlite.org/datatype3.html Section 3.1.
|
|
* <p>
|
|
* This is the value Room uses for equality check.
|
|
*/
|
|
@ColumnInfo.SQLiteTypeAffinity
|
|
public final int affinity;
|
|
/**
|
|
* Whether or not the column can be NULL.
|
|
*/
|
|
public final boolean notNull;
|
|
/**
|
|
* The position of the column in the list of primary keys, 0 if the column is not part
|
|
* of the primary key.
|
|
* <p>
|
|
* This information is only available in API 20+.
|
|
* <a href="https://www.sqlite.org/releaselog/3_7_16_2.html">(SQLite version 3.7.16.2)</a>
|
|
* On older platforms, it will be 1 if the column is part of the primary key and 0
|
|
* otherwise.
|
|
* <p>
|
|
* The {@link #equals(Object)} implementation handles this inconsistency based on
|
|
* API levels os if you are using a custom SQLite deployment, it may return false
|
|
* positives.
|
|
*/
|
|
public final int primaryKeyPosition;
|
|
/**
|
|
* The default value of this column.
|
|
*/
|
|
public final String defaultValue;
|
|
|
|
@CreatedFrom
|
|
private final int mCreatedFrom;
|
|
|
|
/**
|
|
* @deprecated Use {@link Column#Column(String, String, boolean, int, String, int)} instead.
|
|
*/
|
|
@Deprecated
|
|
public Column(String name, String type, boolean notNull, int primaryKeyPosition) {
|
|
this(name, type, notNull, primaryKeyPosition, null, CREATED_FROM_UNKNOWN);
|
|
}
|
|
|
|
// if you change this constructor, you must change TableInfoWriter.kt
|
|
public Column(String name, String type, boolean notNull, int primaryKeyPosition,
|
|
String defaultValue, @CreatedFrom int createdFrom) {
|
|
this.name = name;
|
|
this.type = type;
|
|
this.notNull = notNull;
|
|
this.primaryKeyPosition = primaryKeyPosition;
|
|
this.affinity = findAffinity(type);
|
|
this.defaultValue = defaultValue;
|
|
this.mCreatedFrom = createdFrom;
|
|
}
|
|
|
|
/**
|
|
* Implements https://www.sqlite.org/datatype3.html section 3.1
|
|
*
|
|
* @param type The type that was given to the sqlite
|
|
* @return The normalized type which is one of the 5 known affinities
|
|
*/
|
|
@ColumnInfo.SQLiteTypeAffinity
|
|
private static int findAffinity(@Nullable String type) {
|
|
if (type == null) {
|
|
return ColumnInfo.BLOB;
|
|
}
|
|
String uppercaseType = type.toUpperCase(Locale.US);
|
|
if (uppercaseType.contains("INT")) {
|
|
return ColumnInfo.INTEGER;
|
|
}
|
|
if (uppercaseType.contains("CHAR")
|
|
|| uppercaseType.contains("CLOB")
|
|
|| uppercaseType.contains("TEXT")) {
|
|
return ColumnInfo.TEXT;
|
|
}
|
|
if (uppercaseType.contains("BLOB")) {
|
|
return ColumnInfo.BLOB;
|
|
}
|
|
if (uppercaseType.contains("REAL")
|
|
|| uppercaseType.contains("FLOA")
|
|
|| uppercaseType.contains("DOUB")) {
|
|
return ColumnInfo.REAL;
|
|
}
|
|
// sqlite returns NUMERIC here but it is like a catch all. We already
|
|
// have UNDEFINED so it is better to use UNDEFINED for consistency.
|
|
return ColumnInfo.UNDEFINED;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (!(o instanceof Column)) return false;
|
|
|
|
Column column = (Column) o;
|
|
if (Build.VERSION.SDK_INT >= 20) {
|
|
if (primaryKeyPosition != column.primaryKeyPosition) return false;
|
|
} else {
|
|
if (isPrimaryKey() != column.isPrimaryKey()) return false;
|
|
}
|
|
|
|
if (!name.equals(column.name)) return false;
|
|
//noinspection SimplifiableIfStatement
|
|
if (notNull != column.notNull) return false;
|
|
|
|
// Only validate default value if it was defined in an entity, i.e. if the info
|
|
// from the compiler itself has it. b/136019383
|
|
if (mCreatedFrom == CREATED_FROM_ENTITY
|
|
&& column.mCreatedFrom == CREATED_FROM_DATABASE
|
|
&& (defaultValue != null && !defaultValueEquals(defaultValue,
|
|
column.defaultValue))) {
|
|
return false;
|
|
} else if (mCreatedFrom == CREATED_FROM_DATABASE
|
|
&& column.mCreatedFrom == CREATED_FROM_ENTITY
|
|
&& (column.defaultValue != null && !defaultValueEquals(
|
|
column.defaultValue, defaultValue))) {
|
|
return false;
|
|
} else if (mCreatedFrom != CREATED_FROM_UNKNOWN
|
|
&& mCreatedFrom == column.mCreatedFrom
|
|
&& (defaultValue != null ? !defaultValueEquals(defaultValue,
|
|
column.defaultValue)
|
|
: column.defaultValue != null)) {
|
|
return false;
|
|
}
|
|
|
|
return affinity == column.affinity;
|
|
}
|
|
|
|
/**
|
|
* Checks if the default values provided match. Handles the special case in which the
|
|
* default value is surrounded by parenthesis (e.g. encountered in b/182284899).
|
|
*
|
|
* Surrounding parenthesis are removed by SQLite when reading from the database, hence
|
|
* this function will check if they are present in the actual value, if so, it will
|
|
* compare the two values by ignoring the surrounding parenthesis.
|
|
*
|
|
*/
|
|
public static boolean defaultValueEquals(@NonNull String actual, @Nullable String other) {
|
|
if (other == null) {
|
|
return false;
|
|
}
|
|
|
|
if (actual.equals(other)) {
|
|
return true;
|
|
} else if (containsSurroundingParenthesis(actual)) {
|
|
return actual.substring(1, actual.length() - 1).trim().equals(other);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks for potential surrounding parenthesis, if found, removes them and checks if
|
|
* remaining paranthesis are balanced. If so, the surrounding parenthesis are redundant,
|
|
* and returns true.
|
|
*/
|
|
private static boolean containsSurroundingParenthesis(@NonNull String actual) {
|
|
if (actual.length() == 0) {
|
|
return false;
|
|
}
|
|
int surroundingParenthesis = 0;
|
|
for (int i = 0; i < actual.length(); i++) {
|
|
char c = actual.charAt(i);
|
|
if (i == 0 && c != '(') {
|
|
return false;
|
|
}
|
|
|
|
if (c == '(') {
|
|
surroundingParenthesis++;
|
|
} else if (c == ')') {
|
|
surroundingParenthesis--;
|
|
if (surroundingParenthesis == 0 && i != actual.length() - 1) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return surroundingParenthesis == 0;
|
|
}
|
|
|
|
/**
|
|
* Returns whether this column is part of the primary key or not.
|
|
*
|
|
* @return True if this column is part of the primary key, false otherwise.
|
|
*/
|
|
public boolean isPrimaryKey() {
|
|
return primaryKeyPosition > 0;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = name.hashCode();
|
|
result = 31 * result + affinity;
|
|
result = 31 * result + (notNull ? 1231 : 1237);
|
|
result = 31 * result + primaryKeyPosition;
|
|
// Default value is not part of the hashcode since we conditionally check it for
|
|
// equality which would break the equals + hashcode contract.
|
|
// result = 31 * result + (defaultValue != null ? defaultValue.hashCode() : 0);
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Column{"
|
|
+ "name='" + name + '\''
|
|
+ ", type='" + type + '\''
|
|
+ ", affinity='" + affinity + '\''
|
|
+ ", notNull=" + notNull
|
|
+ ", primaryKeyPosition=" + primaryKeyPosition
|
|
+ ", defaultValue='" + defaultValue + '\''
|
|
+ '}';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds the information about an SQLite foreign key
|
|
*
|
|
* @hide
|
|
*/
|
|
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
|
public static final class ForeignKey {
|
|
@NonNull
|
|
public final String referenceTable;
|
|
@NonNull
|
|
public final String onDelete;
|
|
@NonNull
|
|
public final String onUpdate;
|
|
@NonNull
|
|
public final List<String> columnNames;
|
|
@NonNull
|
|
public final List<String> referenceColumnNames;
|
|
|
|
public ForeignKey(@NonNull String referenceTable, @NonNull String onDelete,
|
|
@NonNull String onUpdate,
|
|
@NonNull List<String> columnNames, @NonNull List<String> referenceColumnNames) {
|
|
this.referenceTable = referenceTable;
|
|
this.onDelete = onDelete;
|
|
this.onUpdate = onUpdate;
|
|
this.columnNames = Collections.unmodifiableList(columnNames);
|
|
this.referenceColumnNames = Collections.unmodifiableList(referenceColumnNames);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (!(o instanceof ForeignKey)) return false;
|
|
|
|
ForeignKey that = (ForeignKey) o;
|
|
|
|
if (!referenceTable.equals(that.referenceTable)) return false;
|
|
if (!onDelete.equals(that.onDelete)) return false;
|
|
if (!onUpdate.equals(that.onUpdate)) return false;
|
|
//noinspection SimplifiableIfStatement
|
|
if (!columnNames.equals(that.columnNames)) return false;
|
|
return referenceColumnNames.equals(that.referenceColumnNames);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result = referenceTable.hashCode();
|
|
result = 31 * result + onDelete.hashCode();
|
|
result = 31 * result + onUpdate.hashCode();
|
|
result = 31 * result + columnNames.hashCode();
|
|
result = 31 * result + referenceColumnNames.hashCode();
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "ForeignKey{"
|
|
+ "referenceTable='" + referenceTable + '\''
|
|
+ ", onDelete='" + onDelete + '\''
|
|
+ ", onUpdate='" + onUpdate + '\''
|
|
+ ", columnNames=" + columnNames
|
|
+ ", referenceColumnNames=" + referenceColumnNames
|
|
+ '}';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Temporary data holder for a foreign key row in the pragma result. We need this to ensure
|
|
* sorting in the generated foreign key object.
|
|
*
|
|
* @hide
|
|
*/
|
|
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
|
static class ForeignKeyWithSequence implements Comparable<ForeignKeyWithSequence> {
|
|
final int mId;
|
|
final int mSequence;
|
|
final String mFrom;
|
|
final String mTo;
|
|
|
|
ForeignKeyWithSequence(int id, int sequence, String from, String to) {
|
|
mId = id;
|
|
mSequence = sequence;
|
|
mFrom = from;
|
|
mTo = to;
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(@NonNull ForeignKeyWithSequence o) {
|
|
final int idCmp = mId - o.mId;
|
|
if (idCmp == 0) {
|
|
return mSequence - o.mSequence;
|
|
} else {
|
|
return idCmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds the information about an SQLite index
|
|
*
|
|
* @hide
|
|
*/
|
|
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
|
|
public static final class Index {
|
|
// should match the value in Index.kt
|
|
public static final String DEFAULT_PREFIX = "index_";
|
|
public final String name;
|
|
public final boolean unique;
|
|
public final List<String> columns;
|
|
public final List<String> orders;
|
|
|
|
/**
|
|
* @deprecated Use {@link #Index(String, boolean, List, List)}
|
|
*/
|
|
public Index(String name, boolean unique, List<String> columns) {
|
|
this(name, unique, columns, null);
|
|
}
|
|
|
|
public Index(String name, boolean unique, List<String> columns, List<String> orders) {
|
|
this.name = name;
|
|
this.unique = unique;
|
|
this.columns = columns;
|
|
this.orders = orders == null || orders.size() == 0
|
|
? Collections.nCopies(columns.size(), androidx.room.Index.Order.ASC.name())
|
|
: orders;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (this == o) return true;
|
|
if (!(o instanceof Index)) return false;
|
|
|
|
Index index = (Index) o;
|
|
if (unique != index.unique) {
|
|
return false;
|
|
}
|
|
if (!columns.equals(index.columns)) {
|
|
return false;
|
|
}
|
|
if (!orders.equals(index.orders)) {
|
|
return false;
|
|
}
|
|
if (name.startsWith(Index.DEFAULT_PREFIX)) {
|
|
return index.name.startsWith(Index.DEFAULT_PREFIX);
|
|
} else {
|
|
return name.equals(index.name);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int result;
|
|
if (name.startsWith(DEFAULT_PREFIX)) {
|
|
result = DEFAULT_PREFIX.hashCode();
|
|
} else {
|
|
result = name.hashCode();
|
|
}
|
|
result = 31 * result + (unique ? 1 : 0);
|
|
result = 31 * result + columns.hashCode();
|
|
result = 31 * result + orders.hashCode();
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Index{"
|
|
+ "name='" + name + '\''
|
|
+ ", unique=" + unique
|
|
+ ", columns=" + columns
|
|
+ ", orders=" + orders
|
|
+ '}';
|
|
}
|
|
}
|
|
}
|