Протоколирование SQL-запросов в Android

#android #sqlite #logging

#Android #sqlite #протоколирование

Вопрос:

Я использую query функции для построения SQL-запросов для своих таблиц. Есть ли способ увидеть фактический выполняемый запрос? Например, протоколировать это где-нибудь?

Пока лучшее, что я мог сделать, это взглянуть на mQuery-элемент курсора, используя точку останова. Я бы хотел выводить запросы автоматически, хотя. Этот участник, конечно, не является общедоступным и не имеет средства получения.


Просто для записи, вот реализация принятого ответа.

 /**
 * Implement the cursor factory in order to log the queries before returning 
 * the cursor
 * 
 * @author Vincent @ MarvinLabs
 */
public class SQLiteCursorFactory implements CursorFactory {

    private boolean debugQueries = false;

    public SQLiteCursorFactory() {
        this.debugQueries = false;
    }

    public SQLiteCursorFactory(boolean debugQueries) {
        this.debugQueries = debugQueries;
    }

    @Override
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, 
                            String editTable, SQLiteQuery query) {
        if (debugQueries) {
            Log.d("SQL", query.toString());
        }
        return new SQLiteCursor(db, masterQuery, editTable, query);
    }
}
  

Комментарии:

1. Есть ли какой-нибудь способ SQLiteCursorFactory.newCursor() получить доступ к selectionArgs , чтобы включить его в выходные данные Log ?

2. Просто для записи. SQLiteCursor(db, masterQuery, editTable, query); не рекомендуется с API 11 уровня, используйте SQLiteCursor(masterQuery, editTable, query); вместо этого.

3. Как вы используете вышеупомянутый SQLiteCursorFactory?

4. Смотрите developer.android.com/reference/android/database/sqlite/…

5. Если вы используете SQLiteOpenHelper, вам просто нужно передать свой SQLiteCursorFactory родительскому элементу в конструкторе вашего помощника, вот так: public DbHelper(Context context) { super(context, DATABASE_NAME, new SQLiteCursorFactory(true), DATABASE_VERSION); }

Ответ №1:

 adb shell setprop log.tag.SQLiteStatements VERBOSE
  

Не забудьте перезапустить свое приложение после установки этого свойства.

Также можно включить протоколирование времени выполнения. Более подробная информация доступна здесь: http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/java/android/database/sqlite/SQLiteDebug.java

Комментарии:

1.Перезапуска вашего приложения может быть недостаточно. Если нет, запустите и это тоже: adb shell stop adb shell start Это перезапустит все, и вы начнете видеть много SQL.

2. Мне не удалось включить подробное протоколирование SQLite, хотя согласно android.googlesource.com/platform/frameworks/base.git / /… , параметр протоколирования действителен, и ничего не изменилось.

3. Где происходит протоколирование? logcat?

4. Журналы отображаются в logcat, убедитесь, что у вас не включен фильтр, журналы не связаны с вашим идентификатором приложения. Кроме того, если вы хотите снова отключить журналы: adb shell setprop log.tag.SQLiteStatements INFO

Ответ №2:

Вы можете применить свои собственные SQLiteDatabase.CursorFactory к базе данных. (Смотрите параметры openDatabase.) Это позволит вам создать свой собственный подкласс Cursor , который хранит запрос в легкодоступном поле.

редактировать: На самом деле, возможно, вам даже не придется создавать подкласс Cursor . Просто попросите newCursor() метод вашего factory вернуть стандартное значение SQLiteCursor , но перед этим запишите запрос.

Комментарии:

1. Работает хорошо, но при этом запрос отображается до привязки каких-либо аргументов. Есть ли способ просмотреть запрос после привязки всех предоставленных аргументов?

2. Похоже, это работает для запросов «на чтение», но не работает для вставок и обновлений. Есть ли решение, которое также работает для вставок и обновлений?

3. как включить протоколирование запросов вообще? где происходит протоколирование?

Ответ №3:

adb shell setprop log.tag.SQLiteLog V 
adb shell setprop log.tag.SQLiteStatements V 
остановка adb shell 
запуск оболочки adb

Комментарии:

1.Из-за отсутствия uid -> adb shell su 0 start adb shell su 0 stop

2. этот параметр можно отменить, заменив V на D , поскольку это ярлыки для уровней ведения журнала VERBOSE и DEBUG.

3. Спасибо! Мне не хватало строки adb shell setprop log.tag.SQLiteLog V , и это не хотело работать. Теперь это происходит!

Ответ №4:

Использовать SQLiteQueryBuilder это до боли просто. buildQuery() возвращает необработанную строку sql, которую затем можно протоколировать:

 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(ExampleTable.TABLE_NAME);
String sql = qb.buildQuery(projection, selection, null, null, sortOrder, null);
Log.d("Example", sql);
  

Комментарии:

1. не рекомендуется с исходного кода API leve 11

Ответ №5:

Пока лучшее, что я мог сделать, это взглянуть на mQuery-элемент курсора, используя точку останова. Этот элемент, конечно, не является общедоступным и не имеет средства получения, следовательно, его невозможно вывести. Есть предложения получше?

Ответ №6:

Если вы используете SQLiteDatabase с его стандартными методами, такими как вставка, обновление и удаление, пользовательский CursorFactory не будет работать.

Я реализовал свое не очень отличное, но рабочее решение на основе класса SQLiteDatabase. Он просто повторяет логику методов insert, update и delete, но без инструкций и фактически выполняет протоколирование инструкций SQL.

 public class SQLiteStatementsLogger {

    private static final String TAG = SQLiteStatementsLogger.class.getSimpleName();

    private static final String[] CONFLICT_VALUES = new String[]
            {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};

    public void logInsert(String table, String nullColumnHack, ContentValues values) {
        logInsertWithOnConflict(table, nullColumnHack, values, 0);
    }

    public static void logInsertWithOnConflict(String table, String nullColumnHack,
                                     ContentValues initialValues, int conflictAlgorithm) {
        StringBuilder sql = new StringBuilder();
        sql.append("INSERT");
        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
        sql.append(" INTO ");
        sql.append(table);
        sql.append('(');

        Object[] bindArgs = null;
        int size = (initialValues != null amp;amp; initialValues.size() > 0)
                ? initialValues.size() : 0;
        if (size > 0) {
            bindArgs = new Object[size];
            int i = 0;
            for (String colName : initialValues.keySet()) {
                sql.append((i > 0) ? "," : "");
                sql.append(colName);
                bindArgs[i  ] = initialValues.get(colName);
            }
            sql.append(')');
            sql.append(" VALUES (");
            for (i = 0; i < size; i  ) {
                sql.append((i > 0) ? ",?" : "?");
            }
        } else {
            sql.append(nullColumnHack   ") VALUES (NULL");
        }
        sql.append(')');
        sql.append(". (");
        for (Object arg : bindArgs) {
            sql.append(String.valueOf(arg)).append(",");
        }
        sql.deleteCharAt(sql.length()-1).append(')');
        Log.d(TAG, sql.toString());
    }

    public static void logUpdate(String table, ContentValues values, String whereClause, String[] whereArgs) {
        logUpdateWithOnConflict(table, values, whereClause, whereArgs, 0);
    }

    public static void logUpdateWithOnConflict(String table, ContentValues values,
                                        String whereClause, String[] whereArgs, int conflictAlgorithm) {

        StringBuilder sql = new StringBuilder(120);
        sql.append("UPDATE ");
        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
        sql.append(table);
        sql.append(" SET ");

        // move all bind args to one array
        int setValuesSize = values.size();
        int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize   whereArgs.length);
        Object[] bindArgs = new Object[bindArgsSize];
        int i = 0;
        for (String colName : values.keySet()) {
            sql.append((i > 0) ? "," : "");
            sql.append(colName);
            bindArgs[i  ] = values.get(colName);
            sql.append("=?");
        }
        if (whereArgs != null) {
            for (i = setValuesSize; i < bindArgsSize; i  ) {
                bindArgs[i] = whereArgs[i - setValuesSize];
            }
        }
        if (!TextUtils.isEmpty(whereClause)) {
            sql.append(" WHERE ");
            sql.append(whereClause);
        }
        sql.append(". (");
        for (Object arg : bindArgs) {
            sql.append(String.valueOf(arg)).append(",");
        }
        sql.deleteCharAt(sql.length()-1).append(')');
        Log.d(TAG, sql.toString());
    }

    public static void logDelete(String table, String whereClause, String[] whereArgs) {
        StringBuilder sql = new StringBuilder("DELETE FROM "   table);
        if (!TextUtils.isEmpty(whereClause)) {
            sql.append(" WHERE "   whereClause);
            sql.append(". (");
            for (Object arg : whereArgs) {
                sql.append(String.valueOf(arg)).append(",");
            }
            sql.deleteCharAt(sql.length()-1).append(')');
        }
        Log.d(TAG, sql.toString());
    }
}
  

Помните, что не следует использовать регистратор в версиях выпуска. Это может увеличить время выполнения запросов.
Вы можете проверить, находится ли сборка в режиме отладки с помощью этой строки кода:

 0 != (getApplicationInfo().flags amp; ApplicationInfo.FLAG_DEBUGGABLE)
  

Ответ №7:

Если это одноразовый сценарий, я бы предложил ввести ошибку (например, введите выражение like LIEK вместо LIKE!) и просмотрите Eclipse LogCat на предмет любых ошибок! HTH!

Ответ №8:

Если вы используете ContentProvider для доступа к БД, вот как я заставил его протоколировать запросы. Не идеальное решение, но оно работает для разработки

 @Override
  public boolean onCreate() {
    dbHelper = new MySQLiteHelper(getContext());
    database=dbHelper.getWritableDatabase();

    if(!database.isReadOnly())
      database.execSQL("PRAGMA foreign_keys=ON;");
    return true;
  }            

  SQLiteDatabase.CursorFactory cursorFactory = new SQLiteDatabase.CursorFactory() {      
    @Override
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) {
      Log.d(TAG, "Query: " query);

      return new SQLiteCursor(db, masterQuery, editTable, query);
    }
  };

  @Override
  public Cursor query(Uri uri, String[] projection, String selection,
      String[] selectionArgs, String sortOrder) {
    String table =getTableName(uri);

    if(Constants.LOG_QUERIES){
      database = SQLiteDatabase.openOrCreateDatabase(database.getPath(), cursorFactory);
    }

    Cursor cursor =database.query(table,  projection, selection, selectionArgs, null, null, sortOrder);
    cursor.moveToFirst();

    return cursor;
  }
  

Это вызовет исключение DatabaseNotClosed, но вы сможете увидеть запрос

Ответ №9:

Лично я регистрирую текст с помощью java.util.Log и Log.w("MYAPPNAME", "My text...") функции. Он отображается в представлении журнала Eclipse и может быть отфильтрован для вывода только журналов для «MYAPPNAME».

Комментарии:

1. -1: Это не помогает регистрировать SQL-запрос, к которому невозможно получить доступ.