/* * Copyright 2014 Realm Inc. * * 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 io.realm.internal; import java.util.Date; import io.realm.RealmFieldType; import io.realm.exceptions.RealmException; import io.realm.exceptions.RealmPrimaryKeyConstraintException; /** * This class is a base class for all Realm tables. The class supports all low level methods * (define/insert/delete/update) a table has. All the native communications to the Realm C++ library are also handled by * this class. */ public class Table implements TableSchema, NativeObject { enum PivotType { COUNT(0), SUM(1), AVG(2), MIN(3), MAX(4); final int value; // Package protected, accessible from Table PivotType(int value) { this.value = value; } } public static final int TABLE_MAX_LENGTH = 56; // Max length of class names without prefix public static final long INFINITE = -1; public static final boolean NULLABLE = true; public static final boolean NOT_NULLABLE = false; public static final int NO_MATCH = -1; private static final String TABLE_PREFIX = Util.getTablePrefix(); private static final String PRIMARY_KEY_TABLE_NAME = "pk"; private static final String PRIMARY_KEY_CLASS_COLUMN_NAME = "pk_table"; private static final long PRIMARY_KEY_CLASS_COLUMN_INDEX = 0; private static final String PRIMARY_KEY_FIELD_COLUMN_NAME = "pk_property"; private static final long PRIMARY_KEY_FIELD_COLUMN_INDEX = 1; public static final long NO_PRIMARY_KEY = -2; private static final long nativeFinalizerPtr = nativeGetFinalizerPtr(); private final long nativePtr; private final NativeContext context; private final SharedRealm sharedRealm; private long cachedPrimaryKeyColumnIndex = NO_MATCH; /** * Constructs a Table base object. It can be used to register columns in this table. Registering into table is * allowed only for empty tables. It creates a native reference of the object and keeps a reference to it. */ public Table() { this.context = new NativeContext(); // Native methods work will be initialized here. Generated classes will // have nothing to do with the native functions. Generated Java Table // classes will work as a wrapper on top of table. this.nativePtr = createNative(); if (nativePtr == 0) { throw new java.lang.OutOfMemoryError("Out of native memory."); } this.sharedRealm = null; context.addReference(this); } Table(Table parent, long nativePointer) { this(parent.sharedRealm, nativePointer); } Table(SharedRealm sharedRealm, long nativePointer) { this.context = sharedRealm.context; this.sharedRealm = sharedRealm; this.nativePtr = nativePointer; context.addReference(this); } @Override public long getNativePtr() { return nativePtr; } @Override public long getNativeFinalizerPtr() { return nativeFinalizerPtr; } public Table getTable() { return this; } /* * Checks if the Table is valid. * Whenever a Table/subtable is changed/updated all it's subtables are invalidated. * You can no longer perform any actions on the table, and if done anyway, an exception is thrown. * The only method you can call is 'isValid()'. */ public boolean isValid() { return nativePtr != 0 && nativeIsValid(nativePtr); } private void verifyColumnName(String name) { if (name.length() > 63) { throw new IllegalArgumentException("Column names are currently limited to max 63 characters."); } } /** * Adds a column to the table dynamically. * * @param type the column type. * @param name the field/column name. * @param isNullable {@code true} if column can contain null values, {@code false} otherwise. * @return the index of the new column. */ public long addColumn(RealmFieldType type, String name, boolean isNullable) { verifyColumnName(name); return nativeAddColumn(nativePtr, type.getNativeValue(), name, isNullable); } /** * Adds a non-nullable column to the table dynamically. * * @return the index of the new column. */ @Override public long addColumn(RealmFieldType type, String name) { return addColumn(type, name, false); } /** * Adds a link column to the table dynamically. * * @return the index of the new column. */ public long addColumnLink(RealmFieldType type, String name, Table table) { verifyColumnName(name); return nativeAddColumnLink(nativePtr, type.getNativeValue(), name, table.nativePtr); } /** * Removes a column in the table dynamically. If {@code columnIndex} is smaller than the primary * key column index, {@link #invalidateCachedPrimaryKeyIndex()} will be called to recalculate the * primary key column index. * <p> * <p>It should be noted if {@code columnIndex} is the same as the primary key column index, * the primary key column is removed from the meta table. * * @param columnIndex the column index to be removed. */ @Override public void removeColumn(long columnIndex) { // Checks the PK column index before removing a column. We don't know if we're hitting a PK col, // but it should be noted that once a column is removed, there is no way we can find whether // a PK exists or not. final long oldPkColumnIndex = getPrimaryKey(); // First removes a column. If there is no error, we can proceed. Otherwise, it will stop here. nativeRemoveColumn(nativePtr, columnIndex); // Checks if a PK exists and takes actions if there is. This is same as hasPrimaryKey(), but // this relies on the local cache. if (oldPkColumnIndex >= 0) { // In case we're hitting PK column, we should remove the PK as it is either 1) a user has // forgotten to remove PK or 2) removeColumn gets called before setPrimaryKey(null) is called. // Since there is no danger in removing PK twice, we'll do it here to be on safe side. if (oldPkColumnIndex == columnIndex) { setPrimaryKey(null); // But if you remove a column with a smaller index than that of PK column, you need to // recalculate the PK column index as core could have changed its column index. } else if (oldPkColumnIndex > columnIndex) { invalidateCachedPrimaryKeyIndex(); } } } /** * Renames a column in the table. If the column is a primary key column, the corresponding entry * in PrimaryKeyTable will be renamed accordingly. * * @param columnIndex the column index to be renamed. * @param newName a new name replacing the old column name. * @throws IllegalArgumentException if {@code newFieldName} is an empty string, or exceeds field name length limit. * @throws IllegalStateException if a PrimaryKey column name could not be found in the meta table, but {@link #getPrimaryKey()} returns an index. */ @Override public void renameColumn(long columnIndex, String newName) { verifyColumnName(newName); // Gets the old column name. We'll assume that the old column name is *NOT* an empty string. final String oldName = nativeGetColumnName(nativePtr, columnIndex); // Also old pk index. Once a column name changes, there is no way you can find the column name // by old name. final long oldPkColumnIndex = getPrimaryKey(); // Then let's try to rename a column. If an error occurs for some reasons, we'll throw. nativeRenameColumn(nativePtr, columnIndex, newName); // Renames a primary key. At this point, renaming the column name should have been fine. if (oldPkColumnIndex == columnIndex) { try { Table pkTable = getPrimaryKeyTable(); if (pkTable == null) { throw new IllegalStateException( "Table is not created from a SharedRealm, primary key is not available"); } long pkRowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, getClassName()); if (pkRowIndex != NO_MATCH) { nativeSetString(pkTable.nativePtr, PRIMARY_KEY_FIELD_COLUMN_INDEX, pkRowIndex, newName, false); } else { throw new IllegalStateException("Non-existent PrimaryKey column cannot be renamed"); } } catch (Exception e) { // We failed to rename the pk meta table. roll back the column name, not pk meta table // then rethrow. nativeRenameColumn(nativePtr, columnIndex, oldName); throw new RuntimeException(e); } } } /** * Checks whether the specific column is nullable? * * @param columnIndex the column index. * @return {@code true} if column is nullable, {@code false} otherwise. */ public boolean isColumnNullable(long columnIndex) { return nativeIsColumnNullable(nativePtr, columnIndex); } /** * Converts a column to be nullable. * * @param columnIndex the column index. */ public void convertColumnToNullable(long columnIndex) { nativeConvertColumnToNullable(nativePtr, columnIndex); } /** * Converts a column to be not nullable. null values will be converted to default values. * * @param columnIndex the column index. */ public void convertColumnToNotNullable(long columnIndex) { nativeConvertColumnToNotNullable(nativePtr, columnIndex); } // Table Size and deletion. AutoGenerated subclasses are nothing to do with this // class. /** * Gets the number of entries/rows of this table. * * @return the number of rows. */ public long size() { return nativeSize(nativePtr); } /** * Checks whether this table is empty or not. * * @return {@code true} if empty, otherwise {@code false}. */ public boolean isEmpty() { return size() == 0; } /** * Clears the table i.e., deleting all rows in the table. */ public void clear() { checkImmutable(); nativeClear(nativePtr); } // Column Information. /** * Returns the number of columns in the table. * * @return the number of columns. */ public long getColumnCount() { return nativeGetColumnCount(nativePtr); } /** * Returns the name of a column identified by columnIndex. Notice that the index is zero based. * * @param columnIndex the column index. * @return the name of the column. */ public String getColumnName(long columnIndex) { return nativeGetColumnName(nativePtr, columnIndex); } /** * Returns the 0-based index of a column based on the name. * * @param columnName column name. * @return the index, {@link #NO_MATCH} if not found. */ public long getColumnIndex(String columnName) { if (columnName == null) { throw new IllegalArgumentException("Column name can not be null."); } return nativeGetColumnIndex(nativePtr, columnName); } /** * Gets the type of a column identified by the columnIndex. * * @param columnIndex index of the column. * @return the type of the particular column. */ public RealmFieldType getColumnType(long columnIndex) { return RealmFieldType.fromNativeValue(nativeGetColumnType(nativePtr, columnIndex)); } /** * Removes a row from the specific index. If it is not the last row in the table, it then moves the last row into * the vacated slot. * * @param rowIndex the row index (starting with 0) */ public void moveLastOver(long rowIndex) { checkImmutable(); nativeMoveLastOver(nativePtr, rowIndex); } /** * Adds an empty row to the table which doesn't have a primary key defined. * <p> * NOTE: To add a table with a primary key defined, use {@link #addEmptyRowWithPrimaryKey(Object)} instead. This * won't check if this table has a primary key. * * @return row index. */ public long addEmptyRow() { checkImmutable(); return nativeAddEmptyRow(nativePtr, 1); } @SuppressWarnings("WeakerAccess") public long addEmptyRows(long rows) { checkImmutable(); if (rows < 1) { throw new IllegalArgumentException("'rows' must be > 0."); } if (hasPrimaryKey()) { if (rows > 1) { throw new RealmException("Multiple empty rows cannot be created if a primary key is defined for the table."); } return addEmptyRow(); } return nativeAddEmptyRow(nativePtr, rows); } /** * Appends the specified row to the end of the table. For internal testing usage only. * * @param values values. * @return the row index of the appended row. * @deprecated Remove this functions since it doesn't seem to be useful. And this function does deal with tables * with primary key defined well. Primary key has to be set with `setXxxUnique` as the first thing to do after row * added. */ protected long add(Object... values) { long rowIndex = addEmptyRow(); checkImmutable(); // Checks values types. int columns = (int) getColumnCount(); if (columns != values.length) { throw new IllegalArgumentException("The number of value parameters (" + String.valueOf(values.length) + ") does not match the number of columns in the table (" + String.valueOf(columns) + ")."); } RealmFieldType[] colTypes = new RealmFieldType[columns]; for (int columnIndex = 0; columnIndex < columns; columnIndex++) { Object value = values[columnIndex]; RealmFieldType colType = getColumnType(columnIndex); colTypes[columnIndex] = colType; if (!colType.isValid(value)) { // String representation of the provided value type. String providedType; if (value == null) { providedType = "null"; } else { providedType = value.getClass().toString(); } throw new IllegalArgumentException("Invalid argument no " + String.valueOf(1 + columnIndex) + ". Expected a value compatible with column type " + colType + ", but got " + providedType + "."); } } // Inserts values. for (long columnIndex = 0; columnIndex < columns; columnIndex++) { Object value = values[(int) columnIndex]; switch (colTypes[(int) columnIndex]) { case BOOLEAN: nativeSetBoolean(nativePtr, columnIndex, rowIndex, (Boolean) value, false); break; case INTEGER: if (value == null) { checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); nativeSetNull(nativePtr, columnIndex, rowIndex, false); } else { long intValue = ((Number) value).longValue(); checkIntValueIsLegal(columnIndex, rowIndex, intValue); nativeSetLong(nativePtr, columnIndex, rowIndex, intValue, false); } break; case FLOAT: nativeSetFloat(nativePtr, columnIndex, rowIndex, (Float) value, false); break; case DOUBLE: nativeSetDouble(nativePtr, columnIndex, rowIndex, (Double) value, false); break; case STRING: if (value == null) { checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); nativeSetNull(nativePtr, columnIndex, rowIndex, false); } else { String stringValue = (String) value; checkStringValueIsLegal(columnIndex, rowIndex, stringValue); nativeSetString(nativePtr, columnIndex, rowIndex, (String) value, false); } break; case DATE: if (value == null) { throw new IllegalArgumentException("Null Date is not allowed."); } nativeSetTimestamp(nativePtr, columnIndex, rowIndex, ((Date) value).getTime(), false); break; case BINARY: if (value == null) { throw new IllegalArgumentException("Null Array is not allowed"); } nativeSetByteArray(nativePtr, columnIndex, rowIndex, (byte[]) value, false); break; case UNSUPPORTED_MIXED: case UNSUPPORTED_TABLE: default: throw new RuntimeException("Unexpected columnType: " + String.valueOf(colTypes[(int) columnIndex])); } } return rowIndex; } private boolean isPrimaryKeyColumn(long columnIndex) { return columnIndex == getPrimaryKey(); } /** * Returns the column index for the primary key. * * @return the column index or {@code #NO_MATCH} if no primary key is set. */ public long getPrimaryKey() { if (cachedPrimaryKeyColumnIndex >= 0 || cachedPrimaryKeyColumnIndex == NO_PRIMARY_KEY) { return cachedPrimaryKeyColumnIndex; } else { Table pkTable = getPrimaryKeyTable(); if (pkTable == null) { return NO_PRIMARY_KEY; // Free table = No primary key. } long rowIndex = pkTable.findFirstString(PRIMARY_KEY_CLASS_COLUMN_INDEX, getClassName()); if (rowIndex != NO_MATCH) { String pkColumnName = pkTable.getUncheckedRow(rowIndex).getString(PRIMARY_KEY_FIELD_COLUMN_INDEX); cachedPrimaryKeyColumnIndex = getColumnIndex(pkColumnName); } else { cachedPrimaryKeyColumnIndex = NO_PRIMARY_KEY; } return cachedPrimaryKeyColumnIndex; } } /** * Checks if a given column is a primary key column. * * @param columnIndex the index of column in the table. * @return {@code true} if column is a primary key, {@code false} otherwise. */ private boolean isPrimaryKey(long columnIndex) { return columnIndex >= 0 && columnIndex == getPrimaryKey(); } /** * Checks if a table has a primary key. * * @return {@code true} if primary key is defined, {@code false} otherwise. */ public boolean hasPrimaryKey() { return getPrimaryKey() >= 0; } void checkStringValueIsLegal(long columnIndex, long rowToUpdate, String value) { if (isPrimaryKey(columnIndex)) { long rowIndex = findFirstString(columnIndex, value); if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { throwDuplicatePrimaryKeyException(value); } } } void checkIntValueIsLegal(long columnIndex, long rowToUpdate, long value) { if (isPrimaryKeyColumn(columnIndex)) { long rowIndex = findFirstLong(columnIndex, value); if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { throwDuplicatePrimaryKeyException(value); } } } // Checks if it is ok to use null value for given row and column. void checkDuplicatedNullForPrimaryKeyValue(long columnIndex, long rowToUpdate) { if (isPrimaryKeyColumn(columnIndex)) { RealmFieldType type = getColumnType(columnIndex); switch (type) { case STRING: case INTEGER: long rowIndex = findFirstNull(columnIndex); if (rowIndex != rowToUpdate && rowIndex != NO_MATCH) { throwDuplicatePrimaryKeyException("null"); } break; default: // Since it is sufficient to check the existence of duplicated null values // on PrimaryKey in supported types only, this part is left empty. } } } /** * Throws a properly formatted exception when multiple objects with the same primary key * value is detected. * * @param value the primary key value. */ public static void throwDuplicatePrimaryKeyException(Object value) { throw new RealmPrimaryKeyConstraintException("Value already exists: " + value); } // // Getters // public long getLong(long columnIndex, long rowIndex) { return nativeGetLong(nativePtr, columnIndex, rowIndex); } public boolean getBoolean(long columnIndex, long rowIndex) { return nativeGetBoolean(nativePtr, columnIndex, rowIndex); } public float getFloat(long columnIndex, long rowIndex) { return nativeGetFloat(nativePtr, columnIndex, rowIndex); } public double getDouble(long columnIndex, long rowIndex) { return nativeGetDouble(nativePtr, columnIndex, rowIndex); } public Date getDate(long columnIndex, long rowIndex) { return new Date(nativeGetTimestamp(nativePtr, columnIndex, rowIndex)); } /** * Gets the value of a (string) cell. * * @param columnIndex 0 based index value of the column * @param rowIndex 0 based index of the row. * @return value of the particular cell */ public String getString(long columnIndex, long rowIndex) { return nativeGetString(nativePtr, columnIndex, rowIndex); } public byte[] getBinaryByteArray(long columnIndex, long rowIndex) { return nativeGetByteArray(nativePtr, columnIndex, rowIndex); } public long getLink(long columnIndex, long rowIndex) { return nativeGetLink(nativePtr, columnIndex, rowIndex); } public Table getLinkTarget(long columnIndex) { long nativeTablePointer = nativeGetLinkTarget(nativePtr, columnIndex); // Copies context reference from parent. return new Table(this.sharedRealm, nativeTablePointer); } public boolean isNull(long columnIndex, long rowIndex) { return nativeIsNull(nativePtr, columnIndex, rowIndex); } /** * Returns a non-checking Row. Incorrect use of this Row will cause a hard core crash. * If error checking is required, use {@link #getCheckedRow(long)} instead. * * @param index the index of row to fetch. * @return the unsafe row wrapper object. */ public UncheckedRow getUncheckedRow(long index) { return UncheckedRow.getByRowIndex(context, this, index); } /** * Returns a non-checking Row. Incorrect use of this Row will cause a hard core crash. * If error checking is required, use {@link #getCheckedRow(long)} instead. * * @param nativeRowPointer the pointer to the row to fetch. * @return the unsafe row wrapper object. */ public UncheckedRow getUncheckedRowByPointer(long nativeRowPointer) { return UncheckedRow.getByRowPointer(context, this, nativeRowPointer); } /** * Returns a wrapper around Row access. All access will be error checked in JNI and will throw an appropriate * {@link RuntimeException} if used incorrectly. * <p> * If error checking is done elsewhere, consider using {@link #getUncheckedRow(long)} for better performance. * * @param index the index of row to fetch. * @return the safe row wrapper object. */ public CheckedRow getCheckedRow(long index) { return CheckedRow.get(context, this, index); } // // Setters // public void setLong(long columnIndex, long rowIndex, long value, boolean isDefault) { checkImmutable(); checkIntValueIsLegal(columnIndex, rowIndex, value); nativeSetLong(nativePtr, columnIndex, rowIndex, value, isDefault); } public void setBoolean(long columnIndex, long rowIndex, boolean value, boolean isDefault) { checkImmutable(); nativeSetBoolean(nativePtr, columnIndex, rowIndex, value, isDefault); } public void setFloat(long columnIndex, long rowIndex, float value, boolean isDefault) { checkImmutable(); nativeSetFloat(nativePtr, columnIndex, rowIndex, value, isDefault); } public void setDouble(long columnIndex, long rowIndex, double value, boolean isDefault) { checkImmutable(); nativeSetDouble(nativePtr, columnIndex, rowIndex, value, isDefault); } public void setDate(long columnIndex, long rowIndex, Date date, boolean isDefault) { if (date == null) { throw new IllegalArgumentException("Null Date is not allowed."); } checkImmutable(); nativeSetTimestamp(nativePtr, columnIndex, rowIndex, date.getTime(), isDefault); } /** * Sets a String value to a cell of Table, pointed by column and row index. * * @param columnIndex 0 based index value of the cell column. * @param rowIndex 0 based index value of the cell row. * @param value a String value to set in the cell. */ public void setString(long columnIndex, long rowIndex, String value, boolean isDefault) { checkImmutable(); if (value == null) { checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); } else { checkStringValueIsLegal(columnIndex, rowIndex, value); nativeSetString(nativePtr, columnIndex, rowIndex, value, isDefault); } } public void setBinaryByteArray(long columnIndex, long rowIndex, byte[] data, boolean isDefault) { checkImmutable(); nativeSetByteArray(nativePtr, columnIndex, rowIndex, data, isDefault); } public void setLink(long columnIndex, long rowIndex, long value, boolean isDefault) { checkImmutable(); nativeSetLink(nativePtr, columnIndex, rowIndex, value, isDefault); } public void setNull(long columnIndex, long rowIndex, boolean isDefault) { checkImmutable(); checkDuplicatedNullForPrimaryKeyValue(columnIndex, rowIndex); nativeSetNull(nativePtr, columnIndex, rowIndex, isDefault); } public void addSearchIndex(long columnIndex) { checkImmutable(); nativeAddSearchIndex(nativePtr, columnIndex); } public void removeSearchIndex(long columnIndex) { checkImmutable(); nativeRemoveSearchIndex(nativePtr, columnIndex); } /** * Defines a primary key for this table. This needs to be called manually before inserting data into the table. * * @param columnName the name of the field that will function primary key. "" or {@code null} will remove any * previous set magic key. * @throws io.realm.exceptions.RealmException if it is not possible to set the primary key due to the column * not having distinct values (i.e. violating the primary key constraint). */ public void setPrimaryKey(String columnName) { Table pkTable = getPrimaryKeyTable(); if (pkTable == null) { throw new RealmException("Primary keys are only supported if Table is part of a Group"); } cachedPrimaryKeyColumnIndex = nativeSetPrimaryKey(pkTable.nativePtr, nativePtr, columnName); } public void setPrimaryKey(long columnIndex) { setPrimaryKey(nativeGetColumnName(nativePtr, columnIndex)); } private Table getPrimaryKeyTable() { if (sharedRealm == null) { return null; } Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); if (pkTable.getColumnCount() == 0) { checkImmutable(); long columnIndex = pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_CLASS_COLUMN_NAME); pkTable.addSearchIndex(columnIndex); pkTable.addColumn(RealmFieldType.STRING, PRIMARY_KEY_FIELD_COLUMN_NAME); } return pkTable; } /** * Invalidates a cached primary key column index for the table. */ private void invalidateCachedPrimaryKeyIndex() { cachedPrimaryKeyColumnIndex = NO_MATCH; } /* * 1) Migration required to fix https://github.com/realm/realm-java/issues/1059 * This will convert INTEGER column to the corresponding STRING column if needed. * Any database created on Realm-Java 0.80.1 and below will have this error. * * 2) Migration required to fix: https://github.com/realm/realm-java/issues/1703 * This will remove the prefix "class_" from all table names in the pk_column * Any database created on Realm-Java 0.84.1 and below will have this error. */ public static boolean migratePrimaryKeyTableIfNeeded(SharedRealm sharedRealm) { if (sharedRealm == null || !sharedRealm.isInTransaction()) { throwImmutable(); } if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { return false; } Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); return nativeMigratePrimaryKeyTableIfNeeded(sharedRealm.getGroupNative(), pkTable.nativePtr); } public static boolean primaryKeyTableNeedsMigration(SharedRealm sharedRealm) { if (!sharedRealm.hasTable(PRIMARY_KEY_TABLE_NAME)) { return false; } Table pkTable = sharedRealm.getTable(PRIMARY_KEY_TABLE_NAME); return nativePrimaryKeyTableNeedsMigration(pkTable.nativePtr); } public boolean hasSearchIndex(long columnIndex) { return nativeHasSearchIndex(nativePtr, columnIndex); } public boolean isNullLink(long columnIndex, long rowIndex) { return nativeIsNullLink(nativePtr, columnIndex, rowIndex); } public void nullifyLink(long columnIndex, long rowIndex) { nativeNullifyLink(nativePtr, columnIndex, rowIndex); } boolean isImmutable() { return sharedRealm != null && !sharedRealm.isInTransaction(); } // This checking should be moved to SharedRealm level. void checkImmutable() { if (isImmutable()) { throwImmutable(); } } // // Count // public long count(long columnIndex, long value) { return nativeCountLong(nativePtr, columnIndex, value); } public long count(long columnIndex, float value) { return nativeCountFloat(nativePtr, columnIndex, value); } public long count(long columnIndex, double value) { return nativeCountDouble(nativePtr, columnIndex, value); } public long count(long columnIndex, String value) { return nativeCountString(nativePtr, columnIndex, value); } // // Searching methods. // public TableQuery where() { long nativeQueryPtr = nativeWhere(nativePtr); // Copies context reference from parent. return new TableQuery(this.context, this, nativeQueryPtr); } public long findFirstLong(long columnIndex, long value) { return nativeFindFirstInt(nativePtr, columnIndex, value); } public long findFirstBoolean(long columnIndex, boolean value) { return nativeFindFirstBool(nativePtr, columnIndex, value); } public long findFirstFloat(long columnIndex, float value) { return nativeFindFirstFloat(nativePtr, columnIndex, value); } public long findFirstDouble(long columnIndex, double value) { return nativeFindFirstDouble(nativePtr, columnIndex, value); } public long findFirstDate(long columnIndex, Date date) { if (date == null) { throw new IllegalArgumentException("null is not supported"); } return nativeFindFirstTimestamp(nativePtr, columnIndex, date.getTime()); } public long findFirstString(long columnIndex, String value) { if (value == null) { throw new IllegalArgumentException("null is not supported"); } return nativeFindFirstString(nativePtr, columnIndex, value); } /** * Searches for first occurrence of null. Beware that the order in the column is undefined. * * @param columnIndex the column to search in. * @return the row index for the first match found or {@link #NO_MATCH}. */ public long findFirstNull(long columnIndex) { return nativeFindFirstNull(nativePtr, columnIndex); } // Experimental feature public long lowerBoundLong(long columnIndex, long value) { return nativeLowerBoundInt(nativePtr, columnIndex, value); } public long upperBoundLong(long columnIndex, long value) { return nativeUpperBoundInt(nativePtr, columnIndex, value); } public Table pivot(long stringCol, long intCol, PivotType pivotType) { if (!this.getColumnType(stringCol).equals(RealmFieldType.STRING)) { throw new UnsupportedOperationException("Group by column must be of type String"); } if (!this.getColumnType(intCol).equals(RealmFieldType.INTEGER)) { throw new UnsupportedOperationException("Aggregation column must be of type Int"); } Table result = new Table(); nativePivot(nativePtr, stringCol, intCol, pivotType.value, result.nativePtr); return result; } // /** * Returns the table name as it is in the associated group. * * @return Name of the the table or null if it not part of a group. */ public String getName() { return nativeGetName(nativePtr); } /** * Returns the class name for the table. * * @return Name of the the table or null if it not part of a group. */ public String getClassName() { return getClassNameForTable(getName()); } public String toJson() { return nativeToJson(nativePtr); } @Override public String toString() { long columnCount = getColumnCount(); String name = getName(); StringBuilder stringBuilder = new StringBuilder("The Table "); if (name != null && !name.isEmpty()) { stringBuilder.append(getName()); stringBuilder.append(" "); } if (hasPrimaryKey()) { String pkFieldName = getColumnName(getPrimaryKey()); stringBuilder.append("has \'").append(pkFieldName).append("\' field as a PrimaryKey, and "); } stringBuilder.append("contains "); stringBuilder.append(columnCount); stringBuilder.append(" columns: "); for (int i = 0; i < columnCount; i++) { if (i != 0) { stringBuilder.append(", "); } stringBuilder.append(getColumnName(i)); } stringBuilder.append("."); stringBuilder.append(" And "); stringBuilder.append(size()); stringBuilder.append(" rows."); return stringBuilder.toString(); } private static void throwImmutable() { throw new IllegalStateException("Cannot modify managed objects outside of a write transaction."); } /** * Compares the schema of the current instance of Table with another instance. * * @param table the instance to compare with. It cannot be null. * @return {@code true} if the two instances have the same schema (column names and types). */ public boolean hasSameSchema(Table table) { if (table == null) { throw new IllegalArgumentException("The argument cannot be null"); } return nativeHasSameSchema(this.nativePtr, table.nativePtr); } /** * Checks if a given table name is a name for a model table. */ public static boolean isModelTable(String tableName) { return tableName.startsWith(TABLE_PREFIX); } /** * Reports the current versioning counter for the table. The versioning counter is guaranteed to * change when the contents of the table changes after advance_read() or promote_to_write(), or * immediately after calls to methods which change the table. * * @return version_counter for the table. */ public long getVersion() { return nativeVersion(nativePtr); } public static String getClassNameForTable(String name) { if (name == null) { return null; } if (!name.startsWith(TABLE_PREFIX)) { return name; } return name.substring(TABLE_PREFIX.length()); } public static String getTableNameForClass(String name) { if (name == null) { return null; } if (name.startsWith(TABLE_PREFIX)) { return name; } return TABLE_PREFIX + name; } protected native long createNative(); private native boolean nativeIsValid(long nativeTablePtr); private native long nativeAddColumn(long nativeTablePtr, int type, String name, boolean isNullable); private native long nativeAddColumnLink(long nativeTablePtr, int type, String name, long targetTablePtr); private native void nativeRenameColumn(long nativeTablePtr, long columnIndex, String name); private native void nativeRemoveColumn(long nativeTablePtr, long columnIndex); private native boolean nativeIsColumnNullable(long nativePtr, long columnIndex); private native void nativeConvertColumnToNullable(long nativeTablePtr, long columnIndex); private native void nativeConvertColumnToNotNullable(long nativePtr, long columnIndex); private native long nativeSize(long nativeTablePtr); private native void nativeClear(long nativeTablePtr); private native long nativeGetColumnCount(long nativeTablePtr); private native String nativeGetColumnName(long nativeTablePtr, long columnIndex); private native long nativeGetColumnIndex(long nativeTablePtr, String columnName); private native int nativeGetColumnType(long nativeTablePtr, long columnIndex); private native void nativeMoveLastOver(long nativeTablePtr, long rowIndex); public static native long nativeAddEmptyRow(long nativeTablePtr, long rows); private native long nativeGetSortedViewMulti(long nativeTableViewPtr, long[] columnIndices, boolean[] ascending); private native long nativeGetLong(long nativeTablePtr, long columnIndex, long rowIndex); private native boolean nativeGetBoolean(long nativeTablePtr, long columnIndex, long rowIndex); private native float nativeGetFloat(long nativeTablePtr, long columnIndex, long rowIndex); private native double nativeGetDouble(long nativeTablePtr, long columnIndex, long rowIndex); private native long nativeGetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex); private native String nativeGetString(long nativePtr, long columnIndex, long rowIndex); private native byte[] nativeGetByteArray(long nativePtr, long columnIndex, long rowIndex); private native long nativeGetLink(long nativePtr, long columnIndex, long rowIndex); public static native long nativeGetLinkView(long nativePtr, long columnIndex, long rowIndex); private native long nativeGetLinkTarget(long nativePtr, long columnIndex); private native boolean nativeIsNull(long nativePtr, long columnIndex, long rowIndex); native long nativeGetRowPtr(long nativePtr, long index); public static native void nativeSetLong(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); public static native void nativeSetLongUnique(long nativeTablePtr, long columnIndex, long rowIndex, long value); public static native void nativeSetBoolean(long nativeTablePtr, long columnIndex, long rowIndex, boolean value, boolean isDefault); public static native void nativeSetFloat(long nativeTablePtr, long columnIndex, long rowIndex, float value, boolean isDefault); public static native void nativeSetDouble(long nativeTablePtr, long columnIndex, long rowIndex, double value, boolean isDefault); public static native void nativeSetTimestamp(long nativeTablePtr, long columnIndex, long rowIndex, long dateTimeValue, boolean isDefault); public static native void nativeSetString(long nativeTablePtr, long columnIndex, long rowIndex, String value, boolean isDefault); public static native void nativeSetStringUnique(long nativeTablePtr, long columnIndex, long rowIndex, String value); public static native void nativeSetNull(long nativeTablePtr, long columnIndex, long rowIndex, boolean isDefault); // Use nativeSetStringUnique(null) for String column! public static native void nativeSetNullUnique(long nativeTablePtr, long columnIndex, long rowIndex); public static native void nativeSetByteArray(long nativePtr, long columnIndex, long rowIndex, byte[] data, boolean isDefault); public static native void nativeSetLink(long nativeTablePtr, long columnIndex, long rowIndex, long value, boolean isDefault); private native long nativeSetPrimaryKey(long privateKeyTableNativePtr, long nativePtr, String columnName); private static native boolean nativeMigratePrimaryKeyTableIfNeeded(long groupNativePtr, long primaryKeyTableNativePtr); private static native boolean nativePrimaryKeyTableNeedsMigration(long primaryKeyTableNativePtr); private native void nativeAddSearchIndex(long nativePtr, long columnIndex); private native void nativeRemoveSearchIndex(long nativePtr, long columnIndex); private native boolean nativeHasSearchIndex(long nativePtr, long columnIndex); private native boolean nativeIsNullLink(long nativePtr, long columnIndex, long rowIndex); public static native void nativeNullifyLink(long nativePtr, long columnIndex, long rowIndex); private native long nativeCountLong(long nativePtr, long columnIndex, long value); private native long nativeCountFloat(long nativePtr, long columnIndex, float value); private native long nativeCountDouble(long nativePtr, long columnIndex, double value); private native long nativeCountString(long nativePtr, long columnIndex, String value); private native long nativeWhere(long nativeTablePtr); public static native long nativeFindFirstInt(long nativeTablePtr, long columnIndex, long value); private native long nativeFindFirstBool(long nativePtr, long columnIndex, boolean value); private native long nativeFindFirstFloat(long nativePtr, long columnIndex, float value); private native long nativeFindFirstDouble(long nativePtr, long columnIndex, double value); private native long nativeFindFirstTimestamp(long nativeTablePtr, long columnIndex, long dateTimeValue); public static native long nativeFindFirstString(long nativeTablePtr, long columnIndex, String value); public static native long nativeFindFirstNull(long nativeTablePtr, long columnIndex); // FIXME: Disabled in cpp code, see comments there // private native long nativeFindAllTimestamp(long nativePtr, long columnIndex, long dateTimeValue); private native long nativeLowerBoundInt(long nativePtr, long columnIndex, long value); private native long nativeUpperBoundInt(long nativePtr, long columnIndex, long value); private native void nativePivot(long nativeTablePtr, long stringCol, long intCol, int pivotType, long resultPtr); private native String nativeGetName(long nativeTablePtr); private native String nativeToJson(long nativeTablePtr); private native boolean nativeHasSameSchema(long thisTable, long otherTable); private native long nativeVersion(long nativeTablePtr); private static native long nativeGetFinalizerPtr(); }