Android: почему текстовое поле в таблице SQLite требует функции getBlob ()? - PullRequest
1 голос
/ 21 июня 2019

Структура таблицы Calcs из базы данных your.db ( SQLite с использованием SQLite Studio ):

Id: Integer
Content: Text  // Type is text, not BLOB (It's a JSON string)
...

Я знаю, что тип SQLite это просто предложение, но я проверил тип за пределами SQLite Studio (см. PS в конце)

My Kotlin код

...
your = File(this.filesDir, "your.db").absolutePath
val db = SQLiteDatabase.openDatabase(your,null,  
             SQLiteDatabase.OPEN_READWRITE)
val c = db.query("Calcs", arrayOf("Conteudo"), "Id=?", 
        arrayOf(1), null, null, null)
val stat = c.moveToFirst()
if (! stat)   exitApp(this)
val conteudo = c.getString(c.getColumnIndex("Conteudo"))
...

Ошибка: SQLite exception unable to convert BLOB to string

Решение моей проблемы было действительно странным.Я заменил последнюю строку исходного кода для:

val blob = c.getBlob(c.getColumnIndex("Conteudo"))
var conteudo = String(blob)
conteudo = conteudo.substring(0,conteudo.length-1)

Это работает, потому что после того, как я использовал функцию fromGSon() из gson для преобразования JSON строка в Kotlin классе, заполненном моими данными.

Сначала он загружает byte array с 0 дополнительным байтом, добавленным в конец строки.Таким образом, длина равна исходному полю в моей таблице SQLite (я проверил с помощью функции length SQLite ) плюс 1. Поэтому мне нужно было извлечь это 0 из строки, прежде чем я смогу использовать в JSON преобразование.

Вопросы:

1) Почему столбцу, объявленному как Text в SQLite, требуется функция getBlob() в Kotlin ?
2) Почему в конце получается дополнительный 0 байт?

PS: :

Поскольку это может быть причудой из SQLite Studio , я решил взглянуть на SQLite утилита командной строки (My SQLite обновлен, и моя система Windows ):

Я заполнил поле conteudo с помощью текстового файла your.txt (2723 символа), которая хранит содержимое, с помощью команды:

UPDATE my_table set my_column=readfile('c:\data\android\your.txt') 
 where id=1;

Итак, я пошел в папку C:\Data\Android, которая содержит your.db и запустил SQLite :

C:\Data\Android> SQLite3 your.db

Затем я запустил следующие команды:

.header on
.mode column
.pragma table_info('calcs');

Показано.

cid         name        type        notnull     dflt_value  pk
----------  ----------  ----------  ----------  ----------  --
0           Id          INTEGER     0                       1
1           Nome        TEXT        0                       0
2           Conteudo    TEXT        0                       0
3           Cont2       BLOB        0                       0

Последнее, но не менее важное:

Команда select typeof(conteudo) from calcs и вернул text.

Ниже я положил 2 изображения, чтобы проверить свои претензии, Первый из SQLite Studio Структура:

SQLite Studio image

Второе изображение из моего Kotlin код Kotlin piece of code

Мой последний фрагмент кода в Kotlin , после всех проверок;

val your = fullPath(this,"your.db")
val db = SQLiteDatabase.openDatabase(your,null, 
          SQLiteDatabase.OPEN_READWRITE)
val c = db.query("Calcs", arrayOf("Conteudo",
       "Length(Conteudo) As Len"), "Nome=?",
       arrayOf(calcActive), null, null, null)
val stat = c.moveToFirst()
if (! stat) exitApp(this)
val blob = c.getBlob(c.getColumnIndex("Conteudo"))
val len = c.getInt(c.getColumnIndex("Len"))
val len2 = blob.size
val char =blob[len2-1]
val result = "Len $len, Len Blob: $len2, $char"

* resultLEN 2273 LEN BLOB 2274,0

ОБНОВЛЕНИЕ 2 :

Теперь внезапно getStrings снова работает для меня.Клянусь, я не редактирую тип поля.В середине я просто добавил фиктивное поле для тестирования.Однако для меня ясно, что getBlob должно выдать исключение и не возвращает молча массив байтов с добавленным в конце 0.

1 Ответ

1 голос
/ 21 июня 2019

Я считаю, что вам нужно выяснить, как вы вставляете или обновляете столбец.Это означает, что SQLite позволяет вам хранить данные любого типа в любом столбце, независимо от типа, который вы используете для определения столбца.Возможно, вы читаете Типы данных в SQLite версии 3

, например, попробуйте запустить это в SQLite Studio: -

DROP TABLE IF EXISTS type_affinity_example;
CREATE TABLE IF NOT EXISTS type_affinity_example (
    id INTEGER PRIMARY KEY, -- <<<<<<<< MUST BE INTEGER VALUE ELSE INVALID DATATYPE
    col1 rumplestiltskin,
    col2 BLOB,
    col3 INTEGER,
    col4 NUMERIC,
    col5 REAL,
    col6 REALINT, -- because of rule 1 will be INTEGER type affinity NOT REAL!!!!
    col7 TEXT
);

INSERT INTO type_affinity_example (col1,col2,col3,col4,col5,col6,col7) VALUES

    (1.3456,1.3456,1.3456,1.3456,1.3456,1.3456,1.3456),
    ('A','A','A','A','A','A','A'),
    (1,1,1,1,1,1,1),
    (x'0102',x'0102',x'0102',x'0102',x'0102',x'0102',x'0102')
;

SELECT 
    rowid,
    *,
    typeof(id) AS ctypeid, 
    typeof(col1) AS ctype1, 
    typeof(col2) AS ctype2, 
    typeof(col3) AS ctype3, 
    typeof(col4) AS ctype4,  
    typeof(col5) AS ctype5, 
    typeof(col6) AS ctype6, 
    typeof(col7) AS ctype7  
FROM  type_affinity_example

, которая выдает: -

enter image description here

  • Использовался Navicat (различные инструменты управления SQLite имеют свой собственный способ обработки больших двоичных объектов)

Как таковойвесьма вероятно, что вы непреднамеренно сохраняете данные как BLOB.

Возможно, вы случайно поменяли местами столбцы, используя cont2 вместо conteudo и, таким образом, некоторое время сохранял большой двоичный объект.

Это может быть из-за того, что вы делаете Сначала он загружает байтовый массив с добавлением 0 дополнительных байтов в конец строки. иЗатем вы вставляете / обновляете байтовый массив, а не строку.

Я бы предложил не использовать SELECT typeof(conteudo) FROM calcs WHERE id = 1, поскольку при этом проверяется только одна строка, а выполняется: -

SELECT typeof(conteudo) FROM calcs;

или

SELECT Id,typeof(conteudo) FROM calcs WHERE typeof(conteudo) = 'blob';

Еще одна мысль, что вы добавляете байт при вставке / вверхдатирование, но тот, который не является допустимым символом при извлечении и, следовательно, что он затем конвертируется в типе BLOB (не пытался увидеть, происходит ли это, но это может быть в SDK, что он не может конвертировать и предполагает, что необратимый символ приравнивается к BLOB), возможно попробуйте дополнить длину символом, а не байтом.

Дополнительно

Вас может заинтересовать следующий эксперимент, но он может объяснить волшебный дополнительный байт: -

Вот DBHelper: -

class DBHelper(context: Context?) : SQLiteOpenHelper(context, DBNAME, null, DBVERSION) {

    internal var mDB: SQLiteDatabase

    init {
        this.mDB = this.writableDatabase
    }

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(crt_mytable_sql)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {}

    fun insert(nome: String, conteudo: String, cont2: ByteArray): Long {
        val cv = ContentValues()
        cv.put(COl_MYTABLE_NOME, nome)
        cv.put(COl_MYTABLE_CONTEUDO, conteudo)
        cv.put(COL_MYTABLE_CONT2, cont2)
        return mDB.insert(TBL_MYTABLE, null, cv)
    }

    companion object {
        val DBNAME = "your.db"
        val DBVERSION = 1
        val TBL_MYTABLE = "calcs"
        val COL_MYTABLE_ID = "Id"
        val COl_MYTABLE_NOME = "Nome"
        val COl_MYTABLE_CONTEUDO = "Conteudo"
        val COL_MYTABLE_CONT2 = "Cont2"

        internal val crt_mytable_sql = "CREATE TABLE IF NOT EXISTS " + TBL_MYTABLE + "(" +
                COL_MYTABLE_ID + " INTEGER PRIMARY KEY, " +
                COl_MYTABLE_NOME + " TEXT, " +
                COl_MYTABLE_CONTEUDO + " TEXT, " +
                COL_MYTABLE_CONT2 + " BLOB " +
                ")"
    }
}

и действие: -

class MainActivity : AppCompatActivity() {

    internal var myTestString = "abcdefghijklmnopqrstuvwxyz1234567890"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var mDBHlpr = DBHelper(this)
        val sb = StringBuilder().append("{")
        for (i in 0..0) {
            sb.append(myTestString)
        }
        sb.append("}")
        mDBHlpr.insert("BLAH", sb.substring(0, sb.length - 1),byteArrayOf(0))
        val columns = arrayOf(DBHelper.COl_MYTABLE_CONTEUDO,"length(" + DBHelper.COl_MYTABLE_CONTEUDO + ") AS len")
        val csr = mDBHlpr.writableDatabase.query(DBHelper.TBL_MYTABLE, columns, null, null, null, null, null)
        while (csr.moveToNext()) {
            Log.d("EXTRACTED",
                    csr.getString(
                    csr.getColumnIndex(DBHelper.COl_MYTABLE_CONTEUDO)) + " Length is " +
                    csr.getString(csr.getColumnIndex("len"))
            )
            val barray = csr.getBlob(csr.getColumnIndex(DBHelper.COl_MYTABLE_CONTEUDO))
            Log.d("BALEN","Length of the byte array is " + barray.size)
            val sb2 = StringBuilder()
            for (b in barray) {
                sb2.append((b and (0xFF).toByte()).toChar())
            }
            Log.d("VIABLOB",
                    sb2.toString() + " Length is " +
                            csr.getString(csr.getColumnIndex("len"))
            )
        }
    }
}

При первом запуске он выдает: -

2019-06-21 15:25:32.633 D/EXTRACTED: {abcdefghijklmnopqrstuvwxyz1234567890 Length is 37
2019-06-21 15:25:32.633 D/BALEN: Length of the byte array is 38
2019-06-21 15:25:32.633 D/VIABLOB: {abcdefghijklmnopqrstuvwxyz1234567890�� Length is 37

т.е.извлечение TEXT в виде большого двоичного объекта и его преобразование - это не просто плавание (то же самое с Java, это не проблема Kotlin, и я не подозреваю проблему SQLite, но я подозреваю проблему SDK)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...