work-in-progress FTS for attached dbs

This commit is contained in:
Peter Gaultney 2024-01-02 14:49:37 -06:00
commit c2e8270b9f
No known key found for this signature in database

View file

@ -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