LINUX.ORG.RU

Как правильно включить поддержку ICU в SQLite

 , ,


0

4

Привет, ЛОР.

Столкнулся с тем, что в SQLite не работают upper() и прочие регистрозависимые строковые функции для нелатинских символов. Долго искал, думал, я дурак, но оказывается, это поведение по умолчанию, давным-давно и много где описанное.

Почитал. В статье на Хабре предлагают либо пересобирать SQLite с включённым SQLITE_ENABLE_ICU, либо загрузить расширение. Ну, думаю, пересобрать и захламить систему я всегда успею, давай попробуем сначала второй путь.

Ссылка из статьи не работает, но в актуальных исходниках SQLite искомый icu.c находится без труда. Качаю. Собираю. Получаю libsqliteicu.so. Гружу его из SQLite CLI – LIKE и upper() начинают прекрасно работать.

Теперь пытаюсь подгрузить это в своей кутешной программе. Сначала вызываю sqlite3_enable_load_extension(), потом sqlite3_load_extension(). Первое проходит успешно, во втором получаю ошибку:

/usr/lib/libsqliteicu.so: undefined symbol: sqlite3_sqliteicu_init

Лезу в исходник: там вместо указанной присутствует функция sqlite3_icu_init(). Ну, хитрожопый я, конечно же, попробовал её переименовать. Добился только того, что текст ошибки поменялся на «error during initialization:».

По совету @utf8nowhere стал указывать другое имя точки входа третьим параметром sqlite3_load_extension() – тот же самый error during. Двоеточие намекает, конечно, что у ошибки должна быть дополнительная расшифровка, но где её искать, не очень понятно.

Подобного рода разночтения, конечно, первым делом наводят на мысль о несовместимости версий. Начинаю потрошить кутешный драйвер БД. Иду в /usr/lib/qt/plugins/sqldrivers (у меня он соответствует Qt5) и делаю ldd libqsqlite.so. Получаю, в числе прочего:

libsqlite3.so.0 => /usr/lib/libsqlite3.so.0 (0x0000728bea28f000)

В свою очередь, libsqlite3.so.0 является симлинком для libsqlite3.so.3.49.1. То есть «библиотечная» SQLite той же версии, что и sqlite3 CLI. Но во втором загрузка расширения работает, а в первой нет.

Куда копать? Можно, конечно, вернуться к варианту с пересборкой, но там свои вопросы будут…

P.S. Попробовал вариант с пересборкой, теперь кутешный драйвер верещит «Driver not loaded», а это может означать что угодно…

Обновление: как выяснил @utf8nowhere, загрузка расширения не работает, если есть открытые запросы к БД. Необходимо либо вызвать у них метод finish(), либо проследить, чтобы соответствующие объекты завершили работу (вызван деструктор).

★★★★★

Последнее исправление: hobbit (всего исправлений: 5)

У меня так:

% pkg info sqlite3-3.46.1_1,1
sqlite3-3.46.1_1,1
Name           : sqlite3
Version        : 3.46.1_1,1
Installed on   : Sat Mar 22 08:33:19 2025 MSK
Origin         : databases/sqlite3
Architecture   : FreeBSD:14:amd64
Prefix         : /usr/local
Categories     : databases
Licenses       : PD
Maintainer     : pavelivolkov@gmail.com
WWW            : https://www.sqlite.org/
Comment        : SQL database engine in a C library
Options        :
	ARMOR          : off
	DBPAGE         : on
	DBSTAT         : on
	DIRECT_READ    : on
	DQS            : off
	EXAMPLES       : off
	EXTENSION      : on
	FTS3_TOKEN     : on
	FTS4           : on
	FTS5           : on
	GEOPOLY        : off
	ICU            : on
	LIBEDIT        : on
	LIKENOTBLOB    : off
	MEMMAN         : off
	METADATA       : on
	NORMALIZE      : off
	NULL_TRIM      : off
	OFFSET         : off
	RBU            : off
	READLINE       : off
	RTREE          : on
	RTREE_INT      : off
	SECURE_DELETE  : on
	SESSION        : off
	SORT_REF       : off
	SOUNDEX        : off
	STAT3          : off
	STAT4          : off
	STATIC         : off
	STMT           : off
	STRIP          : on
	TCL            : off
	THREADS        : on
	TRUSTED_SCHEMA : off
	TS0            : off
	TS1            : off
	TS2            : on
	TS3            : off
	UNICODE61      : on
	UNKNOWN_SQL    : off
	UNLOCK_NOTIFY  : on
	UPDATE_LIMIT   : off
	URI            : on
	URI_AUTHORITY  : on
Shared Libs required:
	libc.so.7
	libedit.so.0
	libicudata.so.76
	libicui18n.so.76
	libicuuc.so.76
	libm.so.5
	libthr.so.3
	libz.so.6
Shared Libs provided:
	libsqlite3.so.0
Annotations    :
	FreeBSD_version: 1402504
	flavor         : default
Flat size      : 5.78MiB
Description    :
SQLite is an SQL database engine in a C library. Programs that link the SQLite
library can have SQL database access without running a separate RDBMS process.
The distribution comes with a standalone command-line access program (sqlite3)
that can be used to administer an SQLite database and which serves as an
example of how to use the SQLite library.
iZEN ★★★★★
()
undefined symbol: sqlite3_sqliteicu_init

Лезу в исходник: там вместо указанной присутствует функцияsqlite3_icu_init(). Ну, хитрожопый я, конечно же, попробовал её переименовать

Передать другое имя в zProc нельзя было просто?

int sqlite3_load_extension(
  sqlite3 *db,          /* Load the extension into this database connection */
  const char *zFile,    /* Name of the shared library containing extension */
  const char *zProc,    /* Entry point.  Derived from zFile if 0 */
  char **pzErrMsg       /* Put error message here if not 0 */
);
utf8nowhere ★★★
()

Ну, думаю, пересобрать и захламить систему я всегда успею

Захламить систему одной библиотекой SQLite (в ней всё амальгамировано и вкомпилируется внутрь)? Которую, по хорошему, надо не в систему ставить, а положить рядом с программой?

Кстати Qt тоже должен быть с ICU для полноты ощущений многоязычности.

raspopov
()
Ответ на: комментарий от raspopov

Которую, по хорошему, надо не в систему ставить, а положить рядом с программой?

В моём случае «рядом» придётся положить ещё и кутешный драйвер SQLite и сделать так, чтобы он именно эту libsqlite видел. А у QtSql на любое отклонение от нормы реакция одна – «Driver not loaded». И диагностировать причину этого not loaded крайне тяжело. Это, наверное, самый большой кошмар программиста и мейнтейнера с QtSql, хотя в целом библиотека очень мощная и удобная. Причём с libsqlite ещё не самый тяжёлый случай, у неё зависимостей немного. А вот когда нужны PostgreSQL и libpq… О-оооо….

Кстати Qt тоже должен быть с ICU

Это условие, видимо, выполняется. Потому, что QString::toUpper() как раз работает корректно.

В принципе, я даже думал, как обойти проблему на стороне C++, зачитав словарь из БД в промежуточный контейнер и искать уже в нём. Но это очень похоже на изготовление кривого костыля для конкретной СУБД, и это будет последний метод, если ничего больше не поможет. К тому же сейчас это поиск в словаре, там записей немного, а если такая же проблема всплывёт в основных таблицах…

Речь идёт о будущем опенсорсном проекте для сугубо домашнего применения. Поэтому возможно, SQLite всё и ограничится. А может, не ограничится, и я захочу альтернативным решением прикрутить туда PostgreSQL, чтобы БД можно было поставить на NAS и ходить туда с разных устройств в домашней сети. Не факт, что я до этого дойду, но если дойду – вышеупомянутый костыль для SQLite в проекте будет выглядеть ещё более нелепо.

hobbit ★★★★★
() автор топика
Последнее исправление: hobbit (всего исправлений: 2)
Ответ на: комментарий от hobbit

В принципе, я даже думал, как обойти проблему на стороне C++, зачитав словарь из БД в промежуточный контейнер и искать уже в нём. Но это очень похоже на изготовление кривого костыля для конкретной СУБД, и это будет последний метод, если ничего больше не поможет. К тому же сейчас это поиск в словаре, там записей немного, а если такая же проблема всплывёт в основных таблицах…

Накостылять столбец с символами в верхнем регистре... нэ подойдёт?

ratvier ★★
()
Ответ на: комментарий от dataman

Это, похоже, всё не про мой случай, поскольку sqlite3_enable_load_extension() возвращает мне SQLITE_OK. А вот на вызове sqlite3_load_extension() после этого уже затык.

Пример моего не особо рабочего кода

hobbit ★★★★★
() автор топика
Последнее исправление: hobbit (всего исправлений: 1)
Ответ на: комментарий от hobbit

Попробуй указать в вызове sqlite3_load_extension 2 параметром полный путь к .so файлу.

У меня даже консольный sqlite3 из текущего каталога не подгружает. Работает или .load ./libsqliteicu или вызов sqlite3 с LD_LIBRARY_PATH=..

Ja-Ja-Hey-Ho ★★★★★
()
Ответ на: комментарий от anonymous

Ну… там автор тоже entry point переименовывал :))) причём ему это помогло, в отличие от.

Единственное, что он делал сверх мной испробованного – помещал библиотеку в /usr/lib/sqlite3/ext.

hobbit ★★★★★
() автор топика

судя по https://github.com/sqlite/sqlite/blob/master/src/loadext.c#L618

  /* If no entry point was specified and the default legacy
  ** entry point name "sqlite3_extension_init" was not found, then
  ** construct an entry point name "sqlite3_X_init" where the X is
  ** replaced by the lowercase value of every ASCII alphabetic 
  ** character in the filename after the last "/" upto the first ".",
  ** and eliding the first three characters if they are "lib".  
  ** Examples:
  **
  **    /usr/local/lib/libExample5.4.3.so ==>  sqlite3_example_init
  **    C:/lib/mathfuncs.dll              ==>  sqlite3_mathfuncs_init
  */
кажется, что

Получаю libsqliteicu.so.

должно быть libicu.so, если хотеть именно sqlite3_icu_init()

Toxo2 ★★★★
()
Последнее исправление: Toxo2 (всего исправлений: 2)
Ответ на: комментарий от Toxo2

Да нет же. Библиотеку она находит, если нет – сообщение другое. И с именем точки входа разобрались, я даже ОП обновил.

Сейчас камень преткновения – это «error during initialization:».

hobbit ★★★★★
() автор топика
Ответ на: комментарий от Toxo2

Минимальный – попробую вечером, если силы будут.

На неминимальный :) была ссылка выше.

hobbit ★★★★★
() автор топика
Последнее исправление: hobbit (всего исправлений: 1)

Загрузить бинарник в gdb, поставить брекпоинт br sqlite3_icu_init, запустить run, при срабатывании брекпоинта сделать fin, посмотреть на код возврата из sqlite3_icu_init. Вероятно код возврата плохой, из-за этого и initialization failed. Разобраться, почему он плохой (не хватает переменных окружения? не вызывали setlocale?).

iliyap ★★★★★
()
Ответ на: комментарий от hobbit

Минимальный – попробую вечером, если силы будут.

https://wandbox.org/permlink/UARJxcyeqPavWIXq (CMake, качает и собирает libsqliteicu.so само, только зависимости в виде Qt, ICU, SQLite3 нужны)

mkdir build
cd build
cmake ..
make
./test_sqlite

УМНР/УМННР:

Database: connection ok
ICU is not supported (yet)
0x5f8fedb340c8
sqlite3_load_extension failed with  error during initialization:
utf8nowhere ★★★
()

Валится на регистрации функции "like":

int sqlite3IcuInit(sqlite3 *db){
# define SQLITEICU_EXTRAFLAGS (SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS)
  static const struct IcuScalar {
    const char *zName;                        /* Function name */
    unsigned char nArg;                       /* Number of arguments */
    unsigned int enc;                         /* Optimal text encoding */
    unsigned char iContext;                   /* sqlite3_user_data() context */
    void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
  } scalars[] = {
    {"icu_load_collation",2,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation},
    {"icu_load_collation",3,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation},
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU)
    {"regexp", 2, SQLITE_ANY|SQLITEICU_EXTRAFLAGS,         0, icuRegexpFunc},
    {"lower",  1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS,       0, icuCaseFunc16},
    {"lower",  2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS,       0, icuCaseFunc16},
    {"upper",  1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS,       1, icuCaseFunc16},
    {"upper",  2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS,       1, icuCaseFunc16},
    {"lower",  1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS,        0, icuCaseFunc16},
    {"lower",  2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS,        0, icuCaseFunc16},
    {"upper",  1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS,        1, icuCaseFunc16},
    {"upper",  2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS,        1, icuCaseFunc16},
    {"like",   2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS,        0, icuLikeFunc},
    {"like",   3, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS,        0, icuLikeFunc},
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */
  };
  int rc = SQLITE_OK;
  int i;
  
  for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){
    const struct IcuScalar *p = &scalars[i];
    rc = sqlite3_create_function(
        db, p->zName, p->nArg, p->enc, 
        p->iContext ? (void*)db : (void*)0,
        p->xFunc, 0, 0
    );
    __builtin_printf("%d %s %d\n", i, p->zName, rc); // <-- добавил
  }

  return rc;
}
0 icu_load_collation 0
1 icu_load_collation 0
2 regexp 0
3 lower 0
4 lower 0
5 upper 0
6 upper 0
7 lower 0
8 lower 0
9 upper 0
10 upper 0
11 like 5

Ошибка 5 означает SQLITE_BUSY, поэтому подозрение сразу падает на использование этой функции (на момент загрузки расширения).

Если не делать запроса-сравнения sqlCheck.prepare(QString::fromUtf8("select upper('Спутник')='СПУТНИК'")); sqlCheck.exec(), то база не BUSY и расширение загружается

Нужно или завершить запрос явно sqlCheck.finish();, или, лучше, сунуть QSqlQuery sqlCheck(sqlDb); с проверками в блок, чтобы по выходе деструктором всё завершалось

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 3)
Ответ на: комментарий от utf8nowhere

Если из init-функции плагина ICU возвращать SQLITE_OK_LOAD_PERMANENTLY - всё начинает работать. Там похоже возвращается SQLITE_OK, а sqlite3LoadExtension ожидает именно кода SQLITE_OK_LOAD_PERMANENTLY.

Ja-Ja-Hey-Ho ★★★★★
()
Ответ на: комментарий от Ja-Ja-Hey-Ho

Щито? Просто функция like используется в момент загрузки плагина и

  /* Check if an existing function is being overridden or deleted. If so,
  ** and there are active VMs, then return SQLITE_BUSY. If a function
  ** is being overridden/deleted but there are no active VMs, allow the
  ** operation to continue but invalidate all precompiled statements.
  */
  p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 0);
  if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==(u32)enc && p->nArg==nArg ){
    if( db->nVdbeActive ){
      sqlite3ErrorWithMsg(db, SQLITE_BUSY,
        "unable to delete/modify user-function due to active statements");
      assert( !db->mallocFailed );
      return SQLITE_BUSY;
    }else{
      sqlite3ExpirePreparedStatements(db, 0);
    }
utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

https://wandbox.org/permlink/UARJxcyeqPavWIXq (CMake, качает и собирает libsqliteicu.so само, только зависимости в виде Qt, ICU, SQLite3 нужны)

Спасибо, интересно, поиграл в это.

Если собирать как libicu.so, то действительно работает и просто res = sqlite3_load_extension(handle, "./libicu", 0, &errMsg); без третьего параметра.

Только это ничего не меняет. Оно всё равно BUSY, пока есть открытое соединение.

Но в целом - логично же. Нельзя же просто так взять и загрузить целый кусок кода, который меняет логику запросов. Думаю на больших БД такой финт точно не должен работать без restart/reload их.

Toxo2 ★★★★
()
Ответ на: комментарий от Toxo2

На больших БД для начала такой элементарщине хорошо бы работать из коробки, без подгрузки расширений. Я понимаю, когда речь идёт про какой-нибудь full-text search по офисным блобам… :)

С другой стороны, могу понять и почему sqlite по умолчанию собирается без сабжа. Это встраиваемая СУБД, а тут каждые полмегабайта могут иметь значение.

Но «могу понять» не означает, что мне от этого легче. :)))

hobbit ★★★★★
() автор топика
Последнее исправление: hobbit (всего исправлений: 2)
Ответ на: комментарий от Ja-Ja-Hey-Ho

https://www.sqlite.org/loadext.html#persistent_loadable_extensions:

To clarify: an extension for which the initialization function returns SQLITE_OK_LOAD_PERMANENTLY continues to exist in memory after the database connection closes. However, the extension is not automatically registered with subsequent database connections.

utf8nowhere ★★★
()