public interface

SupportSQLiteOpenHelper

implements java.io.Closeable

 androidx.sqlite.db.SupportSQLiteOpenHelper

Overview

An interface to map the behavior of . Note that since that class requires overriding certain methods, support implementation uses SupportSQLiteOpenHelper.Factory to create this and SupportSQLiteOpenHelper.Callback to implement the methods that should be overridden.

Summary

Methods
public voidclose()

Close any open database object.

public java.lang.StringgetDatabaseName()

Return the name of the SQLite database being opened, as given to the constructor.

public SupportSQLiteDatabasegetReadableDatabase()

Create and/or open a database.

public SupportSQLiteDatabasegetWritableDatabase()

Create and/or open a database that will be used for reading and writing.

public voidsetWriteAheadLoggingEnabled(boolean enabled)

Enables or disables the use of write-ahead logging for the database.

Methods

public java.lang.String getDatabaseName()

Return the name of the SQLite database being opened, as given to the constructor. null indicates an in-memory database.

public void setWriteAheadLoggingEnabled(boolean enabled)

Enables or disables the use of write-ahead logging for the database. Write-ahead logging cannot be used with read-only databases so the value of this flag is ignored if the database is opened read-only.

Parameters:

enabled: True if write-ahead logging should be enabled, false if it should be disabled.

See also: SupportSQLiteDatabase.enableWriteAheadLogging()

public SupportSQLiteDatabase getWritableDatabase()

Create and/or open a database that will be used for reading and writing. The first time this is called, the database will be opened and SupportSQLiteOpenHelper.Callback.onCreate(SupportSQLiteDatabase), SupportSQLiteOpenHelper.Callback.onUpgrade(SupportSQLiteDatabase, int, int) and/or SupportSQLiteOpenHelper.Callback.onOpen(SupportSQLiteDatabase) will be called.

Once opened successfully, the database is cached, so you can call this method every time you need to write to the database. (Make sure to call SupportSQLiteOpenHelper.close() when you no longer need the database.) Errors such as bad permissions or a full disk may cause this method to fail, but future attempts may succeed if the problem is fixed.

Database upgrade may take a long time, you should not call this method from the application main thread, including from .

Returns:

a read/write database object valid until SupportSQLiteOpenHelper.close() is called

public SupportSQLiteDatabase getReadableDatabase()

Create and/or open a database. This will be the same object returned by SupportSQLiteOpenHelper.getWritableDatabase() unless some problem, such as a full disk, requires the database to be opened read-only. In that case, a read-only database object will be returned. If the problem is fixed, a future call to SupportSQLiteOpenHelper.getWritableDatabase() may succeed, in which case the read-only database object will be closed and the read/write object will be returned in the future.

Like SupportSQLiteOpenHelper.getWritableDatabase(), this method may take a long time to return, so you should not call it from the application main thread, including from .

Returns:

a database object valid until SupportSQLiteOpenHelper.getWritableDatabase() or SupportSQLiteOpenHelper.close() is called.

public void close()

Close any open database object.

Source

/*
 * Copyright (C) 2016 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.sqlite.db;

import android.content.Context;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}.
 * Note that since that class requires overriding certain methods, support implementation
 * uses {@link Factory#create(Configuration)} to create this and {@link Callback} to implement
 * the methods that should be overridden.
 */
@SuppressWarnings("unused")
public interface SupportSQLiteOpenHelper extends Closeable {
    /**
     * Return the name of the SQLite database being opened, as given to
     * the constructor. {@code null} indicates an in-memory database.
     */
    @Nullable
    String getDatabaseName();

    /**
     * Enables or disables the use of write-ahead logging for the database.
     *
     * Write-ahead logging cannot be used with read-only databases so the value of
     * this flag is ignored if the database is opened read-only.
     *
     * @param enabled True if write-ahead logging should be enabled, false if it
     *                should be disabled.
     * @see SupportSQLiteDatabase#enableWriteAheadLogging()
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    void setWriteAheadLoggingEnabled(boolean enabled);

    /**
     * Create and/or open a database that will be used for reading and writing.
     * The first time this is called, the database will be opened and
     * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be
     * called.
     *
     * <p>Once opened successfully, the database is cached, so you can
     * call this method every time you need to write to the database.
     * (Make sure to call {@link #close} when you no longer need the database.)
     * Errors such as bad permissions or a full disk may cause this method
     * to fail, but future attempts may succeed if the problem is fixed.</p>
     *
     * <p class="caution">Database upgrade may take a long time, you
     * should not call this method from the application main thread, including
     * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
     *
     * @return a read/write database object valid until {@link #close} is called
     * @throws SQLiteException if the database cannot be opened for writing
     */
    SupportSQLiteDatabase getWritableDatabase();

    /**
     * Create and/or open a database.  This will be the same object returned by
     * {@link #getWritableDatabase} unless some problem, such as a full disk,
     * requires the database to be opened read-only.  In that case, a read-only
     * database object will be returned.  If the problem is fixed, a future call
     * to {@link #getWritableDatabase} may succeed, in which case the read-only
     * database object will be closed and the read/write object will be returned
     * in the future.
     *
     * <p class="caution">Like {@link #getWritableDatabase}, this method may
     * take a long time to return, so you should not call it from the
     * application main thread, including from
     * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
     *
     * @return a database object valid until {@link #getWritableDatabase}
     * or {@link #close} is called.
     * @throws SQLiteException if the database cannot be opened
     */
    SupportSQLiteDatabase getReadableDatabase();

    /**
     * Close any open database object.
     */
    @Override void close();

    /**
     * Handles various lifecycle events for the SQLite connection, similar to
     * {@link SQLiteOpenHelper}.
     */
    @SuppressWarnings({"unused", "WeakerAccess"})
    abstract class Callback {
        private static final String TAG = "SupportSQLite";
        /**
         * Version number of the database (starting at 1); if the database is older,
         * {@link Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
         * will be used to upgrade the database; if the database is newer,
         * {@link Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
         * will be used to downgrade the database.
         */
        public final int version;

        /**
         * Creates a new Callback to get database lifecycle events.
         * @param version The version for the database instance. See {@link #version}.
         */
        public Callback(int version) {
            this.version = version;
        }

        /**
         * Called when the database connection is being configured, to enable features such as
         * write-ahead logging or foreign key support.
         * <p>
         * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade},
         * or {@link #onOpen} are called. It should not modify the database except to configure the
         * database connection as required.
         * </p>
         * <p>
         * This method should only call methods that configure the parameters of the database
         * connection, such as {@link SupportSQLiteDatabase#enableWriteAheadLogging}
         * {@link SupportSQLiteDatabase#setForeignKeyConstraintsEnabled},
         * {@link SupportSQLiteDatabase#setLocale},
         * {@link SupportSQLiteDatabase#setMaximumSize}, or executing PRAGMA statements.
         * </p>
         *
         * @param db The database.
         */
        public void onConfigure(@NonNull SupportSQLiteDatabase db) {

        }

        /**
         * Called when the database is created for the first time. This is where the
         * creation of tables and the initial population of the tables should happen.
         *
         * @param db The database.
         */
        public abstract void onCreate(@NonNull SupportSQLiteDatabase db);

        /**
         * Called when the database needs to be upgraded. The implementation
         * should use this method to drop tables, add tables, or do anything else it
         * needs to upgrade to the new schema version.
         *
         * <p>
         * The SQLite ALTER TABLE documentation can be found
         * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
         * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
         * you can use ALTER TABLE to rename the old table, then create the new table and then
         * populate the new table with the contents of the old table.
         * </p><p>
         * This method executes within a transaction.  If an exception is thrown, all changes
         * will automatically be rolled back.
         * </p>
         *
         * @param db         The database.
         * @param oldVersion The old database version.
         * @param newVersion The new database version.
         */
        public abstract void onUpgrade(@NonNull SupportSQLiteDatabase db, int oldVersion,
                int newVersion);

        /**
         * Called when the database needs to be downgraded. This is strictly similar to
         * {@link #onUpgrade} method, but is called whenever current version is newer than requested
         * one.
         * However, this method is not abstract, so it is not mandatory for a customer to
         * implement it. If not overridden, default implementation will reject downgrade and
         * throws SQLiteException
         *
         * <p>
         * This method executes within a transaction.  If an exception is thrown, all changes
         * will automatically be rolled back.
         * </p>
         *
         * @param db         The database.
         * @param oldVersion The old database version.
         * @param newVersion The new database version.
         */
        public void onDowngrade(@NonNull SupportSQLiteDatabase db, int oldVersion, int newVersion) {
            throw new SQLiteException("Can't downgrade database from version "
                    + oldVersion + " to " + newVersion);
        }

        /**
         * Called when the database has been opened.  The implementation
         * should check {@link SupportSQLiteDatabase#isReadOnly} before updating the
         * database.
         * <p>
         * This method is called after the database connection has been configured
         * and after the database schema has been created, upgraded or downgraded as necessary.
         * If the database connection must be configured in some way before the schema
         * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
         * </p>
         *
         * @param db The database.
         */
        public void onOpen(@NonNull SupportSQLiteDatabase db) {

        }

        /**
         * The method invoked when database corruption is detected. Default implementation will
         * delete the database file.
         *
         * @param db the {@link SupportSQLiteDatabase} object representing the database on which
         *           corruption is detected.
         */
        public void onCorruption(@NonNull SupportSQLiteDatabase db) {
            // the following implementation is taken from {@link DefaultDatabaseErrorHandler}.

            Log.e(TAG, "Corruption reported by sqlite on database: " + db.getPath());
            // is the corruption detected even before database could be 'opened'?
            if (!db.isOpen()) {
                // database files are not even openable. delete this database file.
                // NOTE if the database has attached databases, then any of them could be corrupt.
                // and not deleting all of them could cause corrupted database file to remain and
                // make the application crash on database open operation. To avoid this problem,
                // the application should provide its own {@link DatabaseErrorHandler} impl class
                // to delete ALL files of the database (including the attached databases).
                deleteDatabaseFile(db.getPath());
                return;
            }

            List<Pair<String, String>> attachedDbs = null;
            try {
                // Close the database, which will cause subsequent operations to fail.
                // before that, get the attached database list first.
                try {
                    attachedDbs = db.getAttachedDbs();
                } catch (SQLiteException e) {
                /* ignore */
                }
                try {
                    db.close();
                } catch (IOException e) {
                /* ignore */
                }
            } finally {
                // Delete all files of this corrupt database and/or attached databases
                if (attachedDbs != null) {
                    for (Pair<String, String> p : attachedDbs) {
                        deleteDatabaseFile(p.second);
                    }
                } else {
                    // attachedDbs = null is possible when the database is so corrupt that even
                    // "PRAGMA database_list;" also fails. delete the main database file
                    deleteDatabaseFile(db.getPath());
                }
            }
        }

        private void deleteDatabaseFile(String fileName) {
            if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
                return;
            }
            Log.w(TAG, "deleting the database file: " + fileName);
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    SupportSQLiteCompat.Api16Impl.deleteDatabase(new File(fileName));
                } else {
                    try {
                        final boolean deleted = new File(fileName).delete();
                        if (!deleted) {
                            Log.e(TAG, "Could not delete the database file " + fileName);
                        }
                    } catch (Exception error) {
                        Log.e(TAG, "error while deleting corrupted database file", error);
                    }
                }
            } catch (Exception e) {
            /* print warning and ignore exception */
                Log.w(TAG, "delete failed: ", e);
            }
        }
    }

    /**
     * The configuration to create an SQLite open helper object using {@link Factory}.
     */
    class Configuration {
        /**
         * Context to use to open or create the database.
         */
        @NonNull
        public final Context context;
        /**
         * Name of the database file, or null for an in-memory database.
         */
        @Nullable
        public final String name;
        /**
         * The callback class to handle creation, upgrade and downgrade.
         */
        @NonNull
        public final SupportSQLiteOpenHelper.Callback callback;
        /**
         * If {@code true} the database will be stored in the no-backup directory.
         */
        public final boolean useNoBackupDirectory;
        /**
         * If {@code true} the database will be delete and its data loss in the case that it
         * cannot be opened.
         */
        public final boolean allowDataLossOnRecovery;

        Configuration(
                @NonNull Context context,
                @Nullable String name,
                @NonNull Callback callback) {
            this(context, name, callback, false);
        }

        Configuration(
                @NonNull Context context,
                @Nullable String name,
                @NonNull Callback callback,
                boolean useNoBackupDirectory) {
            this(context, name, callback, useNoBackupDirectory, false);
        }

        Configuration(
                @NonNull Context context,
                @Nullable String name,
                @NonNull Callback callback,
                boolean useNoBackupDirectory,
                boolean allowDataLossOnRecovery) {
            this.context = context;
            this.name = name;
            this.callback = callback;
            this.useNoBackupDirectory = useNoBackupDirectory;
            this.allowDataLossOnRecovery = allowDataLossOnRecovery;
        }

        /**
         * Creates a new Configuration.Builder to create an instance of Configuration.
         *
         * @param context to use to open or create the database.
         */
        @NonNull
        public static Builder builder(@NonNull Context context) {
            return new Builder(context);
        }

        /**
         * Builder class for {@link Configuration}.
         */
        public static class Builder {
            Context mContext;
            String mName;
            SupportSQLiteOpenHelper.Callback mCallback;
            boolean mUseNoBackupDirectory;
            boolean mAllowDataLossOnRecovery;

            /**
             * <p>
             * Throws an {@link IllegalArgumentException} if the {@link Callback} is {@code null}.
             * <p>
             * Throws an {@link IllegalArgumentException} if the {@link Context} is {@code null}.
             * <p>
             * Throws an {@link IllegalArgumentException} if the {@link String} database
             * name is {@code null}. {@see Context#getNoBackupFilesDir()}
             *
             * @return The {@link Configuration} instance
             */
            @NonNull
            public Configuration build() {
                if (mCallback == null) {
                    throw new IllegalArgumentException("Must set a callback to create the"
                            + " configuration.");
                }
                if (mContext == null) {
                    throw new IllegalArgumentException("Must set a non-null context to create"
                            + " the configuration.");
                }
                if (mUseNoBackupDirectory && TextUtils.isEmpty(mName)) {
                    throw new IllegalArgumentException(
                            "Must set a non-null database name to a configuration that uses the "
                                    + "no backup directory.");
                }
                return new Configuration(mContext, mName, mCallback, mUseNoBackupDirectory,
                        mAllowDataLossOnRecovery);
            }

            Builder(@NonNull Context context) {
                mContext = context;
            }

            /**
             * @param name Name of the database file, or null for an in-memory database.
             * @return This
             */
            @NonNull
            public Builder name(@Nullable String name) {
                mName = name;
                return this;
            }

            /**
             * @param callback The callback class to handle creation, upgrade and downgrade.
             * @return this
             */
            @NonNull
            public Builder callback(@NonNull Callback callback) {
                mCallback = callback;
                return this;
            }

            /**
             * Sets whether to use a no backup directory or not.
             * @param useNoBackupDirectory If {@code true} the database file will be stored in the
             *                             no-backup directory.
             * @return this
             */
            @NonNull
            public Builder noBackupDirectory(boolean useNoBackupDirectory) {
                mUseNoBackupDirectory = useNoBackupDirectory;
                return this;
            }

            /**
             * Sets whether to delete and recreate the database file in situations when the
             * database file cannot be opened, thus allowing for its data to be lost.
             * @param allowDataLossOnRecovery If {@code true} the database file might be recreated
             *                                in the case that it cannot be opened.
             * @return this
             */
            @NonNull
            public Builder allowDataLossOnRecovery(boolean allowDataLossOnRecovery) {
                mAllowDataLossOnRecovery = allowDataLossOnRecovery;
                return this;
            }
        }
    }

    /**
     * Factory class to create instances of {@link SupportSQLiteOpenHelper} using
     * {@link Configuration}.
     */
    interface Factory {
        /**
         * Creates an instance of {@link SupportSQLiteOpenHelper} using the given configuration.
         *
         * @param configuration The configuration to use while creating the open helper.
         *
         * @return A SupportSQLiteOpenHelper which can be used to open a database.
         */
        @NonNull
        SupportSQLiteOpenHelper create(@NonNull Configuration configuration);
    }
}