diff --git a/docs/python-api.rst b/docs/python-api.rst index 79ad510..9f69b34 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -1430,6 +1430,12 @@ The ``detect_fts()`` method returns the associated SQLite FTS table name, if one >> db["authors"].detect_fts() "authors_fts" +The ``.virtual_table_using`` property reveals if a table is a virtual table. It returns ``None`` for regular tables and the upper case version of the type of virtual table otherwise. For example:: + + >> db["authors"].enable_fts(["name"]) + >> db["authors_fts"].virtual_table_using + "FTS5" + .. _python_api_fts: Enabling full-text search diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 2e70f9d..3600e88 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -10,12 +10,32 @@ import itertools import json import os import pathlib +import re import sys import textwrap import uuid SQLITE_MAX_VARS = 999 +_virtual_table_using_re = re.compile( + r""" +^ # Start of string +\s*CREATE\s+VIRTUAL\s+TABLE\s+ # CREATE VIRTUAL TABLE +( + '(?P[^']*(?:''[^']*)*)' | # single quoted name + "(?P[^"]*(?:""[^"]*)*)" | # double quoted name + `(?P[^`]+)` | # `backtick` quoted name + \[(?P[^\]]+)\] | # [...] quoted name + (?P # SQLite non-quoted identifier + [A-Za-z_\u0080-\uffff] # \u0080-\uffff = "any character larger than u007f" + [A-Za-z_\u0080-\uffff0-9\$]* # zero-or-more alphanemuric or $ + ) +) +\s+(IF\s+NOT\s+EXISTS\s+)? # IF NOT EXISTS (optional) +USING\s+(?P\w+) # e.g. USING FTS5 +""", + re.VERBOSE | re.IGNORECASE, +) try: import pandas as pd @@ -676,6 +696,14 @@ class Table(Queryable): ) return fks + @property + def virtual_table_using(self): + "Returns type of virtual table or None if this is not a virtual table" + match = _virtual_table_using_re.match(self.schema) + if match is None: + return None + return match.groupdict()["using"].upper() + @property def indexes(self): sql = 'PRAGMA index_list("{}")'.format(self.name) diff --git a/tests/test_introspect.py b/tests/test_introspect.py index cbec0a1..80cfee6 100644 --- a/tests/test_introspect.py +++ b/tests/test_introspect.py @@ -1,4 +1,4 @@ -from sqlite_utils.db import Index, View +from sqlite_utils.db import Index, View, Database import pytest @@ -142,3 +142,49 @@ def test_triggers(fresh_db): (t.name, t.table) for t in fresh_db["authors"].triggers } assert [] == fresh_db["other"].triggers + + +@pytest.mark.parametrize( + "sql,expected_name,expected_using", + [ + ( + """ + CREATE VIRTUAL TABLE foo USING FTS5(name) + """, + "foo", + "FTS5", + ), + ( + """ + CREATE VIRTUAL TABLE "foo" USING FTS4(name) + """, + "foo", + "FTS4", + ), + ( + """ + CREATE VIRTUAL TABLE IF NOT EXISTS `foo` USING FTS4(name) + """, + "foo", + "FTS4", + ), + ( + """ + CREATE VIRTUAL TABLE IF NOT EXISTS `foo` USING fts5(name) + """, + "foo", + "FTS5", + ), + ( + """ + CREATE TABLE IF NOT EXISTS `foo` (id integer primary key) + """, + "foo", + None, + ), + ], +) +def test_virtual_table_using(sql, expected_name, expected_using): + db = Database(memory=True) + db.execute(sql) + assert db[expected_name].virtual_table_using == expected_using