Test for presence of fts5 extension in sqlite backend initialisation and migration

pull/7861/head
Matt Westcott 2022-01-11 01:43:02 +00:00 zatwierdzone przez Matt Westcott
rodzic cc90ec62b7
commit 4248d406c0
4 zmienionych plików z 59 dodań i 30 usunięć

Wyświetl plik

@ -12,14 +12,13 @@ def SearchBackend(params):
from .mysql.mysql import MySQLSearchBackend
return MySQLSearchBackend(params)
elif connection.vendor == 'sqlite':
import sqlite3
if sqlite3.sqlite_version_info < (3, 19, 0):
# Prior to version 3.19, SQLite doesn't support FTS5 queries with column filters ('{column_1 column_2} : query'), so we need to fall back to the dummy fallback backend.
from .fallback import DatabaseSearchBackend
return DatabaseSearchBackend(params)
else:
from .sqlite.utils import fts5_available
if fts5_available():
from .sqlite.sqlite import SQLiteSearchBackend
return SQLiteSearchBackend(params)
else:
from .fallback import DatabaseSearchBackend
return DatabaseSearchBackend(params)
else:
from .fallback import DatabaseSearchBackend
return DatabaseSearchBackend(params)

Wyświetl plik

@ -0,0 +1,22 @@
import sqlite3
from django.db import OperationalError
def fts5_available():
# based on https://stackoverflow.com/a/36656216/1853523
if sqlite3.sqlite_version_info < (3, 19, 0):
# Prior to version 3.19, SQLite doesn't support FTS5 queries with
# column filters ('{column_1 column_2} : query'), which the sqlite
# fulltext backend needs
return False
tmp_db = sqlite3.connect(':memory:')
try:
tmp_db.execute('CREATE VIRTUAL TABLE fts5test USING fts5 (data);')
except OperationalError:
return False
finally:
tmp_db.close()
return True

Wyświetl plik

@ -47,6 +47,8 @@ class Migration(migrations.Migration):
]
elif connection.vendor == 'sqlite':
from wagtail.search.backends.database.sqlite.utils import fts5_available
operations = [
migrations.AddField(
model_name='indexentry',
@ -63,25 +65,29 @@ class Migration(migrations.Migration):
name='title',
field=models.TextField(),
),
migrations.SeparateDatabaseAndState(state_operations=[
migrations.CreateModel(
name='sqliteftsindexentry',
fields=[
('index_entry', models.OneToOneField(primary_key=True, serialize=False, to='wagtailsearch.indexentry', on_delete=models.CASCADE, db_column='rowid')),
('title', models.TextField()),
('body', models.TextField(null=True)),
('autocomplete', models.TextField(null=True)),
],
options={'db_table': '%s_fts' % IndexEntry._meta.db_table},
),
], database_operations=[
migrations.RunSQL(sql=('CREATE VIRTUAL TABLE %s_fts USING fts5(autocomplete, body, title)' % IndexEntry._meta.db_table), reverse_sql=('DROP TABLE %s_fts' % IndexEntry._meta.db_table)),
migrations.RunSQL(sql=('CREATE TRIGGER insert_wagtailsearch_indexentry_fts AFTER INSERT ON %s BEGIN INSERT INTO %s_fts(title, body, autocomplete, rowid) VALUES (NEW.title, NEW.body, NEW.autocomplete, NEW.id); END' % (IndexEntry._meta.db_table, IndexEntry._meta.db_table)), reverse_sql=('DROP TRIGGER insert_wagtailsearch_indexentry_fts')),
migrations.RunSQL(sql=('CREATE TRIGGER update_wagtailsearch_indexentry_fts AFTER UPDATE ON %s BEGIN UPDATE %s_fts SET title=NEW.title, body=NEW.body, autocomplete=NEW.autocomplete WHERE rowid=NEW.id; END' % (IndexEntry._meta.db_table, IndexEntry._meta.db_table)), reverse_sql=('DROP TRIGGER update_wagtailsearch_indexentry_fts')),
migrations.RunSQL(sql=('CREATE TRIGGER delete_wagtailsearch_indexentry_fts AFTER DELETE ON %s BEGIN DELETE FROM %s_fts WHERE rowid=OLD.id; END' % (IndexEntry._meta.db_table, IndexEntry._meta.db_table)), reverse_sql=('DROP TRIGGER delete_wagtailsearch_indexentry_fts'))
])
]
if fts5_available():
operations.append(
migrations.SeparateDatabaseAndState(state_operations=[
migrations.CreateModel(
name='sqliteftsindexentry',
fields=[
('index_entry', models.OneToOneField(primary_key=True, serialize=False, to='wagtailsearch.indexentry', on_delete=models.CASCADE, db_column='rowid')),
('title', models.TextField()),
('body', models.TextField(null=True)),
('autocomplete', models.TextField(null=True)),
],
options={'db_table': '%s_fts' % IndexEntry._meta.db_table},
),
], database_operations=[
migrations.RunSQL(sql=('CREATE VIRTUAL TABLE %s_fts USING fts5(autocomplete, body, title)' % IndexEntry._meta.db_table), reverse_sql=('DROP TABLE %s_fts' % IndexEntry._meta.db_table)),
migrations.RunSQL(sql=('CREATE TRIGGER insert_wagtailsearch_indexentry_fts AFTER INSERT ON %s BEGIN INSERT INTO %s_fts(title, body, autocomplete, rowid) VALUES (NEW.title, NEW.body, NEW.autocomplete, NEW.id); END' % (IndexEntry._meta.db_table, IndexEntry._meta.db_table)), reverse_sql=('DROP TRIGGER insert_wagtailsearch_indexentry_fts')),
migrations.RunSQL(sql=('CREATE TRIGGER update_wagtailsearch_indexentry_fts AFTER UPDATE ON %s BEGIN UPDATE %s_fts SET title=NEW.title, body=NEW.body, autocomplete=NEW.autocomplete WHERE rowid=NEW.id; END' % (IndexEntry._meta.db_table, IndexEntry._meta.db_table)), reverse_sql=('DROP TRIGGER update_wagtailsearch_indexentry_fts')),
migrations.RunSQL(sql=('CREATE TRIGGER delete_wagtailsearch_indexentry_fts AFTER DELETE ON %s BEGIN DELETE FROM %s_fts WHERE rowid=OLD.id; END' % (IndexEntry._meta.db_table, IndexEntry._meta.db_table)), reverse_sql=('DROP TRIGGER delete_wagtailsearch_indexentry_fts'))
])
)
elif connection.vendor == 'mysql':
operations = [
migrations.AddField(

Wyświetl plik

@ -195,6 +195,7 @@ if connection.vendor == 'postgresql':
AbstractIndexEntry = AbstractPostgresIndexEntry
elif connection.vendor == 'sqlite':
from wagtail.search.backends.database.sqlite.utils import fts5_available
class AbstractSQLiteIndexEntry(BaseIndexEntry):
"""
@ -210,14 +211,15 @@ elif connection.vendor == 'sqlite':
AbstractIndexEntry = AbstractSQLiteIndexEntry
class SQLiteFTSIndexEntry(models.Model):
autocomplete = TextField(null=True)
title = TextField(null=False)
body = TextField(null=True)
index_entry = OneToOneField(primary_key=True, to='wagtailsearch.indexentry', on_delete=models.CASCADE, db_column='rowid')
if fts5_available():
class SQLiteFTSIndexEntry(models.Model):
autocomplete = TextField(null=True)
title = TextField(null=False)
body = TextField(null=True)
index_entry = OneToOneField(primary_key=True, to='wagtailsearch.indexentry', on_delete=models.CASCADE, db_column='rowid')
class Meta:
db_table = "wagtailsearch_indexentry_fts"
class Meta:
db_table = "wagtailsearch_indexentry_fts"
elif connection.vendor == 'mysql':