0

I have a table in room database with a field set as index and it's autoincrement. Sqlite will save some meta data in its master table to keep count of the last auto-generated value.

Based on my app logic I will clear database and keep the structure; suppose I have inserted 3 items to the database and the mentioned action takes place so I clear items, but when I insert a new item its auto-generated field will be 4 which will cause overflow and app crash in the long run. I worked it around by removing autoincrement and setting the field manually!

Now my question is how can I reset the auto-incremented field value to be set to 1 after each database clearance (I will prefer room only way)?

Masked Man
  • 2,176
  • 2
  • 22
  • 41
  • This question was answered here: https://stackoverflow.com/questions/50878734/android-room-reset-auto-generated-key-on-each-app-run?noredirect=1&lq=1 In short you have to do these queries manually: `DELETE FROM SequenceAction;` and `DELETE FROM sqlite_sequence WHERE name = 'SequenceAction';` – muetzenflo May 11 '19 at 20:53
  • The whole *point* of `AUTOINCREMENT` is to never, ever, reuse an automatically generated rowid. Just leave it out of the `INTEGER PRIMARY KEY` column definition if you don't want that behavior. (Though if having it is causing your app to crash you have some serious issues) – Shawn May 11 '19 at 22:04
  • @Shawn its point is not to reuse for a specific table and that table transactions, not the lifetime. However, the `autoincrement` has a maximum limit of about **2147483648** and if the table uses a lot of transactions it will overflow in the long run. – Masked Man May 11 '19 at 23:17
  • 2147483648? Try 9223372036854775807. It's a signed 64 bit integer. You're never going to come remotely close to that unless you're manually inserting rowids that big. But, yes, the point is *never* reusing a rowid in the lifetime of a given table. See https://www.sqlite.org/autoinc.html . If you don't care if a rowid can be reused, don't use `AUTOINCREMENT`. – Shawn May 11 '19 at 23:25
  • Well thanks @Shawn, I thought it's 32 bit. – Masked Man May 12 '19 at 00:00
  • @MaskedMan **Warning**, setting an alias of the rowid column using 9223372036854775807 that has AUTOINCREMENT will result in a situation where no more rows can be added as SQLITE will then issue an SQLITE FULL error. – MikeT May 12 '19 at 00:11
  • @MikeT, by app crash I meant the same thing. – Masked Man May 12 '19 at 08:33

1 Answers1

1

The way that autoincrement works is that 2 values are used when determining a new value :-

  • the first is equivalent to using max(the_autoincrement_column) (i.e the column that aliases the rowid column that has AUTOINCREMENT coded),
  • the second is obtained from the table sqlite_sequence from the seq column of the row that has the table name in the name column.

    • Note that the value(s) are not stored in THE master table, sqlite_master (the schema) but in the sqlite_sequence table.
    • The sqlite_sequence table will only exist if AUTOINCREMENT has been used.

1 is added to the greater value.

To reset, in theory, you should delete all rows from the table and delete the respective row from the sqlite_sequence table.

However, room protects system tables. So in short there appears to be no way of using room to do the latter and hence the issue. Here is answer is an example that does the above BUT it has to be run outside of (before) room and is thus limited.

  • Note in the answer there is additional code that is used to start numbering from 0 (the Trigger).

However in regards to overflow then it's basically highly unlikely as per :-

  1. Maximum Number Of Rows In A Table

The theoretical maximum number of rows in a table is 2 to the power of 64 (18446744073709551616 or about 1.8e+19). This limit is unreachable since the maximum database size of 140 terabytes will be reached first. A 140 terabytes database can hold no more than approximately 1e+13 rows, and then only if there are no indices and if each row contains very little data. Limits In SQLite

With autoincrement it is 2 to power of 63 (9,223,372,036,854,775,808‬) (without autoincrement you can use negative values(java) so you can utilise the 64th bit hence the thoerectical maximum) as such the limitation would likely be disk capacity rather than the highest id being reached.

Additional

After some playing around, the following does reset the sequence whilst Room has the database.

That is the following builds the Room Database inserts two rows, resets the sequence (including deleting the recently added rows)

  • by opening the database as a standard SQLiteDatabase
    • Note the use of both OPENREADWRITE and ENABLEWRITEAHEADLOGGING
    • (if not the latter then a warning message saying that WAL can't be turned off as the db is open, so this just opens it in WAL mode)
  • deleting the existing rows in the table and
  • deleting the respective row from sqlite_sequence and finally
  • closing this other database.

:-

public class MainActivity extends AppCompatActivity {

    public static final String DBNAME = "mydatabase";
    public static final String MYTABLENAME = "mytable";

    MyDatabase mydb,mydb2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mydb = Room.databaseBuilder(this,MyDatabase.class,DBNAME).allowMainThreadQueries().build();
        MyTableDAO mytabledao = mydb.getMyTableDAO();
        MyTable mt1 = new MyTable();
        mt1.setName("Test001");
        mytabledao.insert(mt1);
        MyTable mt2 = new MyTable();
        mt2.setName("Test002");
        mytabledao.insert(mt2);
        for (MyTable mt: mytabledao.getAllMyTables()) {
            Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
        }

        /*
        while (mydb.isOpen()) {
            mydb.close();
        }
        Ouch if used :-
        E/ROOM: Invalidation tracker is initialized twice :/. (ignored)
        E/ROOM: Cannot run invalidation tracker. Is the db closed?
        java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
        */
        resetSequencedTable(MYTABLENAME);
        //mydb2 = Room.databaseBuilder(this,MyDatabase.class,DBNAME).allowMainThreadQueries().build(); // No Good

        /*
            Works even though :-
            05-12 12:31:40.112 28585-28585/? D/MYTABLEROW: ID=1 Name=Test001
            05-12 12:31:40.112 28585-28585/? D/MYTABLEROW: ID=2 Name=Test002
            05-12 12:31:40.114 28585-28585/? E/SQLiteLog: (5) statement aborts at 2: [PRAGMA journal_mode=PERSIST]
            05-12 12:31:40.115 28585-28585/? W/SQLiteConnection: Could not change the database journal mode of '/data/user/0/soa.myapplication/databases/mydatabase' from 'wal' to 'PERSIST' because the database is locked.  This usually means that there are other open connections to the database which prevents the database from enabling or disabling write-ahead logging mode.  Proceeding without changing the journal mode.
            05-12 12:31:40.126 28585-28585/? D/MYTABLEROW: ID=1 Name=Test003
            05-12 12:31:40.126 28585-28585/? D/MYTABLEROW: ID=2 Name=Test004
         */

        for (MyTable mt: mytabledao.getAllMyTables()) {
            Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
        }
        MyTable mt3 = new MyTable();
        mt3.setName("Test003");
        mytabledao.insert(mt3);
        MyTable mt4 = new MyTable();
        mt4.setName("Test004");
        mytabledao.insert(mt4);
        for (MyTable mt: mytabledao.getAllMyTables()) {
            Log.d("MYTABLEROW","ID=" + String.valueOf(mt.getId()) + " Name=" + mt.getName());
        }
    }

    private void resetSequencedTable(String table) {
        Log.d("RESETSEQ","Initiating sequence reset");
        SQLiteDatabase db = SQLiteDatabase.openDatabase(this.getDatabasePath(DBNAME).toString(),null,SQLiteDatabase.OPEN_READWRITE | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING);
        db.delete(table,null,null);
        String whereclause = "name=?";
        String[] whereargs = new String[]{table};
        db.delete("sqlite_sequence",whereclause,whereargs);
        db.close();
        Log.d("RESETSEQ", "Terminating sequence reset");
    }
}

The Entity for the table is :-

@Entity(tableName = MainActivity.MYTABLENAME)
public class MyTable {
    @PrimaryKey(autoGenerate = true)
    private long id;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
MikeT
  • 51,415
  • 16
  • 49
  • 68