ESP32 и SQLite - проблема с ALTER TABLE

Я использую ESP32 и SQLite3 от https://github.com/siara-cc/esp32_arduino_sqlite3_lib Все в порядке, кроме команды ALTER TABLE. Когда я пытаюсь

    ALTER TABLE TAB_JOUEUR RENAME TO TAB_NEW_JOUEUR

Я получаю "логическую ошибку SQL". Я просто вижу, что исходный код по умолчанию не будет компилироваться с опцией ALTER TABLE, если определена SQLITE_OMIT_ALTERTABLE. Поэтому в начале источника .c я добавил:

 #if defined(SQLITE_OMIT_ALTERTABLE)
      #undef SQLITE_OMIT_ALTERTABLE
#endif

Но результат тот же самый.... Есть идеи?

, 👍1

Обсуждение

Похоже на известную проблему без известного решения: https://github.com/siara-cc/esp32_arduino_sqlite3_lib/issues/10, @Majenko

Благодарю. я посмотрю, так ли это и в случае с полем добавления., @Peter


1 ответ


1

Что ж. После долгих поисков вот способ "ПЕРЕИМЕНОВАТЬ" таблицу. Это немного сложно... Переименование таблицы в ESP SQLite очень интересно, потому что SQLite имеет очень ограниченные возможности для редактирования таблицы. Таким образом, вы не можете добавлять поля, удалять и так далее. Единственный способ-создать новую таблицу tmp, скопировать содержимое исходной таблицы в эту таблицу tmp, удалить исходную и переименовать tmp с именем новой. ВЫПАДАЮЩАЯ ТАБЛИЦА подходит для ESP-версии SQLite, но, как сказано в вопросе, ИЗМЕНЕНИЕ ПЕРЕИМЕНОВАНИЯ не работает.

Итак, вот как это сделать:

1-Какие у нас есть таблицы?

Когда мы инициализируем ESP, мы вызываем

  sqlite3_initialize(); 

а затем мы передаем SQLite файл, который он будет использовать для сохранения данных, с вызовом функции sqlite3_open (), указывающей имя ранее созданного файла.

В этот файл будут сохранены все наши собственные таблицы, а также определения наших таблиц. Для этого SQLite создаст системную таблицу. Таким образом, в базе данных у нас есть наши собственные таблицы, а также системные таблицы SQLite

2 - Таблица схемы базы данных

Одной из наиболее важных таблиц является таблица схемы базы данных. Его название зависит от версии SQLite. В версии, которую мы используем на ESP8266 или ESP32, ее имя-sqlite_master . Эта таблица содержит 4 поля:

  • типа. Значением может быть "таблица", "индекс", "представление" или "триггер".
  • имя. Значение - это имя данных. Если тип= "таблица", имя-это имя
    таблицы, определенной в этой записи
  • имя tbl_. Название таблицы. Поэтому, если тип= "имя", tbl_name = имя
  • корневая страница. Int, который является индексом в дереве Btree, используемом SQLite для доступа к данным
  • sql. Код для создания таблицы

3-Проверьте схему

Чтобы проверить содержимое этой конкретной таблицы, просто создайте ТАБЛИЦУ, а затем ВЫБЕРИТЕ * ИЗ sqlite_master

Вот что я сделал и каков результат:

ВЫЗОВ СОЗДАТЬ ТАБЛИЦУ:

создайте ТАБЛИЦУ, ЕСЛИ НЕ СУЩЕСТВУЕТ TAB_JOUEUR (ЦЕЛОЕ ЧИСЛО"tjoueur_id", ТЕКСТ "tjoueur_prenom", ТЕКСТ "tjoueur_nom", ТЕКСТ"tjoueur_ville", ПЕРВИЧНЫЙ КЛЮЧ (АВТОИНКРЕМЕНТ "tjoueur_id"))

Результат ВЫБОРА * ИЗ sqlite_master

  • тип = таблица
  • имя = TAB_JOUEUR
  • tbl_name = TAB_JOUEUR
  • корневая страница = 2
  • sql = СОЗДАТЬ ТАБЛИЦУ TAB_JOUEUR ('tjoueur_id' ЦЕЛОЕ ЧИСЛО, ТЕКСТ "tjoueur_prenom", ТЕКСТ "tjoueur_nom", ТЕКСТ"tjoueur_ville", ПЕРВИЧНЫЙ КЛЮЧ (АВТОИНКРЕМЕНТ "tjoueur_id"))

а потом

  • тип = таблица
  • имя = sqlite_sequence
  • tbl_name = последовательность sqlite_sequence
  • корневая страница = 3
  • sql = СОЗДАТЬ ТАБЛИЦУ sqlite_sequence(имя,раздел)

Как вы можете видеть, поле sql относительно таблицы "TAB_JOUEUR" является вызовом CREATE, но это не чистая копия исходного вызова CREATE. В исходном варианте как "создать" в нижнем регистре были дополнительные пробелы и есть "ЕСЛИ НЕ СУЩЕСТВУЕТ", которого у нас нет в поле sql sqlite_master. Но часть между () точно такая же (одинарная кавычка для tjoueur_id, двойная для tjoueur_nom, дополнительные пробелы...)

4 - Хитрость

Чтобы переименовать таблицу, вам просто нужно сделать ВЫБОР в sqlite_master, чтобы получить запись таблицы, которую вы хотите переименовать. Затем измените содержимое поля sql, чтобы заменить "старое имя" на "новое", и выполните обновление, используя корневую страницу в качестве идентификатора. Вот так:

void alter_table(String old_name, String new_name){

int rc ;
String data_rootpage;
String data_sql;
int pos_start;
char *err_msg = 0;

String query_sql = "SELECT rootpage,sql FROM sqlite_master WHERE type='table' AND name = '"+old_name+"'"; 

const char* sql = query_sql.c_str(); 
rc = sqlite3_prepare_v2(db_handle, sql, -1, &stmt, NULL);
rc = sqlite3_step(stmt);    
data_rootpage = (const char *)sqlite3_column_text(stmt, 0);     
data_sql = (const char *)sqlite3_column_text(stmt, 1); 

// Find first ( in the string
pos_start = data_sql.indexOf ('(');
String tmp = "CREATE TABLE "+new_name+" "+data_sql.substring(pos_start);

query_sql = "UPDATE sqlite_master SET sql='"+tmp+"', name='"+new_name+"', tbl_name='"+new_name+"' WHERE rootpage = "+data_rootpage;
sql = query_sql.c_str();    
rc = sqlite3_exec(db_handle, sql, 0, 0, &err_msg);  

sqlite3_close(db_handle);       
sqlite3_initialize();
sql_connect("/littlefs/transcom.db", &db_handle);}

Это базовый и не требующий тестирования (вы должны добавить его!). Я начинаю с поиска записи, касающейся таблицы "old_name". В поле sql я ищу " ( " , а затем заменяю первую часть данных поля sql новым именем таблицы. Затем я обновляю. Обратите внимание, что я не избегаю цитаты, потому что ранее я делал вызов CREATE без каких-либо " или " (проще!)

После этого я выполняю sqlite3_close (), а затем повторно открываю с помощью sqlite3_initialize(); Если вы этого не сделаете, SQLite не учитывает изменения (возможно, есть кэш для данных).

5 - Отличное, но... недостаточно!

Если вы попытаетесь это сделать, вы получите сообщение об ошибке при вызове обновления. Это потому, что для вас, "пользователь", sqlite_master-это таблица только для чтения. Поэтому вы не можете обновить его. Чтобы предотвратить запись в какую-либо таблицу (например, в таблицу sqlite_master), при ее создании SQLite устанавливает флаг "только для чтения". Хитрость заключается в том, чтобы отредактировать исходный код, чтобы придать этому флагу значение "чтение и запись". В файле sqlite3.c библиотеки это строка 17420. Вы увидите:

  #define TF_Readonly        0x0001    /* Read-only system table */

Просто измените его на

   #define TF_Readonly        0x0000    /* Read-only system table */

Поэтому, когда SQLite создаст системную таблицу, он установит флаг "чтение+запись", и, таким образом, вы сможете обновить таблицу sqlite_master. Вам нужно снова скомпилировать de lib (в IDE Arduino просто выберите неправильную плату, попробуйте скомпилировать, получите ошибку, выберите ESP32 и скомпилируйте снова, и IDE выполнит полную компиляцию lib)

Если у вас есть триггер и какая-то другая конкретная функция, возможно, было бы неплохо проверить результат select * from sqlite_master, чтобы узнать, нужно ли также обновлять некоторые записи. Но для базовой таблицы это сделает свою работу.

Надеюсь, это поможет.

Править

Если вы добавляете индекс (или более одного) в свою таблицу, вам также придется обновить записи "индекса" из sqlite_master. Они такие:

  • тип = индекс
  • имя = index_ttest_nom
  • tbl_name = TAB_TEST
  • корневая страница = 6
  • sql = СОЗДАЙТЕ УНИКАЛЬНЫЙ ИНДЕКС index_ttest_nom НА ВКЛАДКЕ TAB_TEST (ttest_nom)

Поэтому просто обновите значение tbl_name, указав новое имя таблицы, и обновите поле sql таким же образом.

,