mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-09-27 20:34:12 +02:00
work-in-progress FTS for attached dbs
This commit is contained in:
parent
885a0b321d
commit
c2e8270b9f
1 changed files with 130 additions and 70 deletions
|
|
@ -601,6 +601,10 @@ class Database:
|
||||||
|
|
||||||
return self.quote(value)
|
return self.quote(value)
|
||||||
|
|
||||||
|
def database_names(self) -> List[str]:
|
||||||
|
"List of string database names available in this connection."
|
||||||
|
return [r[1] for r in self.execute("PRAGMA database_list").fetchall()]
|
||||||
|
|
||||||
def table_names(self, fts4: bool = False, fts5: bool = False) -> List[str]:
|
def table_names(self, fts4: bool = False, fts5: bool = False) -> List[str]:
|
||||||
"""
|
"""
|
||||||
List of string table names in this database.
|
List of string table names in this database.
|
||||||
|
|
@ -614,7 +618,20 @@ class Database:
|
||||||
if fts5:
|
if fts5:
|
||||||
where.append("sql like '%USING FTS5%'")
|
where.append("sql like '%USING FTS5%'")
|
||||||
sql = "select name from sqlite_master where {}".format(" AND ".join(where))
|
sql = "select name from sqlite_master where {}".format(" AND ".join(where))
|
||||||
return [r[0] for r in self.execute(sql).fetchall()]
|
|
||||||
|
def _exec_in_db(db_name: str, sql: str) -> List[str]:
|
||||||
|
if db_name == "main":
|
||||||
|
db_name = ""
|
||||||
|
if db_name:
|
||||||
|
sql = sql.replace("sqlite_master", f"{db_name}.sqlite_master")
|
||||||
|
table_names = [r[0] for r in self.execute(sql).fetchall()]
|
||||||
|
if db_name:
|
||||||
|
return [f"{db_name}.{tbl_name}" for tbl_name in table_names]
|
||||||
|
return table_names
|
||||||
|
|
||||||
|
return list(
|
||||||
|
itertools.chain(*[_exec_in_db(db_name, sql) for db_name in self.database_names()])
|
||||||
|
)
|
||||||
|
|
||||||
def view_names(self) -> List[str]:
|
def view_names(self) -> List[str]:
|
||||||
"List of string view names in this database."
|
"List of string view names in this database."
|
||||||
|
|
@ -1271,12 +1288,34 @@ class Database:
|
||||||
return result and bool(result[0])
|
return result and bool(result[0])
|
||||||
|
|
||||||
|
|
||||||
|
def _split_names(fullname: str) -> Tuple[str, str]:
|
||||||
|
if '.' not in fullname:
|
||||||
|
return '', fullname
|
||||||
|
return fullname.split('.')
|
||||||
|
|
||||||
|
|
||||||
|
def dbname(fullname: str) -> str:
|
||||||
|
return _split_names(fullname)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def tablename(fullname: str) -> str:
|
||||||
|
return _split_names(fullname)[1]
|
||||||
|
|
||||||
|
|
||||||
|
def escaped_name(fullname: str) -> str:
|
||||||
|
"""This is how SQLite expects a database name joined to a table name to use the square-bracket escapes."""
|
||||||
|
db, tbl = _split_names(fullname)
|
||||||
|
if not db:
|
||||||
|
return f'[{tbl}]'
|
||||||
|
return f'{db}.[{tbl}]'
|
||||||
|
|
||||||
|
|
||||||
class Queryable:
|
class Queryable:
|
||||||
def exists(self) -> bool:
|
def exists(self) -> bool:
|
||||||
"Does this table or view exist yet?"
|
"Does this table or view exist yet?"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __init__(self, db, name):
|
def __init__(self, db, name: str):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|
@ -1292,7 +1331,7 @@ class Queryable:
|
||||||
:param where_args: Parameters to use with that fragment - an iterable for ``id > ?``
|
:param where_args: Parameters to use with that fragment - an iterable for ``id > ?``
|
||||||
parameters, or a dictionary for ``id > :id``
|
parameters, or a dictionary for ``id > :id``
|
||||||
"""
|
"""
|
||||||
sql = "select count(*) from [{}]".format(self.name)
|
sql = "select count(*) from {}".format(escaped_name(self.name))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
sql += " where " + where
|
sql += " where " + where
|
||||||
return self.db.execute(sql, where_args or []).fetchone()[0]
|
return self.db.execute(sql, where_args or []).fetchone()[0]
|
||||||
|
|
@ -1335,7 +1374,7 @@ class Queryable:
|
||||||
"""
|
"""
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
return
|
return
|
||||||
sql = "select {} from [{}]".format(select, self.name)
|
sql = "select {} from {}".format(select, escaped_name(self.name))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
sql += " where " + where
|
sql += " where " + where
|
||||||
if order_by is not None:
|
if order_by is not None:
|
||||||
|
|
@ -1387,12 +1426,23 @@ class Queryable:
|
||||||
row_pk = row_pk[0]
|
row_pk = row_pk[0]
|
||||||
yield row_pk, row
|
yield row_pk, row
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_attached(self) -> bool:
|
||||||
|
return dbname(self.name) not in {'', 'main'}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _pragma_name(self) -> Tuple[str, str]:
|
||||||
|
if "." in self.name:
|
||||||
|
db, name = self.name.split(".")
|
||||||
|
return db + ".", name
|
||||||
|
return "", self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def columns(self) -> List["Column"]:
|
def columns(self) -> List["Column"]:
|
||||||
"List of :ref:`Columns <reference_db_other_column>` representing the columns in this table or view."
|
"List of :ref:`Columns <reference_db_other_column>` representing the columns in this table or view."
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
return []
|
return []
|
||||||
rows = self.db.execute("PRAGMA table_info([{}])".format(self.name)).fetchall()
|
rows = self.db.execute("PRAGMA {}table_info([{}])".format(*self._pragma_name)).fetchall()
|
||||||
return [Column(*row) for row in rows]
|
return [Column(*row) for row in rows]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -1403,9 +1453,10 @@ class Queryable:
|
||||||
@property
|
@property
|
||||||
def schema(self) -> str:
|
def schema(self) -> str:
|
||||||
"SQL schema for this table or view."
|
"SQL schema for this table or view."
|
||||||
return self.db.execute(
|
db, name = self._pragma_name
|
||||||
"select sql from sqlite_master where name = ?", (self.name,)
|
return self.db.execute(f"select sql from {db}sqlite_master where name = ?", (name,)).fetchone()[
|
||||||
).fetchone()[0]
|
0
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Table(Queryable):
|
class Table(Queryable):
|
||||||
|
|
@ -1544,7 +1595,7 @@ class Table(Queryable):
|
||||||
"List of foreign keys defined on this table."
|
"List of foreign keys defined on this table."
|
||||||
fks = []
|
fks = []
|
||||||
for row in self.db.execute(
|
for row in self.db.execute(
|
||||||
"PRAGMA foreign_key_list([{}])".format(self.name)
|
"PRAGMA {}foreign_key_list([{}])".format(*self._pragma_name)
|
||||||
).fetchall():
|
).fetchall():
|
||||||
if row is not None:
|
if row is not None:
|
||||||
id, seq, table_name, from_, to_, on_update, on_delete, match = row
|
id, seq, table_name, from_, to_, on_update, on_delete, match = row
|
||||||
|
|
@ -1569,7 +1620,8 @@ class Table(Queryable):
|
||||||
@property
|
@property
|
||||||
def indexes(self) -> List[Index]:
|
def indexes(self) -> List[Index]:
|
||||||
"List of indexes defined on this table."
|
"List of indexes defined on this table."
|
||||||
sql = 'PRAGMA index_list("{}")'.format(self.name)
|
db, table_name = self._pragma_name
|
||||||
|
sql = 'PRAGMA {}index_list("{}")'.format(db, table_name)
|
||||||
indexes = []
|
indexes = []
|
||||||
for row in self.db.execute_returning_dicts(sql):
|
for row in self.db.execute_returning_dicts(sql):
|
||||||
index_name = row["name"]
|
index_name = row["name"]
|
||||||
|
|
@ -1578,7 +1630,7 @@ class Table(Queryable):
|
||||||
if not index_name.startswith('"')
|
if not index_name.startswith('"')
|
||||||
else index_name
|
else index_name
|
||||||
)
|
)
|
||||||
column_sql = "PRAGMA index_info({})".format(index_name_quoted)
|
column_sql = "PRAGMA {}index_info({})".format(db, index_name_quoted)
|
||||||
columns = []
|
columns = []
|
||||||
for seqno, cid, name in self.db.execute(column_sql).fetchall():
|
for seqno, cid, name in self.db.execute(column_sql).fetchall():
|
||||||
columns.append(name)
|
columns.append(name)
|
||||||
|
|
@ -1593,7 +1645,8 @@ class Table(Queryable):
|
||||||
@property
|
@property
|
||||||
def xindexes(self) -> List[XIndex]:
|
def xindexes(self) -> List[XIndex]:
|
||||||
"List of indexes defined on this table using the more detailed ``XIndex`` format."
|
"List of indexes defined on this table using the more detailed ``XIndex`` format."
|
||||||
sql = 'PRAGMA index_list("{}")'.format(self.name)
|
db, table_name = self._pragma_name
|
||||||
|
sql = 'PRAGMA {}index_list("{}")'.format(db, table_name)
|
||||||
indexes = []
|
indexes = []
|
||||||
for row in self.db.execute_returning_dicts(sql):
|
for row in self.db.execute_returning_dicts(sql):
|
||||||
index_name = row["name"]
|
index_name = row["name"]
|
||||||
|
|
@ -1602,7 +1655,7 @@ class Table(Queryable):
|
||||||
if not index_name.startswith('"')
|
if not index_name.startswith('"')
|
||||||
else index_name
|
else index_name
|
||||||
)
|
)
|
||||||
column_sql = "PRAGMA index_xinfo({})".format(index_name_quoted)
|
column_sql = "PRAGMA {}index_xinfo({})".format(db, index_name_quoted)
|
||||||
index_columns = []
|
index_columns = []
|
||||||
for info in self.db.execute(column_sql).fetchall():
|
for info in self.db.execute(column_sql).fetchall():
|
||||||
index_columns.append(XIndexColumn(*info))
|
index_columns.append(XIndexColumn(*info))
|
||||||
|
|
@ -1612,12 +1665,13 @@ class Table(Queryable):
|
||||||
@property
|
@property
|
||||||
def triggers(self) -> List[Trigger]:
|
def triggers(self) -> List[Trigger]:
|
||||||
"List of triggers defined on this table."
|
"List of triggers defined on this table."
|
||||||
|
db, table_name = self._pragma_name
|
||||||
return [
|
return [
|
||||||
Trigger(*r)
|
Trigger(*r)
|
||||||
for r in self.db.execute(
|
for r in self.db.execute(
|
||||||
"select name, tbl_name, sql from sqlite_master where type = 'trigger'"
|
f"select name, tbl_name, sql from {db}sqlite_master where type = 'trigger'"
|
||||||
" and tbl_name = ?",
|
" and tbl_name = ?",
|
||||||
(self.name,),
|
(table_name,),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1709,9 +1763,9 @@ class Table(Queryable):
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
raise NoTable(f"Table {self.name} does not exist")
|
raise NoTable(f"Table {self.name} does not exist")
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
sql = "CREATE TABLE [{new_table}] AS SELECT * FROM [{table}];".format(
|
sql = "CREATE TABLE {new_table} AS SELECT * FROM {table};".format(
|
||||||
new_table=new_name,
|
new_table=escaped_name(new_name),
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
)
|
)
|
||||||
self.db.execute(sql)
|
self.db.execute(sql)
|
||||||
return self.db[new_name]
|
return self.db[new_name]
|
||||||
|
|
@ -1765,21 +1819,22 @@ class Table(Queryable):
|
||||||
column_order=column_order,
|
column_order=column_order,
|
||||||
keep_table=keep_table,
|
keep_table=keep_table,
|
||||||
)
|
)
|
||||||
pragma_foreign_keys_was_on = self.db.execute("PRAGMA foreign_keys").fetchone()[
|
db, _ = self._pragma_name
|
||||||
|
pragma_foreign_keys_was_on = self.db.execute(f"PRAGMA {db}foreign_keys").fetchone()[
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
if pragma_foreign_keys_was_on:
|
if pragma_foreign_keys_was_on:
|
||||||
self.db.execute("PRAGMA foreign_keys=0;")
|
self.db.execute(f"PRAGMA {db}foreign_keys=0;")
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
for sql in sqls:
|
for sql in sqls:
|
||||||
self.db.execute(sql)
|
self.db.execute(sql)
|
||||||
# Run the foreign_key_check before we commit
|
# Run the foreign_key_check before we commit
|
||||||
if pragma_foreign_keys_was_on:
|
if pragma_foreign_keys_was_on:
|
||||||
self.db.execute("PRAGMA foreign_key_check;")
|
self.db.execute(f"PRAGMA {db}foreign_key_check;")
|
||||||
finally:
|
finally:
|
||||||
if pragma_foreign_keys_was_on:
|
if pragma_foreign_keys_was_on:
|
||||||
self.db.execute("PRAGMA foreign_keys=1;")
|
self.db.execute(f"PRAGMA {db}foreign_keys=1;")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def transform_sql(
|
def transform_sql(
|
||||||
|
|
@ -1944,9 +1999,9 @@ class Table(Queryable):
|
||||||
if "rowid" not in new_cols:
|
if "rowid" not in new_cols:
|
||||||
new_cols.insert(0, "rowid")
|
new_cols.insert(0, "rowid")
|
||||||
old_cols.insert(0, "rowid")
|
old_cols.insert(0, "rowid")
|
||||||
copy_sql = "INSERT INTO [{new_table}] ({new_cols})\n SELECT {old_cols} FROM [{old_table}];".format(
|
copy_sql = "INSERT INTO {new_table} ({new_cols})\n SELECT {old_cols} FROM {old_table};".format(
|
||||||
new_table=new_table_name,
|
new_table=escaped_name(new_table_name),
|
||||||
old_table=self.name,
|
old_table=escaped_name(self.name),
|
||||||
old_cols=", ".join("[{}]".format(col) for col in old_cols),
|
old_cols=", ".join("[{}]".format(col) for col in old_cols),
|
||||||
new_cols=", ".join("[{}]".format(col) for col in new_cols),
|
new_cols=", ".join("[{}]".format(col) for col in new_cols),
|
||||||
)
|
)
|
||||||
|
|
@ -1954,13 +2009,13 @@ class Table(Queryable):
|
||||||
# Drop (or keep) the old table
|
# Drop (or keep) the old table
|
||||||
if keep_table:
|
if keep_table:
|
||||||
sqls.append(
|
sqls.append(
|
||||||
"ALTER TABLE [{}] RENAME TO [{}];".format(self.name, keep_table)
|
"ALTER TABLE {} RENAME TO {};".format(escaped_name(self.name), escaped_name(keep_table))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sqls.append("DROP TABLE [{}];".format(self.name))
|
sqls.append("DROP TABLE {};".format(escaped_name(self.name)))
|
||||||
# Rename the new one
|
# Rename the new one
|
||||||
sqls.append(
|
sqls.append(
|
||||||
"ALTER TABLE [{}] RENAME TO [{}];".format(new_table_name, self.name)
|
"ALTER TABLE {} RENAME TO {};".format(escaped_name(new_table_name), escaped_name(self.name))
|
||||||
)
|
)
|
||||||
return sqls
|
return sqls
|
||||||
|
|
||||||
|
|
@ -2023,11 +2078,11 @@ class Table(Queryable):
|
||||||
lookup_columns = [(rename.get(col) or col) for col in columns]
|
lookup_columns = [(rename.get(col) or col) for col in columns]
|
||||||
lookup_table.create_index(lookup_columns, unique=True, if_not_exists=True)
|
lookup_table.create_index(lookup_columns, unique=True, if_not_exists=True)
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"INSERT OR IGNORE INTO [{lookup_table}] ({lookup_columns}) SELECT DISTINCT {table_cols} FROM [{table}]".format(
|
"INSERT OR IGNORE INTO {lookup_table} ({lookup_columns}) SELECT DISTINCT {table_cols} FROM {table}".format(
|
||||||
lookup_table=table,
|
lookup_table=escaped_name(table),
|
||||||
lookup_columns=", ".join("[{}]".format(c) for c in lookup_columns),
|
lookup_columns=", ".join("[{}]".format(c) for c in lookup_columns),
|
||||||
table_cols=", ".join("[{}]".format(c) for c in columns),
|
table_cols=", ".join("[{}]".format(c) for c in columns),
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2036,14 +2091,14 @@ class Table(Queryable):
|
||||||
|
|
||||||
# And populate it
|
# And populate it
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"UPDATE [{table}] SET [{magic_lookup_column}] = (SELECT id FROM [{lookup_table}] WHERE {where})".format(
|
"UPDATE {table} SET [{magic_lookup_column}] = (SELECT id FROM {lookup_table} WHERE {where})".format(
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
magic_lookup_column=magic_lookup_column,
|
magic_lookup_column=magic_lookup_column,
|
||||||
lookup_table=table,
|
lookup_table=escaped_name(table),
|
||||||
where=" AND ".join(
|
where=" AND ".join(
|
||||||
"[{table}].[{column}] IS [{lookup_table}].[{lookup_column}]".format(
|
"{table}.[{column}] IS {lookup_table}.[{lookup_column}]".format(
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
lookup_table=table,
|
lookup_table=escaped_name(table),
|
||||||
column=column,
|
column=column,
|
||||||
lookup_column=rename.get(column) or column,
|
lookup_column=rename.get(column) or column,
|
||||||
)
|
)
|
||||||
|
|
@ -2117,13 +2172,13 @@ class Table(Queryable):
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
CREATE {unique}INDEX {if_not_exists}[{index_name}]
|
CREATE {unique}INDEX {if_not_exists}[{index_name}]
|
||||||
ON [{table_name}] ({columns});
|
ON {table_name} ({columns});
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.format(
|
||||||
index_name=created_index_name,
|
index_name=created_index_name,
|
||||||
table_name=self.name,
|
table_name=escaped_name(self.name),
|
||||||
columns=", ".join(columns_sql),
|
columns=", ".join(columns_sql),
|
||||||
unique="UNIQUE " if unique else "",
|
unique="UNIQUE " if unique else "",
|
||||||
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
|
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
|
||||||
|
|
@ -2193,8 +2248,8 @@ class Table(Queryable):
|
||||||
not_null_sql = "NOT NULL DEFAULT {}".format(
|
not_null_sql = "NOT NULL DEFAULT {}".format(
|
||||||
self.db.quote_default_value(not_null_default)
|
self.db.quote_default_value(not_null_default)
|
||||||
)
|
)
|
||||||
sql = "ALTER TABLE [{table}] ADD COLUMN [{col_name}] {col_type}{not_null_default};".format(
|
sql = "ALTER TABLE {table} ADD COLUMN [{col_name}] {col_type}{not_null_default};".format(
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
col_name=col_name,
|
col_name=col_name,
|
||||||
col_type=fk_col_type or COLUMN_TYPE_MAPPING[col_type],
|
col_type=fk_col_type or COLUMN_TYPE_MAPPING[col_type],
|
||||||
not_null_default=(" " + not_null_sql) if not_null_sql else "",
|
not_null_default=(" " + not_null_sql) if not_null_sql else "",
|
||||||
|
|
@ -2211,7 +2266,7 @@ class Table(Queryable):
|
||||||
:param ignore: Set to ``True`` to ignore the error if the table does not exist
|
:param ignore: Set to ``True`` to ignore the error if the table does not exist
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.db.execute("DROP TABLE [{}]".format(self.name))
|
self.db.execute("DROP TABLE {}".format(escaped_name(self.name)))
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
if not ignore:
|
if not ignore:
|
||||||
raise
|
raise
|
||||||
|
|
@ -2378,6 +2433,9 @@ class Table(Queryable):
|
||||||
"""
|
"""
|
||||||
Enable SQLite full-text search against the specified columns.
|
Enable SQLite full-text search against the specified columns.
|
||||||
|
|
||||||
|
Creates the FTS virtual table(s) in the `main` database, even if the
|
||||||
|
source table is in an attached database.
|
||||||
|
|
||||||
See :ref:`python_api_fts` for more details.
|
See :ref:`python_api_fts` for more details.
|
||||||
|
|
||||||
:param columns: List of column names to include in the search index.
|
:param columns: List of column names to include in the search index.
|
||||||
|
|
@ -2386,6 +2444,7 @@ class Table(Queryable):
|
||||||
:param tokenize: Custom SQLite tokenizer to use, for example ``"porter"`` to enable Porter stemming.
|
:param tokenize: Custom SQLite tokenizer to use, for example ``"porter"`` to enable Porter stemming.
|
||||||
:param replace: Should any existing FTS index for this table be replaced by the new one?
|
:param replace: Should any existing FTS index for this table be replaced by the new one?
|
||||||
"""
|
"""
|
||||||
|
table_name = tablename(self.name)
|
||||||
create_fts_sql = (
|
create_fts_sql = (
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
|
|
@ -2397,19 +2456,19 @@ class Table(Queryable):
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.format(
|
||||||
table=self.name,
|
table=table_name,
|
||||||
columns=", ".join("[{}]".format(c) for c in columns),
|
columns=", ".join("[{}]".format(c) for c in columns),
|
||||||
fts_version=fts_version,
|
fts_version=fts_version,
|
||||||
tokenize="\n tokenize='{}',".format(tokenize) if tokenize else "",
|
tokenize="\n tokenize='{}',".format(tokenize) if tokenize else "",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
should_recreate = False
|
should_recreate = False
|
||||||
if replace and self.db["{}_fts".format(self.name)].exists():
|
if replace and self.db["{}_fts".format(table_name)].exists():
|
||||||
# Does the table need to be recreated?
|
# Does the table need to be recreated?
|
||||||
fts_schema = self.db["{}_fts".format(self.name)].schema
|
fts_schema = self.db["{}_fts".format(table_name)].schema
|
||||||
if fts_schema != create_fts_sql:
|
if fts_schema != create_fts_sql:
|
||||||
should_recreate = True
|
should_recreate = True
|
||||||
expected_triggers = {self.name + suffix for suffix in ("_ai", "_ad", "_au")}
|
expected_triggers = {table_name + suffix for suffix in ("_ai", "_ad", "_au")}
|
||||||
existing_triggers = {t.name for t in self.triggers}
|
existing_triggers = {t.name for t in self.triggers}
|
||||||
has_triggers = existing_triggers.issuperset(expected_triggers)
|
has_triggers = existing_triggers.issuperset(expected_triggers)
|
||||||
if has_triggers != create_triggers:
|
if has_triggers != create_triggers:
|
||||||
|
|
@ -2444,7 +2503,7 @@ class Table(Queryable):
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.format(
|
||||||
table=self.name,
|
table=table_name,
|
||||||
columns=", ".join("[{}]".format(c) for c in columns),
|
columns=", ".join("[{}]".format(c) for c in columns),
|
||||||
old_cols=old_cols,
|
old_cols=old_cols,
|
||||||
new_cols=new_cols,
|
new_cols=new_cols,
|
||||||
|
|
@ -2469,7 +2528,7 @@ class Table(Queryable):
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.format(
|
||||||
table=self.name, columns=", ".join("[{}]".format(c) for c in columns)
|
table=tablename(self.name), columns=", ".join("[{}]".format(c) for c in columns)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.db.executescript(sql)
|
self.db.executescript(sql)
|
||||||
|
|
@ -2505,9 +2564,9 @@ class Table(Queryable):
|
||||||
fts_table = self.detect_fts()
|
fts_table = self.detect_fts()
|
||||||
if fts_table is None:
|
if fts_table is None:
|
||||||
# Assume this is itself an FTS table
|
# Assume this is itself an FTS table
|
||||||
fts_table = self.name
|
fts_table = escaped_name(self.name)
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"INSERT INTO [{table}]([{table}]) VALUES('rebuild');".format(
|
"INSERT INTO {table}({table}) VALUES('rebuild');".format(
|
||||||
table=fts_table
|
table=fts_table
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -2529,10 +2588,11 @@ class Table(Queryable):
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
).strip()
|
).strip()
|
||||||
|
table_name = tablename(self.name)
|
||||||
args = {
|
args = {
|
||||||
"like": "%VIRTUAL TABLE%USING FTS%content=[{}]%".format(self.name),
|
"like": "%VIRTUAL TABLE%USING FTS%content=[{}]%".format(table_name),
|
||||||
"like2": '%VIRTUAL TABLE%USING FTS%content="{}"%'.format(self.name),
|
"like2": '%VIRTUAL TABLE%USING FTS%content="{}"%'.format(table_name),
|
||||||
"table": self.name,
|
"table": table_name,
|
||||||
}
|
}
|
||||||
rows = self.db.execute(sql, args).fetchall()
|
rows = self.db.execute(sql, args).fetchall()
|
||||||
if len(rows) == 0:
|
if len(rows) == 0:
|
||||||
|
|
@ -2592,7 +2652,7 @@ class Table(Queryable):
|
||||||
select
|
select
|
||||||
rowid,
|
rowid,
|
||||||
{columns}
|
{columns}
|
||||||
from [{dbtable}]{where_clause}
|
from {dbtable}{where_clause}
|
||||||
)
|
)
|
||||||
select
|
select
|
||||||
{columns_with_prefix}
|
{columns_with_prefix}
|
||||||
|
|
@ -2621,7 +2681,7 @@ class Table(Queryable):
|
||||||
if offset is not None:
|
if offset is not None:
|
||||||
limit_offset += " offset {}".format(offset)
|
limit_offset += " offset {}".format(offset)
|
||||||
return sql.format(
|
return sql.format(
|
||||||
dbtable=self.name,
|
dbtable=escaped_name(self.name),
|
||||||
where_clause="\n where {}".format(where) if where else "",
|
where_clause="\n where {}".format(where) if where else "",
|
||||||
original=original,
|
original=original,
|
||||||
columns=columns_sql,
|
columns=columns_sql,
|
||||||
|
|
@ -2692,8 +2752,8 @@ class Table(Queryable):
|
||||||
pk_values = [pk_values]
|
pk_values = [pk_values]
|
||||||
self.get(pk_values)
|
self.get(pk_values)
|
||||||
wheres = ["[{}] = ?".format(pk_name) for pk_name in self.pks]
|
wheres = ["[{}] = ?".format(pk_name) for pk_name in self.pks]
|
||||||
sql = "delete from [{table}] where {wheres}".format(
|
sql = "delete from {table} where {wheres}".format(
|
||||||
table=self.name, wheres=" and ".join(wheres)
|
table=escaped_name(self.name), wheres=" and ".join(wheres)
|
||||||
)
|
)
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
self.db.execute(sql, pk_values)
|
self.db.execute(sql, pk_values)
|
||||||
|
|
@ -2717,7 +2777,7 @@ class Table(Queryable):
|
||||||
"""
|
"""
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
return self
|
return self
|
||||||
sql = "delete from [{}]".format(self.name)
|
sql = f"delete from {escaped_name(self.name)}"
|
||||||
if where is not None:
|
if where is not None:
|
||||||
sql += " where " + where
|
sql += " where " + where
|
||||||
self.db.execute(sql, where_args or [])
|
self.db.execute(sql, where_args or [])
|
||||||
|
|
@ -2762,8 +2822,8 @@ class Table(Queryable):
|
||||||
args.append(jsonify_if_needed(value))
|
args.append(jsonify_if_needed(value))
|
||||||
wheres = ["[{}] = ?".format(pk_name) for pk_name in pks]
|
wheres = ["[{}] = ?".format(pk_name) for pk_name in pks]
|
||||||
args.extend(pk_values)
|
args.extend(pk_values)
|
||||||
sql = "update [{table}] set {sets} where {wheres}".format(
|
sql = "update {table} set {sets} where {wheres}".format(
|
||||||
table=self.name, sets=", ".join(sets), wheres=" and ".join(wheres)
|
table=escaped_name(self.name), sets=", ".join(sets), wheres=" and ".join(wheres)
|
||||||
)
|
)
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
try:
|
try:
|
||||||
|
|
@ -2843,8 +2903,8 @@ class Table(Queryable):
|
||||||
if fn_name == "<lambda>":
|
if fn_name == "<lambda>":
|
||||||
fn_name = f"lambda_{abs(hash(fn))}"
|
fn_name = f"lambda_{abs(hash(fn))}"
|
||||||
self.db.register_function(convert_value, name=fn_name)
|
self.db.register_function(convert_value, name=fn_name)
|
||||||
sql = "update [{table}] set {sets}{where};".format(
|
sql = "update {table} set {sets}{where};".format(
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
sets=", ".join(
|
sets=", ".join(
|
||||||
[
|
[
|
||||||
"[{output_column}] = {fn_name}([{column}])".format(
|
"[{output_column}] = {fn_name}([{column}])".format(
|
||||||
|
|
@ -2965,8 +3025,8 @@ class Table(Queryable):
|
||||||
# them since it ignores the resulting integrity errors
|
# them since it ignores the resulting integrity errors
|
||||||
if not_null:
|
if not_null:
|
||||||
placeholders.extend(not_null)
|
placeholders.extend(not_null)
|
||||||
sql = "INSERT OR IGNORE INTO [{table}]({cols}) VALUES({placeholders});".format(
|
sql = "INSERT OR IGNORE INTO {table}({cols}) VALUES({placeholders});".format(
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
cols=", ".join(["[{}]".format(p) for p in placeholders]),
|
cols=", ".join(["[{}]".format(p) for p in placeholders]),
|
||||||
placeholders=", ".join(["?" for p in placeholders]),
|
placeholders=", ".join(["?" for p in placeholders]),
|
||||||
)
|
)
|
||||||
|
|
@ -2976,8 +3036,8 @@ class Table(Queryable):
|
||||||
# UPDATE [book] SET [name] = 'Programming' WHERE [id] = 1001;
|
# UPDATE [book] SET [name] = 'Programming' WHERE [id] = 1001;
|
||||||
set_cols = [col for col in all_columns if col not in pks]
|
set_cols = [col for col in all_columns if col not in pks]
|
||||||
if set_cols:
|
if set_cols:
|
||||||
sql2 = "UPDATE [{table}] SET {pairs} WHERE {wheres}".format(
|
sql2 = "UPDATE {table} SET {pairs} WHERE {wheres}".format(
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
pairs=", ".join(
|
pairs=", ".join(
|
||||||
"[{}] = {}".format(col, conversions.get(col, "?"))
|
"[{}] = {}".format(col, conversions.get(col, "?"))
|
||||||
for col in set_cols
|
for col in set_cols
|
||||||
|
|
@ -3004,10 +3064,10 @@ class Table(Queryable):
|
||||||
elif ignore:
|
elif ignore:
|
||||||
or_what = "OR IGNORE "
|
or_what = "OR IGNORE "
|
||||||
sql = """
|
sql = """
|
||||||
INSERT {or_what}INTO [{table}] ({columns}) VALUES {rows};
|
INSERT {or_what}INTO {table} ({columns}) VALUES {rows};
|
||||||
""".strip().format(
|
""".strip().format(
|
||||||
or_what=or_what,
|
or_what=or_what,
|
||||||
table=self.name,
|
table=escaped_name(self.name),
|
||||||
columns=", ".join("[{}]".format(c) for c in all_columns),
|
columns=", ".join("[{}]".format(c) for c in all_columns),
|
||||||
rows=", ".join(
|
rows=", ".join(
|
||||||
"({placeholders})".format(
|
"({placeholders})".format(
|
||||||
|
|
@ -3265,7 +3325,7 @@ class Table(Queryable):
|
||||||
self.last_rowid = None
|
self.last_rowid = None
|
||||||
self.last_pk = None
|
self.last_pk = None
|
||||||
if truncate and self.exists():
|
if truncate and self.exists():
|
||||||
self.db.execute("DELETE FROM [{}];".format(self.name))
|
self.db.execute("DELETE FROM {};".format(escaped_name(self.name)))
|
||||||
for chunk in chunks(itertools.chain([first_record], records), batch_size):
|
for chunk in chunks(itertools.chain([first_record], records), batch_size):
|
||||||
chunk = list(chunk)
|
chunk = list(chunk)
|
||||||
num_records_processed += len(chunk)
|
num_records_processed += len(chunk)
|
||||||
|
|
@ -3776,7 +3836,7 @@ class View(Queryable):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.db.execute("DROP VIEW [{}]".format(self.name))
|
self.db.execute("DROP VIEW {}".format(escaped_name(self.name)))
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
if not ignore:
|
if not ignore:
|
||||||
raise
|
raise
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue