diff --git a/docs/cli.rst b/docs/cli.rst index 3fe2d8e5..88756ba9 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -547,13 +547,13 @@ To see the in-memory database schema that would be used for a file or for multip .. code-block:: output - CREATE TABLE [dogs] ( - [id] INTEGER, - [age] INTEGER, - [name] TEXT + CREATE TABLE "dogs" ( + "id" INTEGER, + "age" INTEGER, + "name" TEXT ); - CREATE VIEW t1 AS select * from [dogs]; - CREATE VIEW t AS select * from [dogs]; + CREATE VIEW "t1" AS select * from "dogs"; + CREATE VIEW "t" AS select * from "dogs"; You can run the equivalent of the :ref:`analyze-tables ` command using ``--analyze``: @@ -596,15 +596,15 @@ You can output SQL that will both create the tables and insert the full data use .. code-block:: output BEGIN TRANSACTION; - CREATE TABLE [dogs] ( - [id] INTEGER, - [age] INTEGER, - [name] TEXT + CREATE TABLE "dogs" ( + "id" INTEGER, + "age" INTEGER, + "name" TEXT ); INSERT INTO "dogs" VALUES('1','4','Cleo'); INSERT INTO "dogs" VALUES('2','2','Pancakes'); - CREATE VIEW t1 AS select * from [dogs]; - CREATE VIEW t AS select * from [dogs]; + CREATE VIEW "t1" AS select * from "dogs"; + CREATE VIEW "t" AS select * from "dogs"; COMMIT; Passing ``--save other.db`` will instead use that SQL to populate a new database file: @@ -746,10 +746,10 @@ Use ``--schema`` to include the schema of each table: ------- ----------------------------------------------- Gosh CREATE TABLE Gosh (c1 text, c2 text, c3 text) Gosh2 CREATE TABLE Gosh2 (c1 text, c2 text, c3 text) - dogs CREATE TABLE [dogs] ( - [id] INTEGER, - [age] INTEGER, - [name] TEXT) + dogs CREATE TABLE "dogs" ( + "id" INTEGER, + "age" INTEGER, + "name" TEXT) The ``--nl``, ``--csv``, ``--tsv``, ``--table`` and ``--fmt`` options are also available. @@ -839,13 +839,13 @@ The ``triggers`` command shows any triggers configured for the database: name table sql --------------- --------- ----------------------------------------------------------------- - plants_insert plants CREATE TRIGGER [plants_insert] AFTER INSERT ON [plants] + plants_insert plants CREATE TRIGGER "plants_insert" AFTER INSERT ON "plants" BEGIN - INSERT OR REPLACE INTO [_counts] + INSERT OR REPLACE INTO "_counts" VALUES ( 'plants', COALESCE( - (SELECT count FROM [_counts] WHERE [table] = 'plants'), + (SELECT count FROM "_counts" WHERE "table" = 'plants'), 0 ) + 1 ); @@ -876,8 +876,8 @@ The ``sqlite-utils schema`` command shows the full SQL schema for the database: .. code-block:: output CREATE TABLE "dogs" ( - [id] INTEGER PRIMARY KEY, - [name] TEXT + "id" INTEGER PRIMARY KEY, + "name" TEXT ); This will show the schema for every table and index in the database. To view the schema just for a specified subset of tables pass those as additional arguments: @@ -983,16 +983,16 @@ The ``_analyze_tables_`` table has the following schema: .. code-block:: sql - CREATE TABLE [_analyze_tables_] ( - [table] TEXT, - [column] TEXT, - [total_rows] INTEGER, - [num_null] INTEGER, - [num_blank] INTEGER, - [num_distinct] INTEGER, - [most_common] TEXT, - [least_common] TEXT, - PRIMARY KEY ([table], [column]) + CREATE TABLE "_analyze_tables_" ( + "table" TEXT, + "column" TEXT, + "total_rows" INTEGER, + "num_null" INTEGER, + "num_blank" INTEGER, + "num_distinct" INTEGER, + "most_common" TEXT, + "least_common" TEXT, + PRIMARY KEY ("table", "column") ); The ``most_common`` and ``least_common`` columns will contain nested JSON arrays of the most common and least common values that look like this: @@ -1197,23 +1197,23 @@ Inserting this into a table using ``sqlite-utils insert logs.db logs log.json`` .. code-block:: sql - CREATE TABLE [logs] ( - [httpRequest] TEXT, - [insertId] TEXT, - [labels] TEXT + CREATE TABLE "logs" ( + "httpRequest" TEXT, + "insertId" TEXT, + "labels" TEXT ); With the ``--flatten`` option columns will be created using ``topkey_nextkey`` column names - so running ``sqlite-utils insert logs.db logs log.json --flatten`` will create the following schema instead: .. code-block:: sql - CREATE TABLE [logs] ( - [httpRequest_latency] TEXT, - [httpRequest_requestMethod] TEXT, - [httpRequest_requestSize] TEXT, - [httpRequest_status] INTEGER, - [insertId] TEXT, - [labels_service] TEXT + CREATE TABLE "logs" ( + "httpRequest_latency" TEXT, + "httpRequest_requestMethod" TEXT, + "httpRequest_requestSize" TEXT, + "httpRequest_status" INTEGER, + "insertId" TEXT, + "labels_service" TEXT ); .. _cli_insert_csv_tsv: @@ -1272,9 +1272,9 @@ Will produce this schema: .. code-block:: output CREATE TABLE "creatures" ( - [name] TEXT, - [age] INTEGER, - [weight] FLOAT + "name" TEXT, + "age" INTEGER, + "weight" FLOAT ); You can set the ``SQLITE_UTILS_DETECT_TYPES`` environment variable if you want ``--detect-types`` to be the default behavior: @@ -1354,8 +1354,8 @@ This will produce the following schema: .. code-block:: sql - CREATE TABLE [loglines] ( - [line] TEXT + CREATE TABLE "loglines" ( + "line" TEXT ); You can also insert the entire contents of the file into a single column called ``text`` using ``--text``: @@ -1368,8 +1368,8 @@ The schema here will be: .. code-block:: sql - CREATE TABLE [content] ( - [text] TEXT + CREATE TABLE "content" ( + "text" TEXT ); .. _cli_insert_convert: @@ -1462,8 +1462,8 @@ The result looks like this: .. code-block:: output BEGIN TRANSACTION; - CREATE TABLE [words] ( - [word] TEXT + CREATE TABLE "words" ( + "word" TEXT ); INSERT INTO "words" VALUES('A'); INSERT INTO "words" VALUES('bunch'); @@ -1568,10 +1568,10 @@ By default this command will create a table with the following schema: .. code-block:: sql - CREATE TABLE [images] ( - [path] TEXT PRIMARY KEY, - [content] BLOB, - [size] INTEGER + CREATE TABLE "images" ( + "path" TEXT PRIMARY KEY, + "content" BLOB, + "size" INTEGER ); Content will be treated as binary by default and stored in a ``BLOB`` column. You can use the ``--text`` option to store that content in a ``TEXT`` column instead. @@ -1586,10 +1586,10 @@ This will result in the following schema: .. code-block:: sql - CREATE TABLE [images] ( - [path] TEXT PRIMARY KEY, - [md5] TEXT, - [mtime] FLOAT + CREATE TABLE "images" ( + "path" TEXT PRIMARY KEY, + "md5" TEXT, + "mtime" FLOAT ); Note that there's no ``content`` column here at all - if you specify custom columns using ``-c`` you need to include ``-c content`` to create that column. @@ -1886,10 +1886,10 @@ The type of the returned values will be taken into account when creating the new .. code-block:: sql - CREATE TABLE [places] ( - [location] TEXT, - [latitude] FLOAT, - [longitude] FLOAT + CREATE TABLE "places" ( + "location" TEXT, + "latitude" FLOAT, + "longitude" FLOAT ); The code function can also return ``None``, in which case its output will be ignored. You can drop the original column at the end of the operation by adding ``--drop``. @@ -1933,11 +1933,11 @@ You can specify columns that should be NOT NULL using ``--not-null colname``. Yo table schema ------- -------------------------------- - mytable CREATE TABLE [mytable] ( - [id] INTEGER PRIMARY KEY, - [name] TEXT NOT NULL, - [age] INTEGER NOT NULL, - [is_good] INTEGER DEFAULT '1' + mytable CREATE TABLE "mytable" ( + "id" INTEGER PRIMARY KEY, + "name" TEXT NOT NULL, + "age" INTEGER NOT NULL, + "is_good" INTEGER DEFAULT '1' ) You can specify foreign key relationships between the tables you are creating using ``--fk colname othertable othercolumn``: @@ -1964,14 +1964,14 @@ You can specify foreign key relationships between the tables you are creating us table schema ------- ------------------------------------------------- - authors CREATE TABLE [authors] ( - [id] INTEGER PRIMARY KEY, - [name] TEXT + authors CREATE TABLE "authors" ( + "id" INTEGER PRIMARY KEY, + "name" TEXT ) - books CREATE TABLE [books] ( - [id] INTEGER PRIMARY KEY, - [title] TEXT, - [author_id] INTEGER REFERENCES [authors]([id]) + books CREATE TABLE "books" ( + "id" INTEGER PRIMARY KEY, + "title" TEXT, + "author_id" INTEGER REFERENCES "authors"("id") ) You can create a table in `SQLite STRICT mode `__ using ``--strict``: @@ -1988,9 +1988,9 @@ You can create a table in `SQLite STRICT mode >> db = sqlite_utils.Database("dogs.db") >>> print(db.schema) CREATE TABLE "dogs" ( - [id] INTEGER PRIMARY KEY, - [name] TEXT + "id" INTEGER PRIMARY KEY, + "name" TEXT ); .. _python_api_creating_tables: @@ -544,11 +544,11 @@ This will create a table with the following schema: .. code-block:: sql - CREATE TABLE [dogs] ( - [id] INTEGER PRIMARY KEY, - [name] TEXT, - [age] INTEGER, - [weight] FLOAT + CREATE TABLE "dogs" ( + "id" INTEGER PRIMARY KEY, + "name" TEXT, + "age" INTEGER, + "weight" FLOAT ) .. _python_api_explicit_create: @@ -729,10 +729,10 @@ Here's an example that uses these features: # {'id': 3, 'name': 'Dharma', 'score': 1}] print(db.table("authors").schema) # Outputs: - # CREATE TABLE [authors] ( - # [id] INTEGER PRIMARY KEY, - # [name] TEXT NOT NULL, - # [score] INTEGER NOT NULL DEFAULT 1 + # CREATE TABLE "authors" ( + # "id" INTEGER PRIMARY KEY, + # "name" TEXT NOT NULL, + # "score" INTEGER NOT NULL DEFAULT 1 # ) @@ -1196,10 +1196,10 @@ You can inspect the database to see the results like this:: >>> list(db.table("characteristics_dogs").rows) [{'characteristics_id': 1, 'dogs_id': 1}, {'characteristics_id': 2, 'dogs_id': 1}] >>> print(db.table("characteristics_dogs").schema) - CREATE TABLE [characteristics_dogs] ( - [characteristics_id] INTEGER REFERENCES [characteristics]([id]), - [dogs_id] INTEGER REFERENCES [dogs]([id]), - PRIMARY KEY ([characteristics_id], [dogs_id]) + CREATE TABLE "characteristics_dogs" ( + "characteristics_id" INTEGER REFERENCES "characteristics"("id"), + "dogs_id" INTEGER REFERENCES "dogs"("id"), + PRIMARY KEY ("characteristics_id", "dogs_id") ) .. _python_api_analyze_column: @@ -1648,10 +1648,10 @@ The schema of the above table is: .. code-block:: sql - CREATE TABLE [Trees] ( - [id] INTEGER PRIMARY KEY, - [TreeAddress] TEXT, - [Species] TEXT + CREATE TABLE "Trees" ( + "id" INTEGER PRIMARY KEY, + "TreeAddress" TEXT, + "Species" TEXT ) Here's how to extract the ``Species`` column using ``.extract()``: @@ -1665,9 +1665,9 @@ After running this code the table schema now looks like this: .. code-block:: sql CREATE TABLE "Trees" ( - [id] INTEGER PRIMARY KEY, - [TreeAddress] TEXT, - [Species_id] INTEGER, + "id" INTEGER PRIMARY KEY, + "TreeAddress" TEXT, + "Species_id" INTEGER, FOREIGN KEY(Species_id) REFERENCES Species(id) ) @@ -1675,9 +1675,9 @@ A new ``Species`` table will have been created with the following schema: .. code-block:: sql - CREATE TABLE [Species] ( - [id] INTEGER PRIMARY KEY, - [Species] TEXT + CREATE TABLE "Species" ( + "id" INTEGER PRIMARY KEY, + "Species" TEXT ) The ``.extract()`` method defaults to creating a table with the same name as the column that was extracted, and adding a foreign key column called ``tablename_id``. @@ -1693,15 +1693,15 @@ The resulting schema looks like this: .. code-block:: sql CREATE TABLE "Trees" ( - [id] INTEGER PRIMARY KEY, - [TreeAddress] TEXT, - [tree_species_id] INTEGER, + "id" INTEGER PRIMARY KEY, + "TreeAddress" TEXT, + "tree_species_id" INTEGER, FOREIGN KEY(tree_species_id) REFERENCES tree_species(id) ) - CREATE TABLE [tree_species] ( - [id] INTEGER PRIMARY KEY, - [Species] TEXT + CREATE TABLE "tree_species" ( + "id" INTEGER PRIMARY KEY, + "Species" TEXT ) You can also extract multiple columns into the same external table. Say for example you have a table like this: @@ -1726,15 +1726,15 @@ This produces the following schema: .. code-block:: sql CREATE TABLE "Trees" ( - [id] INTEGER PRIMARY KEY, - [TreeAddress] TEXT, - [CommonName_LatinName_id] INTEGER, + "id" INTEGER PRIMARY KEY, + "TreeAddress" TEXT, + "CommonName_LatinName_id" INTEGER, FOREIGN KEY(CommonName_LatinName_id) REFERENCES CommonName_LatinName(id) ) - CREATE TABLE [CommonName_LatinName] ( - [id] INTEGER PRIMARY KEY, - [CommonName] TEXT, - [LatinName] TEXT + CREATE TABLE "CommonName_LatinName" ( + "id" INTEGER PRIMARY KEY, + "CommonName" TEXT, + "LatinName" TEXT ) The table name ``CommonName_LatinName`` is derived from the extract columns. You can use ``table=`` and ``fk_column=`` to specify custom names like this: @@ -1748,15 +1748,15 @@ This produces the following schema: .. code-block:: sql CREATE TABLE "Trees" ( - [id] INTEGER PRIMARY KEY, - [TreeAddress] TEXT, - [species_id] INTEGER, + "id" INTEGER PRIMARY KEY, + "TreeAddress" TEXT, + "species_id" INTEGER, FOREIGN KEY(species_id) REFERENCES Species(id) ) - CREATE TABLE [Species] ( - [id] INTEGER PRIMARY KEY, - [CommonName] TEXT, - [LatinName] TEXT + CREATE TABLE "Species" ( + "id" INTEGER PRIMARY KEY, + "CommonName" TEXT, + "LatinName" TEXT ) You can use the ``rename=`` argument to rename columns in the lookup table. To create a ``Species`` table with columns called ``name`` and ``latin`` you can do this: @@ -1774,10 +1774,10 @@ This produces a lookup table like so: .. code-block:: sql - CREATE TABLE [Species] ( - [id] INTEGER PRIMARY KEY, - [name] TEXT, - [latin] TEXT + CREATE TABLE "Species" ( + "id" INTEGER PRIMARY KEY, + "name" TEXT, + "latin" TEXT ) .. _python_api_hash: @@ -2100,12 +2100,12 @@ The ``.schema`` property outputs the table's schema as a SQL string:: "Longitude" REAL, "Location" TEXT , - FOREIGN KEY ("PlantType") REFERENCES [PlantType](id), - FOREIGN KEY ("qCaretaker") REFERENCES [qCaretaker](id), - FOREIGN KEY ("qSpecies") REFERENCES [qSpecies](id), - FOREIGN KEY ("qSiteInfo") REFERENCES [qSiteInfo](id), - FOREIGN KEY ("qCareAssistant") REFERENCES [qCareAssistant](id), - FOREIGN KEY ("qLegalStatus") REFERENCES [qLegalStatus](id)) + FOREIGN KEY ("PlantType") REFERENCES "PlantType"(id), + FOREIGN KEY ("qCaretaker") REFERENCES "qCaretaker"(id), + FOREIGN KEY ("qSpecies") REFERENCES "qSpecies"(id), + FOREIGN KEY ("qSiteInfo") REFERENCES "qSiteInfo"(id), + FOREIGN KEY ("qCareAssistant") REFERENCES "qCareAssistant"(id), + FOREIGN KEY ("qLegalStatus") REFERENCES "qLegalStatus"(id)) .. _python_api_introspection_strict: @@ -2507,9 +2507,9 @@ This will create the ``_counts`` table if it does not already exist, with the fo .. code-block:: sql - CREATE TABLE [_counts] ( - [table] TEXT PRIMARY KEY, - [count] INTEGER DEFAULT 0 + CREATE TABLE "_counts" ( + "table" TEXT PRIMARY KEY, + "count" INTEGER DEFAULT 0 ) You can enable cached counts for every table in a database (except for virtual tables and the ``_counts`` table itself) using the database ``enable_counts()`` method: @@ -2719,11 +2719,11 @@ For example: # The table schema looks like this: # print(db.table("cats").schema) - # CREATE TABLE [cats] ( - # [id] INTEGER PRIMARY KEY, - # [name] TEXT, - # [age] INTEGER, - # [thumbnail] BLOB + # CREATE TABLE "cats" ( + # "id" INTEGER PRIMARY KEY, + # "name" TEXT, + # "age" INTEGER, + # "thumbnail" BLOB # ) .. _python_api_register_function: @@ -2887,9 +2887,9 @@ If we insert this data directly into a table we will get a schema that is entire db.table("creatures").insert_all(rows) print(db.schema) # Outputs: - # CREATE TABLE [creatures] ( - # [id] TEXT, - # [name] TEXT + # CREATE TABLE "creatures" ( + # "id" TEXT, + # "name" TEXT # ); We can detect the best column types using a ``TypeTracker`` instance: @@ -2910,9 +2910,9 @@ We can then apply those types to our new table using the :ref:`table.transform() db.table("creatures2").transform(types=tracker.types) print(db.table("creatures2").schema) # Outputs: - # CREATE TABLE [creatures2] ( - # [id] INTEGER, - # [name] TEXT + # CREATE TABLE "creatures2" ( + # "id" INTEGER, + # "name" TEXT # ); .. _python_api_gis: diff --git a/docs/tutorial.ipynb b/docs/tutorial.ipynb index 179f0104..bade6868 100644 --- a/docs/tutorial.ipynb +++ b/docs/tutorial.ipynb @@ -180,10 +180,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "CREATE TABLE [creatures] (\n", - " [name] TEXT,\n", - " [species] TEXT,\n", - " [age] FLOAT\n", + "CREATE TABLE \"creatures\" (\n", + " \"name\" TEXT,\n", + " \"species\" TEXT,\n", + " \"age\" FLOAT\n", ")\n" ] } @@ -534,11 +534,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "CREATE TABLE [creatures] (\n", - " [id] INTEGER PRIMARY KEY,\n", - " [name] TEXT,\n", - " [species] TEXT,\n", - " [age] FLOAT\n", + "CREATE TABLE \"creatures\" (\n", + " \"id\" INTEGER PRIMARY KEY,\n", + " \"name\" TEXT,\n", + " \"species\" TEXT,\n", + " \"age\" FLOAT\n", ")\n" ] } @@ -929,11 +929,11 @@ "output_type": "stream", "text": [ "CREATE TABLE \"creatures\" (\n", - " [id] INTEGER PRIMARY KEY,\n", - " [name] TEXT,\n", - " [species_id] INTEGER,\n", - " [age] FLOAT,\n", - " FOREIGN KEY([species_id]) REFERENCES [species]([id])\n", + " \"id\" INTEGER PRIMARY KEY,\n", + " \"name\" TEXT,\n", + " \"species_id\" INTEGER,\n", + " \"age\" FLOAT,\n", + " FOREIGN KEY(\"species_id\") REFERENCES \"species\"(\"id\")\n", ")\n", "[{'id': 1, 'name': 'Cleo', 'species_id': 1, 'age': 6.0}, {'id': 2, 'name': 'Lila', 'species_id': 2, 'age': 0.8}, {'id': 3, 'name': 'Bants', 'species_id': 2, 'age': 0.8}, {'id': 4, 'name': 'Azi', 'species_id': 2, 'age': 0.8}, {'id': 5, 'name': 'Snowy', 'species_id': 2, 'age': 0.9}, {'id': 6, 'name': 'Blue', 'species_id': 2, 'age': 0.9}]\n" ] @@ -962,9 +962,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "CREATE TABLE [species] (\n", - " [id] INTEGER PRIMARY KEY,\n", - " [species] TEXT\n", + "CREATE TABLE \"species\" (\n", + " \"id\" INTEGER PRIMARY KEY,\n", + " \"species\" TEXT\n", ")\n", "[{'id': 1, 'species': 'dog'}, {'id': 2, 'species': 'chicken'}]\n" ] @@ -1048,4 +1048,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 80251330..81548bcd 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -6,7 +6,13 @@ import pathlib from runpy import run_module import sqlite_utils -from sqlite_utils.db import AlterError, BadMultiValues, DescIndex, NoTable +from sqlite_utils.db import ( + AlterError, + BadMultiValues, + DescIndex, + NoTable, + quote_identifier, +) from sqlite_utils.plugins import pm, get_plugins from sqlite_utils.utils import maximize_csv_field_size_limit from sqlite_utils import recipes @@ -1967,7 +1973,9 @@ def memory( view_names.append("t") for view_name in view_names: if not db[view_name].exists(): - db.create_view(view_name, "select * from [{}]".format(file_table)) + db.create_view( + view_name, "select * from {}".format(quote_identifier(file_table)) + ) if fp: fp.close() @@ -2230,8 +2238,8 @@ def rows( """ columns = "*" if column: - columns = ", ".join("[{}]".format(c) for c in column) - sql = "select {} from [{}]".format(columns, dbtable) + columns = ", ".join(quote_identifier(c) for c in column) + sql = "select {} from {}".format(columns, quote_identifier(dbtable)) if where: sql += " where " + where if order: @@ -2288,10 +2296,10 @@ def triggers( \b sqlite-utils triggers trees.db """ - sql = "select name, tbl_name as [table], sql from sqlite_master where type = 'trigger'" + sql = "select name, tbl_name as \"table\", sql from sqlite_master where type = 'trigger'" if tables: quote = sqlite_utils.Database(memory=True).quote - sql += " and [table] in ({})".format( + sql += ' and "table" in ({})'.format( ", ".join(quote(table) for table in tables) ) ctx.invoke( diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 2c3176d9..b1b6088e 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -70,6 +70,16 @@ re.VERBOSE | re.IGNORECASE, ) + +def quote_identifier(identifier: str) -> str: + """ + Quote an identifier (table name, column name, etc.) using double quotes. + + Double quotes inside the identifier are escaped by doubling them. + """ + return '"{}"'.format(identifier.replace('"', '""')) + + try: import pandas as pd # type: ignore except ImportError: @@ -280,8 +290,8 @@ def __init__(self, values): _COUNTS_TABLE_CREATE_SQL = """ -CREATE TABLE IF NOT EXISTS [{}]( - [table] TEXT PRIMARY KEY, +CREATE TABLE IF NOT EXISTS "{}"( + "table" TEXT PRIMARY KEY, count INTEGER DEFAULT 0 ); """.strip() @@ -500,9 +510,9 @@ def attach(self, alias: str, filepath: Union[str, pathlib.Path]): :param filepath: Path to SQLite database file on disk """ attach_sql = """ - ATTACH DATABASE '{}' AS [{}]; + ATTACH DATABASE '{}' AS {}; """.format( - str(pathlib.Path(filepath).resolve()), alias + str(pathlib.Path(filepath).resolve()), quote_identifier(alias) ).strip() self.execute(attach_sql) @@ -786,9 +796,9 @@ def cached_counts(self, tables: Optional[Iterable[str]] = None) -> Dict[str, int :param tables: Subset list of tables to return counts for. """ - sql = "select [table], count from {}".format(self._counts_table_name) + sql = 'select "table", count from {}'.format(self._counts_table_name) if tables: - sql += " where [table] in ({})".format(", ".join("?" for table in tables)) + sql += ' where "table" in ({})'.format(", ".join("?" for table in tables)) try: return {r[0]: r[1] for r in self.execute(sql, tables).fetchall()} except OperationalError: @@ -933,7 +943,6 @@ def create_table_sql( ), "defaults set {} includes items not in columns {}".format( repr(set(defaults)), repr(set(columns.keys())) ) - validate_column_names(columns.keys()) column_items = list(columns.items()) if column_order is not None: @@ -976,9 +985,13 @@ def sort_key(p): ) if column_name in foreign_keys_by_column: column_extras.append( - "REFERENCES [{other_table}]([{other_column}])".format( - other_table=foreign_keys_by_column[column_name].other_table, - other_column=foreign_keys_by_column[column_name].other_column, + "REFERENCES {}({})".format( + quote_identifier( + foreign_keys_by_column[column_name].other_table + ), + quote_identifier( + foreign_keys_by_column[column_name].other_column + ), ) ) column_type_str = COLUMN_TYPE_MAPPING[column_type] @@ -987,8 +1000,8 @@ def sort_key(p): if strict and column_type_str == "FLOAT": column_type_str = "REAL" column_defs.append( - " [{column_name}] {column_type}{column_extras}".format( - column_name=column_name, + " {} {column_type}{column_extras}".format( + quote_identifier(column_name), column_type=column_type_str, column_extras=( (" " + " ".join(column_extras)) if column_extras else "" @@ -998,15 +1011,15 @@ def sort_key(p): extra_pk = "" if single_pk is None and pk and len(pk) > 1: extra_pk = ",\n PRIMARY KEY ({pks})".format( - pks=", ".join(["[{}]".format(p) for p in pk]) + pks=", ".join([quote_identifier(p) for p in pk]) ) columns_sql = ",\n".join(column_defs) - sql = """CREATE TABLE {if_not_exists}[{table}] ( + sql = """CREATE TABLE {if_not_exists}{table} ( {columns_sql}{extra_pk} ){strict}; """.format( if_not_exists="IF NOT EXISTS " if if_not_exists else "", - table=name, + table=quote_identifier(name), columns_sql=columns_sql, extra_pk=extra_pk, strict=" STRICT" if strict and self.supports_strict else "", @@ -1144,8 +1157,8 @@ def rename_table(self, name: str, new_name: str): :param new_name: Name to rename it to """ self.execute( - "ALTER TABLE [{name}] RENAME TO [{new_name}]".format( - name=name, new_name=new_name + "ALTER TABLE {} RENAME TO {}".format( + quote_identifier(name), quote_identifier(new_name) ) ) @@ -1163,7 +1176,9 @@ def create_view( assert not ( ignore and replace ), "Use one or the other of ignore/replace, not both" - create_sql = "CREATE VIEW {name} AS {sql}".format(name=name, sql=sql) + create_sql = "CREATE VIEW {name} AS {sql}".format( + name=quote_identifier(name), sql=sql + ) if ignore or replace: # Does view exist already? if name in self.view_names(): @@ -1270,7 +1285,7 @@ def analyze(self, name=None): """ sql = "ANALYZE" if name is not None: - sql += " [{}]".format(name) + sql += " {}".format(quote_identifier(name)) self.execute(sql) def iterdump(self) -> Generator[str, None, None]: @@ -1348,7 +1363,7 @@ def count_where( :param where_args: Parameters to use with that fragment - an iterable for ``id > ?`` parameters, or a dictionary for ``id > :id`` """ - sql = "select count(*) from [{}]".format(self.name) + sql = "select count(*) from {}".format(quote_identifier(self.name)) if where is not None: sql += " where " + where return self.db.execute(sql, where_args or []).fetchone()[0] @@ -1391,7 +1406,7 @@ def rows_where( """ if not self.exists(): return - sql = "select {} from [{}]".format(select, self.name) + sql = "select {} from {}".format(select, quote_identifier(self.name)) if where is not None: sql += " where " + where if order_by is not None: @@ -1429,7 +1444,7 @@ def pks_and_rows_where( if not pks: column_names.insert(0, "rowid") pks = ["rowid"] - select = ",".join("[{}]".format(column_name) for column_name in column_names) + select = ",".join(quote_identifier(column_name) for column_name in column_names) for row in self.rows_where( select=select, where=where, @@ -1448,7 +1463,9 @@ def columns(self) -> List["Column"]: "List of :ref:`Columns ` representing the columns in this table or view." if not self.exists(): return [] - rows = self.db.execute("PRAGMA table_info([{}])".format(self.name)).fetchall() + rows = self.db.execute( + "PRAGMA table_info({})".format(quote_identifier(self.name)) + ).fetchall() return [Column(*row) for row in rows] @property @@ -1588,7 +1605,7 @@ def get(self, pk_values: Union[list, tuple, str, int]) -> dict: ) ) - wheres = ["[{}] = ?".format(pk_name) for pk_name in pks] + wheres = ["{} = ?".format(quote_identifier(pk_name)) for pk_name in pks] rows = self.rows_where(" and ".join(wheres), pk_values) try: row = list(rows)[0] @@ -1602,7 +1619,7 @@ def foreign_keys(self) -> List["ForeignKey"]: "List of foreign keys defined on this table." fks = [] for row in self.db.execute( - "PRAGMA foreign_key_list([{}])".format(self.name) + "PRAGMA foreign_key_list({})".format(quote_identifier(self.name)) ).fetchall(): if row is not None: id, seq, table_name, from_, to_, on_update, on_delete, match = row @@ -1799,9 +1816,9 @@ def duplicate(self, new_name: str) -> "Table": if not self.exists(): raise NoTable(f"Table {self.name} does not exist") with self.db.conn: - sql = "CREATE TABLE [{new_table}] AS SELECT * FROM [{table}];".format( - new_table=new_name, - table=self.name, + sql = "CREATE TABLE {} AS SELECT * FROM {};".format( + quote_identifier(new_name), + quote_identifier(self.name), ) self.db.execute(sql) return self.db[new_name] @@ -2034,23 +2051,27 @@ def transform_sql( if "rowid" not in new_cols: new_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( - new_table=new_table_name, - old_table=self.name, - old_cols=", ".join("[{}]".format(col) for col in old_cols), - new_cols=", ".join("[{}]".format(col) for col in new_cols), + copy_sql = "INSERT INTO {} ({new_cols})\n SELECT {old_cols} FROM {};".format( + quote_identifier(new_table_name), + quote_identifier(self.name), + old_cols=", ".join(quote_identifier(col) for col in old_cols), + new_cols=", ".join(quote_identifier(col) for col in new_cols), ) sqls.append(copy_sql) # Drop (or keep) the old table if keep_table: sqls.append( - "ALTER TABLE [{}] RENAME TO [{}];".format(self.name, keep_table) + "ALTER TABLE {} RENAME TO {};".format( + quote_identifier(self.name), quote_identifier(keep_table) + ) ) else: - sqls.append("DROP TABLE [{}];".format(self.name)) + sqls.append("DROP TABLE {};".format(quote_identifier(self.name))) # Rename the new one sqls.append( - "ALTER TABLE [{}] RENAME TO [{}];".format(new_table_name, self.name) + "ALTER TABLE {} RENAME TO {};".format( + quote_identifier(new_table_name), quote_identifier(self.name) + ) ) # Re-add existing indexes for index in self.indexes: @@ -2066,7 +2087,7 @@ def transform_sql( "transformation and manually recreate the new index after running this transformation." ) if keep_table: - sqls.append(f"DROP INDEX IF EXISTS [{index.name}];") + sqls.append(f"DROP INDEX IF EXISTS {quote_identifier(index.name)};") for col in index.columns: if col in rename.keys() or col in drop: raise TransformError( @@ -2137,11 +2158,11 @@ def extract( lookup_columns = [(rename.get(col) or col) for col in columns] lookup_table.create_index(lookup_columns, unique=True, if_not_exists=True) self.db.execute( - "INSERT OR IGNORE INTO [{lookup_table}] ({lookup_columns}) SELECT DISTINCT {table_cols} FROM [{table}]".format( - lookup_table=table, - lookup_columns=", ".join("[{}]".format(c) for c in lookup_columns), - table_cols=", ".join("[{}]".format(c) for c in columns), - table=self.name, + "INSERT OR IGNORE INTO {} ({lookup_columns}) SELECT DISTINCT {table_cols} FROM {}".format( + quote_identifier(table), + quote_identifier(self.name), + lookup_columns=", ".join(quote_identifier(c) for c in lookup_columns), + table_cols=", ".join(quote_identifier(c) for c in columns), ) ) @@ -2150,16 +2171,16 @@ def extract( # And populate it self.db.execute( - "UPDATE [{table}] SET [{magic_lookup_column}] = (SELECT id FROM [{lookup_table}] WHERE {where})".format( - table=self.name, - magic_lookup_column=magic_lookup_column, - lookup_table=table, + "UPDATE {} SET {} = (SELECT id FROM {} WHERE {where})".format( + quote_identifier(self.name), + quote_identifier(magic_lookup_column), + quote_identifier(table), where=" AND ".join( - "[{table}].[{column}] IS [{lookup_table}].[{lookup_column}]".format( - table=self.name, - lookup_table=table, - column=column, - lookup_column=rename.get(column) or column, + "{}.{} IS {}.{}".format( + quote_identifier(self.name), + quote_identifier(column), + quote_identifier(table), + quote_identifier(rename.get(column) or column), ) for column in columns ), @@ -2216,10 +2237,9 @@ def create_index( columns_sql = [] for column in columns: if isinstance(column, DescIndex): - fmt = "[{}] desc" + columns_sql.append("{} desc".format(quote_identifier(column))) else: - fmt = "[{}]" - columns_sql.append(fmt.format(column)) + columns_sql.append(quote_identifier(column)) suffix = None created_index_name = None @@ -2230,14 +2250,14 @@ def create_index( sql = ( textwrap.dedent( """ - CREATE {unique}INDEX {if_not_exists}[{index_name}] - ON [{table_name}] ({columns}); + CREATE {unique}INDEX {if_not_exists}{index_name} + ON {table_name} ({columns}); """ ) .strip() .format( - index_name=created_index_name, - table_name=self.name, + index_name=quote_identifier(created_index_name), + table_name=quote_identifier(self.name), columns=", ".join(columns_sql), unique="UNIQUE " if unique else "", if_not_exists="IF NOT EXISTS " if if_not_exists else "", @@ -2307,9 +2327,9 @@ def add_column( not_null_sql = "NOT NULL DEFAULT {}".format( self.db.quote_default_value(not_null_default) ) - sql = "ALTER TABLE [{table}] ADD COLUMN [{col_name}] {col_type}{not_null_default};".format( - table=self.name, - col_name=col_name, + sql = "ALTER TABLE {} ADD COLUMN {} {col_type}{not_null_default};".format( + quote_identifier(self.name), + quote_identifier(col_name), col_type=fk_col_type or COLUMN_TYPE_MAPPING[col_type], not_null_default=(" " + not_null_sql) if not_null_sql else "", ) @@ -2325,7 +2345,7 @@ def drop(self, ignore: bool = False): :param ignore: Set to ``True`` to ignore the error if the table does not exist """ try: - self.db.execute("DROP TABLE [{}]".format(self.name)) + self.db.execute("DROP TABLE {}".format(quote_identifier(self.name))) except sqlite3.OperationalError: if not ignore: raise @@ -2431,29 +2451,29 @@ def enable_counts(self): textwrap.dedent( """ {create_counts_table} - CREATE TRIGGER IF NOT EXISTS [{table}{counts_table}_insert] AFTER INSERT ON [{table}] + CREATE TRIGGER IF NOT EXISTS {trigger_insert} AFTER INSERT ON {table} BEGIN - INSERT OR REPLACE INTO [{counts_table}] + INSERT OR REPLACE INTO {counts_table} VALUES ( {table_quoted}, COALESCE( - (SELECT count FROM [{counts_table}] WHERE [table] = {table_quoted}), + (SELECT count FROM {counts_table} WHERE "table" = {table_quoted}), 0 ) + 1 ); END; - CREATE TRIGGER IF NOT EXISTS [{table}{counts_table}_delete] AFTER DELETE ON [{table}] + CREATE TRIGGER IF NOT EXISTS {trigger_delete} AFTER DELETE ON {table} BEGIN - INSERT OR REPLACE INTO [{counts_table}] + INSERT OR REPLACE INTO {counts_table} VALUES ( {table_quoted}, COALESCE( - (SELECT count FROM [{counts_table}] WHERE [table] = {table_quoted}), + (SELECT count FROM {counts_table} WHERE "table" = {table_quoted}), 0 ) - 1 ); END; - INSERT OR REPLACE INTO _counts VALUES ({table_quoted}, (select count(*) from [{table}])); + INSERT OR REPLACE INTO _counts VALUES ({table_quoted}, (select count(*) from {table})); """ ) .strip() @@ -2461,9 +2481,15 @@ def enable_counts(self): create_counts_table=_COUNTS_TABLE_CREATE_SQL.format( self.db._counts_table_name ), - counts_table=self.db._counts_table_name, - table=self.name, + counts_table=quote_identifier(self.db._counts_table_name), + table=quote_identifier(self.name), table_quoted=self.db.quote(self.name), + trigger_insert=quote_identifier( + self.name + self.db._counts_table_name + "_insert" + ), + trigger_delete=quote_identifier( + self.name + self.db._counts_table_name + "_delete" + ), ) ) with self.db.conn: @@ -2503,16 +2529,17 @@ def enable_fts( create_fts_sql = ( textwrap.dedent( """ - CREATE VIRTUAL TABLE [{table}_fts] USING {fts_version} ( + CREATE VIRTUAL TABLE {table_fts} USING {fts_version} ( {columns},{tokenize} - content=[{table}] + content={table} ) """ ) .strip() .format( - table=self.name, - columns=", ".join("[{}]".format(c) for c in columns), + table=quote_identifier(self.name), + table_fts=quote_identifier(self.name + "_fts"), + columns=", ".join(quote_identifier(c) for c in columns), fts_version=fts_version, tokenize="\n tokenize='{}',".format(tokenize) if tokenize else "", ) @@ -2539,27 +2566,34 @@ def enable_fts( self.populate_fts(columns) if create_triggers: - old_cols = ", ".join("old.[{}]".format(c) for c in columns) - new_cols = ", ".join("new.[{}]".format(c) for c in columns) + old_cols = ", ".join("old.{}".format(quote_identifier(c)) for c in columns) + new_cols = ", ".join("new.{}".format(quote_identifier(c)) for c in columns) + columns_quoted = ", ".join(quote_identifier(c) for c in columns) + table = quote_identifier(self.name) + table_fts = quote_identifier(self.name + "_fts") triggers = ( textwrap.dedent( """ - CREATE TRIGGER [{table}_ai] AFTER INSERT ON [{table}] BEGIN - INSERT INTO [{table}_fts] (rowid, {columns}) VALUES (new.rowid, {new_cols}); + CREATE TRIGGER {table_ai} AFTER INSERT ON {table} BEGIN + INSERT INTO {table_fts} (rowid, {columns}) VALUES (new.rowid, {new_cols}); END; - CREATE TRIGGER [{table}_ad] AFTER DELETE ON [{table}] BEGIN - INSERT INTO [{table}_fts] ([{table}_fts], rowid, {columns}) VALUES('delete', old.rowid, {old_cols}); + CREATE TRIGGER {table_ad} AFTER DELETE ON {table} BEGIN + INSERT INTO {table_fts} ({table_fts}, rowid, {columns}) VALUES('delete', old.rowid, {old_cols}); END; - CREATE TRIGGER [{table}_au] AFTER UPDATE ON [{table}] BEGIN - INSERT INTO [{table}_fts] ([{table}_fts], rowid, {columns}) VALUES('delete', old.rowid, {old_cols}); - INSERT INTO [{table}_fts] (rowid, {columns}) VALUES (new.rowid, {new_cols}); + CREATE TRIGGER {table_au} AFTER UPDATE ON {table} BEGIN + INSERT INTO {table_fts} ({table_fts}, rowid, {columns}) VALUES('delete', old.rowid, {old_cols}); + INSERT INTO {table_fts} (rowid, {columns}) VALUES (new.rowid, {new_cols}); END; """ ) .strip() .format( - table=self.name, - columns=", ".join("[{}]".format(c) for c in columns), + table=table, + table_fts=table_fts, + table_ai=quote_identifier(self.name + "_ai"), + table_ad=quote_identifier(self.name + "_ad"), + table_au=quote_identifier(self.name + "_au"), + columns=columns_quoted, old_cols=old_cols, new_cols=new_cols, ) @@ -2574,16 +2608,19 @@ def populate_fts(self, columns: Iterable[str]) -> "Table": :param columns: Columns to populate the data for """ + columns_quoted = ", ".join(quote_identifier(c) for c in columns) sql = ( textwrap.dedent( """ - INSERT INTO [{table}_fts] (rowid, {columns}) - SELECT rowid, {columns} FROM [{table}]; + INSERT INTO {table_fts} (rowid, {columns}) + SELECT rowid, {columns} FROM {table}; """ ) .strip() .format( - table=self.name, columns=", ".join("[{}]".format(c) for c in columns) + table=quote_identifier(self.name), + table_fts=quote_identifier(self.name + "_fts"), + columns=columns_quoted, ) ) self.db.executescript(sql) @@ -2600,18 +2637,20 @@ def disable_fts(self) -> "Table": """ SELECT name FROM sqlite_master WHERE type = 'trigger' - AND sql LIKE '% INSERT INTO [{}]%' + AND (sql LIKE '% INSERT INTO [{}]%' OR sql LIKE '% INSERT INTO "{}"%') """ ) .strip() - .format(fts_table) + .format(fts_table, fts_table) ) trigger_names = [] for row in self.db.execute(sql).fetchall(): trigger_names.append(row[0]) with self.db.conn: for trigger_name in trigger_names: - self.db.execute("DROP TRIGGER IF EXISTS [{}]".format(trigger_name)) + self.db.execute( + "DROP TRIGGER IF EXISTS {}".format(quote_identifier(trigger_name)) + ) return self def rebuild_fts(self): @@ -2621,8 +2660,8 @@ def rebuild_fts(self): # Assume this is itself an FTS table fts_table = self.name self.db.execute( - "INSERT INTO [{table}]([{table}]) VALUES('rebuild');".format( - table=fts_table + "INSERT INTO {table}({table}) VALUES('rebuild');".format( + table=quote_identifier(fts_table) ) ) return self @@ -2644,7 +2683,7 @@ def detect_fts(self) -> Optional[str]: """ ).strip() args = { - "like": "%VIRTUAL TABLE%USING FTS%content=[{}]%".format(self.name), + "like": '%VIRTUAL TABLE%USING FTS%content="{}"%'.format(self.name), "like2": '%VIRTUAL TABLE%USING FTS%content="{}"%'.format(self.name), "table": self.name, } @@ -2660,9 +2699,9 @@ def optimize(self) -> "Table": if fts_table is not None: self.db.execute( """ - INSERT INTO [{table}] ([{table}]) VALUES ("optimize"); + INSERT INTO {table} ({table}) VALUES ("optimize"); """.strip().format( - table=fts_table + table=quote_identifier(fts_table) ) ) return self @@ -2688,17 +2727,19 @@ def search_sql( """ # Pick names for table and rank column that don't clash original = "original_" if self.name == "original" else "original" + original_quoted = quote_identifier(original) columns_sql = "*" - columns_with_prefix_sql = "[{}].*".format(original) + columns_with_prefix_sql = "{}.*".format(original_quoted) if columns: - columns_sql = ",\n ".join("[{}]".format(c) for c in columns) + columns_sql = ",\n ".join(quote_identifier(c) for c in columns) columns_with_prefix_sql = ",\n ".join( - "[{}].[{}]".format(original, c) for c in columns + "{}.{}".format(original_quoted, quote_identifier(c)) for c in columns ) fts_table = self.detect_fts() assert fts_table, "Full-text search is not configured for table '{}'".format( self.name ) + fts_table_quoted = quote_identifier(fts_table) virtual_table_using = self.db[fts_table].virtual_table_using sql = textwrap.dedent( """ @@ -2706,26 +2747,26 @@ def search_sql( select rowid, {columns} - from [{dbtable}]{where_clause} + from {dbtable}{where_clause} ) select {columns_with_prefix} from - [{original}] - join [{fts_table}] on [{original}].rowid = [{fts_table}].rowid + {original} + join {fts_table} on {original}.rowid = {fts_table}.rowid where - [{fts_table}] match :query + {fts_table} match :query order by {order_by} {limit_offset} """ ).strip() if virtual_table_using == "FTS5": - rank_implementation = "[{}].rank".format(fts_table) + rank_implementation = "{}.rank".format(fts_table_quoted) else: self.db.register_fts4_bm25() - rank_implementation = "rank_bm25(matchinfo([{}], 'pcnalx'))".format( - fts_table + rank_implementation = "rank_bm25(matchinfo({}, 'pcnalx'))".format( + fts_table_quoted ) if include_rank: columns_with_prefix_sql += ",\n " + rank_implementation + " rank" @@ -2735,12 +2776,12 @@ def search_sql( if offset is not None: limit_offset += " offset {}".format(offset) return sql.format( - dbtable=self.name, + dbtable=quote_identifier(self.name), where_clause="\n where {}".format(where) if where else "", - original=original, + original=original_quoted, columns=columns_sql, columns_with_prefix=columns_with_prefix_sql, - fts_table=fts_table, + fts_table=fts_table_quoted, order_by=order_by or rank_implementation, limit_offset=limit_offset.strip(), ).strip() @@ -2808,9 +2849,9 @@ def delete(self, pk_values: Union[list, tuple, str, int, float]) -> "Table": if not isinstance(pk_values, (list, tuple)): pk_values = [pk_values] self.get(pk_values) - wheres = ["[{}] = ?".format(pk_name) for pk_name in self.pks] - sql = "delete from [{table}] where {wheres}".format( - table=self.name, wheres=" and ".join(wheres) + wheres = ["{} = ?".format(quote_identifier(pk_name)) for pk_name in self.pks] + sql = "delete from {} where {wheres}".format( + quote_identifier(self.name), wheres=" and ".join(wheres) ) with self.db.conn: self.db.execute(sql, pk_values) @@ -2834,7 +2875,7 @@ def delete_where( """ if not self.exists(): return self - sql = "delete from [{}]".format(self.name) + sql = "delete from {}".format(quote_identifier(self.name)) if where is not None: sql += " where " + where self.db.execute(sql, where_args or []) @@ -2873,14 +2914,17 @@ def update( sets = [] wheres = [] pks = self.pks - validate_column_names(updates.keys()) for key, value in updates.items(): - sets.append("[{}] = {}".format(key, conversions.get(key, "?"))) + sets.append( + "{} = {}".format(quote_identifier(key), conversions.get(key, "?")) + ) args.append(jsonify_if_needed(value)) - wheres = ["[{}] = ?".format(pk_name) for pk_name in pks] + wheres = ["{} = ?".format(quote_identifier(pk_name)) for pk_name in pks] args.extend(pk_values) - sql = "update [{table}] set {sets} where {wheres}".format( - table=self.name, sets=", ".join(sets), wheres=" and ".join(wheres) + sql = "update {} set {sets} where {wheres}".format( + quote_identifier(self.name), + sets=", ".join(sets), + wheres=" and ".join(wheres), ) with self.db.conn: try: @@ -2957,14 +3001,14 @@ def convert_value(v): if fn_name == "": fn_name = f"lambda_{abs(hash(fn))}" self.db.register_function(convert_value, name=fn_name) - sql = "update [{table}] set {sets}{where};".format( - table=self.name, + sql = "update {} set {sets}{where};".format( + quote_identifier(self.name), sets=", ".join( [ - "[{output_column}] = {fn_name}([{column}])".format( - output_column=output or column, - column=column, - fn_name=fn_name, + "{} = {}({})".format( + quote_identifier(output or column), + fn_name, + quote_identifier(column), ) for column in columns ] @@ -2992,7 +3036,7 @@ def _convert_multi( ) as bar: for row in self.rows_where( select=", ".join( - "[{}]".format(column_name) for column_name in (pks + [column]) + quote_identifier(column_name) for column_name in (pks + [column]) ), where=where, where_args=where_args, @@ -3097,7 +3141,7 @@ def build_insert_queries_and_params( record_values.append(value) values.append(record_values) - columns_sql = ", ".join(f"[{c}]" for c in all_columns) + columns_sql = ", ".join(quote_identifier(c) for c in all_columns) placeholder_expr = ", ".join(conversions.get(c, "?") for c in all_columns) row_placeholders_sql = ", ".join(f"({placeholder_expr})" for _ in values) flat_params = list(itertools.chain.from_iterable(values)) @@ -3105,7 +3149,7 @@ def build_insert_queries_and_params( # replace=True mean INSERT OR REPLACE INTO if replace: sql = ( - f"INSERT OR REPLACE INTO [{self.name}] " + f"INSERT OR REPLACE INTO {quote_identifier(self.name)} " f"({columns_sql}) VALUES {row_placeholders_sql}" ) return [(sql, flat_params)] @@ -3116,7 +3160,7 @@ def build_insert_queries_and_params( if ignore: or_ignore = " OR IGNORE" sql = ( - f"INSERT{or_ignore} INTO [{self.name}] " + f"INSERT{or_ignore} INTO {quote_identifier(self.name)} " f"({columns_sql}) VALUES {row_placeholders_sql}" ) return [(sql, flat_params)] @@ -3124,26 +3168,27 @@ def build_insert_queries_and_params( # Everything from here on is for upsert=True pk_cols = [pk] if isinstance(pk, str) else list(pk) non_pk_cols = [c for c in all_columns if c not in pk_cols] - conflict_sql = ", ".join(f"[{c}]" for c in pk_cols) + conflict_sql = ", ".join(quote_identifier(c) for c in pk_cols) if self.db.supports_on_conflict and not self.db.use_old_upsert: if non_pk_cols: # DO UPDATE assignments = [] for c in non_pk_cols: + c_quoted = quote_identifier(c) if c in conversions: assignments.append( - f"[{c}] = {conversions[c].replace('?', f'excluded.[{c}]')}" + f"{c_quoted} = {conversions[c].replace('?', f'excluded.{c_quoted}')}" ) else: - assignments.append(f"[{c}] = excluded.[{c}]") + assignments.append(f"{c_quoted} = excluded.{c_quoted}") do_clause = "DO UPDATE SET " + ", ".join(assignments) else: # All columns are in the PK – nothing to update. do_clause = "DO NOTHING" sql = ( - f"INSERT INTO [{self.name}] ({columns_sql}) " + f"INSERT INTO {quote_identifier(self.name)} ({columns_sql}) " f"VALUES {row_placeholders_sql} " f"ON CONFLICT({conflict_sql}) {do_clause}" ) @@ -3164,24 +3209,30 @@ def build_insert_queries_and_params( # them since it ignores the resulting integrity errors if not_null: placeholders.extend(not_null) - sql = "INSERT OR IGNORE INTO [{table}]({cols}) VALUES({placeholders});".format( - table=self.name, - cols=", ".join(["[{}]".format(p) for p in placeholders]), - placeholders=", ".join(["?" for p in placeholders]), + sql = ( + "INSERT OR IGNORE INTO {table}({cols}) VALUES({placeholders});".format( + table=quote_identifier(self.name), + cols=", ".join([quote_identifier(p) for p in placeholders]), + placeholders=", ".join(["?" for p in placeholders]), + ) ) queries_and_params.append( (sql, [record[col] for col in pks] + ["" for _ in (not_null or [])]) ) - # 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] if set_cols: - sql2 = "UPDATE [{table}] SET {pairs} WHERE {wheres}".format( - table=self.name, + sql2 = "UPDATE {} SET {pairs} WHERE {wheres}".format( + quote_identifier(self.name), pairs=", ".join( - "[{}] = {}".format(col, conversions.get(col, "?")) + "{} = {}".format( + quote_identifier(col), conversions.get(col, "?") + ) for col in set_cols ), - wheres=" AND ".join("[{}] = ?".format(pk) for pk in pks), + wheres=" AND ".join( + "{} = ?".format(quote_identifier(pk)) for pk in pks + ), ) queries_and_params.append( ( @@ -3452,9 +3503,6 @@ def insert_all( else: # Dict mode: traditional behavior records_iter = itertools.chain([first_record], records_iter) - records_iter = fix_square_braces( - cast(Iterable[Dict[str, Any]], records_iter) - ) try: first_record = next(records_iter) except StopIteration: @@ -3469,7 +3517,7 @@ def insert_all( self.last_rowid = None self.last_pk = None if truncate and self.exists(): - self.db.execute("DELETE FROM [{}];".format(self.name)) + self.db.execute("DELETE FROM {};".format(quote_identifier(self.name))) result = None for chunk in chunks(itertools.chain([first_record], records_iter), batch_size): chunk = list(chunk) @@ -3724,7 +3772,9 @@ def lookup( unique_column_sets = [set(i.columns) for i in self.indexes] if set(lookup_values.keys()) not in unique_column_sets: self.create_index(lookup_values.keys(), unique=True) - wheres = ["[{}] = ?".format(column) for column in lookup_values] + wheres = [ + "{} = ?".format(quote_identifier(column)) for column in lookup_values + ] rows = list( self.rows_where( " and ".join(wheres), [value for _, value in lookup_values.items()] @@ -3887,20 +3937,24 @@ def truncate(value): value = value[:value_truncate] + "..." return value + table_quoted = quote_identifier(table) + column_quoted = quote_identifier(column) num_null = db.execute( - "select count(*) from [{}] where [{}] is null".format(table, column) + "select count(*) from {} where {} is null".format( + table_quoted, column_quoted + ) ).fetchone()[0] num_blank = db.execute( - "select count(*) from [{}] where [{}] = ''".format(table, column) + "select count(*) from {} where {} = ''".format(table_quoted, column_quoted) ).fetchone()[0] num_distinct = db.execute( - "select count(distinct [{}]) from [{}]".format(column, table) + "select count(distinct {}) from {}".format(column_quoted, table_quoted) ).fetchone()[0] most_common_results = None least_common_results = None if num_distinct == 1: value = db.execute( - "select [{}] from [{}] limit 1".format(column, table) + "select {} from {} limit 1".format(column_quoted, table_quoted) ).fetchone()[0] most_common_results = [(truncate(value), total_rows)] elif num_distinct != total_rows: @@ -3912,8 +3966,12 @@ def truncate(value): most_common_results = [ (truncate(r[0]), r[1]) for r in db.execute( - "select [{}], count(*) from [{}] group by [{}] order by count(*) desc, [{}] limit {}".format( - column, table, column, column, common_limit + "select {}, count(*) from {} group by {} order by count(*) desc, {} limit {}".format( + column_quoted, + table_quoted, + column_quoted, + column_quoted, + common_limit, ) ).fetchall() ] @@ -3926,8 +3984,12 @@ def truncate(value): least_common_results = [ (truncate(r[0]), r[1]) for r in db.execute( - "select [{}], count(*) from [{}] group by [{}] order by count(*), [{}] desc limit {}".format( - column, table, column, column, common_limit + "select {}, count(*) from {} group by {} order by count(*), {} desc limit {}".format( + column_quoted, + table_quoted, + column_quoted, + column_quoted, + common_limit, ) ).fetchall() ] @@ -4045,7 +4107,7 @@ def drop(self, ignore=False): """ try: - self.db.execute("DROP VIEW [{}]".format(self.name)) + self.db.execute("DROP VIEW {}".format(quote_identifier(self.name))) except sqlite3.OperationalError: if not ignore: raise @@ -4082,25 +4144,6 @@ def resolve_extracts( return extracts -def validate_column_names(columns): - # Validate no columns contain '[' or ']' - #86 - for column in columns: - assert ( - "[" not in column and "]" not in column - ), "'[' and ']' cannot be used in column names" - - -def fix_square_braces(records: Iterable[Dict[str, Any]]): - for record in records: - if any("[" in key or "]" in key for key in record.keys()): - yield { - key.replace("[", "_").replace("]", "_"): value - for key, value in record.items() - } - else: - yield record - - def _decode_default_value(value): if value.startswith("'") and value.endswith("'"): # It's a string diff --git a/tests/test_cli.py b/tests/test_cli.py index 17e5ed6f..bcf3e983 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -62,8 +62,8 @@ def test_views(db_path): result = CliRunner().invoke(cli.cli, ["views", db_path, "--table", "--schema"]) assert ( "view schema\n" - "------ --------------------------------------------\n" - "hello CREATE VIEW hello AS select sqlite_version()" + "------ ----------------------------------------------\n" + 'hello CREATE VIEW "hello" AS select sqlite_version()' ) == result.output.strip() @@ -132,7 +132,7 @@ def test_tables_schema(db_path): assert ( '[{"table": "Gosh", "schema": "CREATE TABLE Gosh (c1 text, c2 text, c3 text)"},\n' ' {"table": "Gosh2", "schema": "CREATE TABLE Gosh2 (c1 text, c2 text, c3 text)"},\n' - ' {"table": "lots", "schema": "CREATE TABLE [lots] (\\n [id] INTEGER,\\n [age] INTEGER\\n)"}]' + ' {"table": "lots", "schema": "CREATE TABLE \\"lots\\" (\\n \\"id\\" INTEGER,\\n \\"age\\" INTEGER\\n)"}]' ) == result.output.strip() @@ -264,38 +264,38 @@ def test_create_index_desc(db_path): assert result.exit_code == 0 assert ( db.execute("select sql from sqlite_master where type='index'").fetchone()[0] - == "CREATE INDEX [idx_Gosh_c1]\n ON [Gosh] ([c1] desc)" + == 'CREATE INDEX "idx_Gosh_c1"\n ON "Gosh" ("c1" desc)' ) @pytest.mark.parametrize( "col_name,col_type,expected_schema", ( - ("text", "TEXT", "CREATE TABLE [dogs] (\n [name] TEXT\n, [text] TEXT)"), - ("text", "str", "CREATE TABLE [dogs] (\n [name] TEXT\n, [text] TEXT)"), - ("text", "STR", "CREATE TABLE [dogs] (\n [name] TEXT\n, [text] TEXT)"), + ("text", "TEXT", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "text" TEXT)'), + ("text", "str", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "text" TEXT)'), + ("text", "STR", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "text" TEXT)'), ( "integer", "INTEGER", - "CREATE TABLE [dogs] (\n [name] TEXT\n, [integer] INTEGER)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "integer" INTEGER)', ), ( "integer", "int", - "CREATE TABLE [dogs] (\n [name] TEXT\n, [integer] INTEGER)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "integer" INTEGER)', ), - ("float", "FLOAT", "CREATE TABLE [dogs] (\n [name] TEXT\n, [float] FLOAT)"), - ("blob", "blob", "CREATE TABLE [dogs] (\n [name] TEXT\n, [blob] BLOB)"), - ("blob", "BLOB", "CREATE TABLE [dogs] (\n [name] TEXT\n, [blob] BLOB)"), - ("blob", "bytes", "CREATE TABLE [dogs] (\n [name] TEXT\n, [blob] BLOB)"), - ("blob", "BYTES", "CREATE TABLE [dogs] (\n [name] TEXT\n, [blob] BLOB)"), - ("default", None, "CREATE TABLE [dogs] (\n [name] TEXT\n, [default] TEXT)"), + ("float", "FLOAT", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "float" FLOAT)'), + ("blob", "blob", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "blob" BLOB)'), + ("blob", "BLOB", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "blob" BLOB)'), + ("blob", "bytes", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "blob" BLOB)'), + ("blob", "BYTES", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "blob" BLOB)'), + ("default", None, 'CREATE TABLE "dogs" (\n "name" TEXT\n, "default" TEXT)'), ), ) def test_add_column(db_path, col_name, col_type, expected_schema): db = Database(db_path) db.create_table("dogs", {"name": str}) - assert db["dogs"].schema == "CREATE TABLE [dogs] (\n [name] TEXT\n)" + assert db["dogs"].schema == 'CREATE TABLE "dogs" (\n "name" TEXT\n)' args = ["add-column", db_path, "dogs", col_name] if col_type is not None: args.append(col_type) @@ -319,7 +319,7 @@ def test_add_column_ignore(db_path, ignore): def test_add_column_not_null_default(db_path): db = Database(db_path) db.create_table("dogs", {"name": str}) - assert db["dogs"].schema == "CREATE TABLE [dogs] (\n [name] TEXT\n)" + assert db["dogs"].schema == 'CREATE TABLE "dogs" (\n "name" TEXT\n)' args = [ "add-column", db_path, @@ -330,9 +330,9 @@ def test_add_column_not_null_default(db_path): ] assert CliRunner().invoke(cli.cli, args).exit_code == 0 assert db["dogs"].schema == ( - "CREATE TABLE [dogs] (\n" - " [name] TEXT\n" - ", [nickname] TEXT NOT NULL DEFAULT 'dogs''dawg')" + 'CREATE TABLE "dogs" (\n' + ' "name" TEXT\n' + ", \"nickname\" TEXT NOT NULL DEFAULT 'dogs''dawg')" ) @@ -403,8 +403,8 @@ def test_add_column_foreign_key(db_path): assert result.exit_code == 0, result.output assert db["books"].schema == ( 'CREATE TABLE "books" (\n' - " [title] TEXT,\n" - " [author_id] INTEGER REFERENCES [authors]([id])\n" + ' "title" TEXT,\n' + ' "author_id" INTEGER REFERENCES "authors"("id")\n' ")" ) # Try it again with a custom --fk-col @@ -424,9 +424,9 @@ def test_add_column_foreign_key(db_path): assert result.exit_code == 0, result.output assert db["books"].schema == ( 'CREATE TABLE "books" (\n' - " [title] TEXT,\n" - " [author_id] INTEGER REFERENCES [authors]([id]),\n" - " [author_name_ref] TEXT REFERENCES [authors]([name])\n" + ' "title" TEXT,\n' + ' "author_id" INTEGER REFERENCES "authors"("id"),\n' + ' "author_name_ref" TEXT REFERENCES "authors"("name")\n' ")" ) # Throw an error if the --fk table does not exist @@ -492,10 +492,10 @@ def test_enable_fts(db_path): assert "http://example.com_fts" == db["http://example.com"].detect_fts() # Check tokenize was set to porter assert ( - "CREATE VIRTUAL TABLE [http://example.com_fts] USING FTS4 (\n" - " [c1],\n" + 'CREATE VIRTUAL TABLE "http://example.com_fts" USING FTS4 (\n' + ' "c1",\n' " tokenize='porter',\n" - " content=[http://example.com]" + ' content="http://example.com"' "\n)" ) == db["http://example.com_fts"].schema db["http://example.com"].drop() @@ -516,7 +516,7 @@ def test_enable_fts_replace(db_path): cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"] ) assert result2.exit_code == 1 - assert result2.output == "Error: table [Gosh_fts] already exists\n" + assert result2.output == 'Error: table "Gosh_fts" already exists\n' # This should work result3 = CliRunner().invoke( @@ -1139,7 +1139,7 @@ def test_upsert_alter(db_path, tmpdir): "age", "integer", ], - ("CREATE TABLE [t] (\n [name] TEXT,\n [age] INTEGER\n)"), + ('CREATE TABLE "t" (\n "name" TEXT,\n "age" INTEGER\n)'), ), # All types: ( @@ -1158,31 +1158,31 @@ def test_upsert_alter(db_path, tmpdir): "id", ], ( - "CREATE TABLE [t] (\n" - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [age] INTEGER,\n" - " [weight] FLOAT,\n" - " [thumbnail] BLOB\n" + 'CREATE TABLE "t" (\n' + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "age" INTEGER,\n' + ' "weight" FLOAT,\n' + ' "thumbnail" BLOB\n' ")" ), ), # Not null: ( ["name", "text", "--not-null", "name"], - ("CREATE TABLE [t] (\n" " [name] TEXT NOT NULL\n" ")"), + ('CREATE TABLE "t" (\n' ' "name" TEXT NOT NULL\n' ")"), ), # Default: ( ["age", "integer", "--default", "age", "3"], - ("CREATE TABLE [t] (\n" " [age] INTEGER DEFAULT '3'\n" ")"), + ('CREATE TABLE "t" (\n' " \"age\" INTEGER DEFAULT '3'\n" ")"), ), # Compound primary key ( ["category", "text", "name", "text", "--pk", "category", "--pk", "name"], ( - "CREATE TABLE [t] (\n [category] TEXT,\n [name] TEXT,\n" - " PRIMARY KEY ([category], [name])\n)" + 'CREATE TABLE "t" (\n "category" TEXT,\n "name" TEXT,\n' + ' PRIMARY KEY ("category", "name")\n)' ), ), ], @@ -1233,16 +1233,16 @@ def test_create_table_foreign_key(): assert result.exit_code == 0 db = Database("books.db") assert ( - "CREATE TABLE [authors] (\n" - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT\n" + 'CREATE TABLE "authors" (\n' + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT\n' ")" ) == db["authors"].schema assert ( - "CREATE TABLE [books] (\n" - " [id] INTEGER PRIMARY KEY,\n" - " [title] TEXT,\n" - " [author_id] INTEGER REFERENCES [authors]([id])\n" + 'CREATE TABLE "books" (\n' + ' "id" INTEGER PRIMARY KEY,\n' + ' "title" TEXT,\n' + ' "author_id" INTEGER REFERENCES "authors"("id")\n' ")" ) == db["books"].schema @@ -1271,7 +1271,7 @@ def test_create_table_ignore(): cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--ignore"] ) assert result.exit_code == 0 - assert "CREATE TABLE [dogs] (\n [name] TEXT\n)" == db["dogs"].schema + assert 'CREATE TABLE "dogs" (\n "name" TEXT\n)' == db["dogs"].schema def test_create_table_replace(): @@ -1283,7 +1283,7 @@ def test_create_table_replace(): cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--replace"] ) assert result.exit_code == 0 - assert "CREATE TABLE [dogs] (\n [id] INTEGER\n)" == db["dogs"].schema + assert 'CREATE TABLE "dogs" (\n "id" INTEGER\n)' == db["dogs"].schema def test_create_view(): @@ -1294,7 +1294,9 @@ def test_create_view(): cli.cli, ["create-view", "test.db", "version", "select sqlite_version()"] ) assert result.exit_code == 0 - assert "CREATE VIEW version AS select sqlite_version()" == db["version"].schema + assert ( + 'CREATE VIEW "version" AS select sqlite_version()' == db["version"].schema + ) def test_create_view_error_if_view_exists(): @@ -1329,7 +1331,8 @@ def test_create_view_ignore(): ) assert result.exit_code == 0 assert ( - "CREATE VIEW version AS select sqlite_version() + 1" == db["version"].schema + 'CREATE VIEW "version" AS select sqlite_version() + 1' + == db["version"].schema ) @@ -1349,7 +1352,9 @@ def test_create_view_replace(): ], ) assert result.exit_code == 0 - assert "CREATE VIEW version AS select sqlite_version()" == db["version"].schema + assert ( + 'CREATE VIEW "version" AS select sqlite_version()' == db["version"].schema + ) def test_drop_table(): @@ -1537,9 +1542,9 @@ def test_add_foreign_keys(db_path): [], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [age] INTEGER NOT NULL DEFAULT '1',\n" - " [name] TEXT\n" + ' "id" INTEGER PRIMARY KEY,\n' + " \"age\" INTEGER NOT NULL DEFAULT '1',\n" + ' "name" TEXT\n' ")" ), ), @@ -1547,9 +1552,9 @@ def test_add_foreign_keys(db_path): ["--type", "age", "text"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [age] TEXT NOT NULL DEFAULT '1',\n" - " [name] TEXT\n" + ' "id" INTEGER PRIMARY KEY,\n' + " \"age\" TEXT NOT NULL DEFAULT '1',\n" + ' "name" TEXT\n' ")" ), ), @@ -1557,8 +1562,8 @@ def test_add_foreign_keys(db_path): ["--drop", "age"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT\n' ")" ), ), @@ -1566,9 +1571,9 @@ def test_add_foreign_keys(db_path): ["--rename", "age", "age2", "--rename", "id", "pk"], ( 'CREATE TABLE "dogs" (\n' - " [pk] INTEGER PRIMARY KEY,\n" - " [age2] INTEGER NOT NULL DEFAULT '1',\n" - " [name] TEXT\n" + ' "pk" INTEGER PRIMARY KEY,\n' + " \"age2\" INTEGER NOT NULL DEFAULT '1',\n" + ' "name" TEXT\n' ")" ), ), @@ -1576,9 +1581,9 @@ def test_add_foreign_keys(db_path): ["--not-null", "name"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [age] INTEGER NOT NULL DEFAULT '1',\n" - " [name] TEXT NOT NULL\n" + ' "id" INTEGER PRIMARY KEY,\n' + " \"age\" INTEGER NOT NULL DEFAULT '1',\n" + ' "name" TEXT NOT NULL\n' ")" ), ), @@ -1586,9 +1591,9 @@ def test_add_foreign_keys(db_path): ["--not-null-false", "age"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [age] INTEGER DEFAULT '1',\n" - " [name] TEXT\n" + ' "id" INTEGER PRIMARY KEY,\n' + " \"age\" INTEGER DEFAULT '1',\n" + ' "name" TEXT\n' ")" ), ), @@ -1596,9 +1601,9 @@ def test_add_foreign_keys(db_path): ["--pk", "name"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER,\n" - " [age] INTEGER NOT NULL DEFAULT '1',\n" - " [name] TEXT PRIMARY KEY\n" + ' "id" INTEGER,\n' + " \"age\" INTEGER NOT NULL DEFAULT '1',\n" + ' "name" TEXT PRIMARY KEY\n' ")" ), ), @@ -1606,9 +1611,9 @@ def test_add_foreign_keys(db_path): ["--pk-none"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER,\n" - " [age] INTEGER NOT NULL DEFAULT '1',\n" - " [name] TEXT\n" + ' "id" INTEGER,\n' + " \"age\" INTEGER NOT NULL DEFAULT '1',\n" + ' "name" TEXT\n' ")" ), ), @@ -1616,9 +1621,9 @@ def test_add_foreign_keys(db_path): ["--default", "name", "Turnip"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [age] INTEGER NOT NULL DEFAULT '1',\n" - " [name] TEXT DEFAULT 'Turnip'\n" + ' "id" INTEGER PRIMARY KEY,\n' + " \"age\" INTEGER NOT NULL DEFAULT '1',\n" + " \"name\" TEXT DEFAULT 'Turnip'\n" ")" ), ), @@ -1626,9 +1631,9 @@ def test_add_foreign_keys(db_path): ["--default-none", "age"], ( 'CREATE TABLE "dogs" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [age] INTEGER NOT NULL,\n" - " [name] TEXT\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "age" INTEGER NOT NULL,\n' + ' "name" TEXT\n' ")" ), ), @@ -1636,9 +1641,9 @@ def test_add_foreign_keys(db_path): ["-o", "name", "--column-order", "age", "-o", "id"], ( 'CREATE TABLE "dogs" (\n' - " [name] TEXT,\n" - " [age] INTEGER NOT NULL DEFAULT '1',\n" - " [id] INTEGER PRIMARY KEY\n" + ' "name" TEXT,\n' + " \"age\" INTEGER NOT NULL DEFAULT '1',\n" + ' "id" INTEGER PRIMARY KEY\n' ")" ), ), @@ -1667,11 +1672,11 @@ def test_transform(db_path, args, expected_schema): ["--drop-foreign-key", "country"], ( 'CREATE TABLE "places" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [country] INTEGER,\n" - " [city] INTEGER REFERENCES [city]([id]),\n" - " [continent] INTEGER\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "country" INTEGER,\n' + ' "city" INTEGER REFERENCES "city"("id"),\n' + ' "continent" INTEGER\n' ")" ), ), @@ -1679,11 +1684,11 @@ def test_transform(db_path, args, expected_schema): ["--drop-foreign-key", "country", "--drop-foreign-key", "city"], ( 'CREATE TABLE "places" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [country] INTEGER,\n" - " [city] INTEGER,\n" - " [continent] INTEGER\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "country" INTEGER,\n' + ' "city" INTEGER,\n' + ' "continent" INTEGER\n' ")" ), ), @@ -1691,11 +1696,11 @@ def test_transform(db_path, args, expected_schema): ["--add-foreign-key", "continent", "continent", "id"], ( 'CREATE TABLE "places" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [country] INTEGER REFERENCES [country]([id]),\n" - " [city] INTEGER REFERENCES [city]([id]),\n" - " [continent] INTEGER REFERENCES [continent]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "country" INTEGER REFERENCES "country"("id"),\n' + ' "city" INTEGER REFERENCES "city"("id"),\n' + ' "continent" INTEGER REFERENCES "continent"("id")\n' ")" ), ), @@ -1734,7 +1739,7 @@ def test_transform_add_or_drop_foreign_key(db_path, extra_args, expected_schema) _common_other_schema = ( - "CREATE TABLE [species] (\n [id] INTEGER PRIMARY KEY,\n [species] TEXT\n)" + 'CREATE TABLE "species" (\n "id" INTEGER PRIMARY KEY,\n "species" TEXT\n)' ) @@ -1745,9 +1750,9 @@ def test_transform_add_or_drop_foreign_key(db_path, extra_args, expected_schema) [], ( 'CREATE TABLE "trees" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [address] TEXT,\n" - " [species_id] INTEGER REFERENCES [species]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "address" TEXT,\n' + ' "species_id" INTEGER REFERENCES "species"("id")\n' ")" ), _common_other_schema, @@ -1756,20 +1761,20 @@ def test_transform_add_or_drop_foreign_key(db_path, extra_args, expected_schema) ["--table", "custom_table"], ( 'CREATE TABLE "trees" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [address] TEXT,\n" - " [custom_table_id] INTEGER REFERENCES [custom_table]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "address" TEXT,\n' + ' "custom_table_id" INTEGER REFERENCES "custom_table"("id")\n' ")" ), - "CREATE TABLE [custom_table] (\n [id] INTEGER PRIMARY KEY,\n [species] TEXT\n)", + 'CREATE TABLE "custom_table" (\n "id" INTEGER PRIMARY KEY,\n "species" TEXT\n)', ), ( ["--fk-column", "custom_fk"], ( 'CREATE TABLE "trees" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [address] TEXT,\n" - " [custom_fk] INTEGER REFERENCES [species]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "address" TEXT,\n' + ' "custom_fk" INTEGER REFERENCES "species"("id")\n' ")" ), _common_other_schema, @@ -1777,11 +1782,11 @@ def test_transform_add_or_drop_foreign_key(db_path, extra_args, expected_schema) ( ["--rename", "name", "name2"], 'CREATE TABLE "trees" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [address] TEXT,\n" - " [species_id] INTEGER REFERENCES [species]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "address" TEXT,\n' + ' "species_id" INTEGER REFERENCES "species"("id")\n' ")", - "CREATE TABLE [species] (\n [id] INTEGER PRIMARY KEY,\n [species] TEXT\n)", + 'CREATE TABLE "species" (\n "id" INTEGER PRIMARY KEY,\n "species" TEXT\n)', ), ], ) @@ -2036,34 +2041,34 @@ def test_triggers(tmpdir, extra_args, expected): ( [], ( - "CREATE TABLE [dogs] (\n" - " [id] INTEGER,\n" - " [name] TEXT\n" + 'CREATE TABLE "dogs" (\n' + ' "id" INTEGER,\n' + ' "name" TEXT\n' ");\n" - "CREATE TABLE [chickens] (\n" - " [id] INTEGER,\n" - " [name] TEXT,\n" - " [breed] TEXT\n" + 'CREATE TABLE "chickens" (\n' + ' "id" INTEGER,\n' + ' "name" TEXT,\n' + ' "breed" TEXT\n' ");\n" - "CREATE INDEX [idx_chickens_breed]\n" - " ON [chickens] ([breed]);\n" + 'CREATE INDEX "idx_chickens_breed"\n' + ' ON "chickens" ("breed");\n' ), ), ( ["dogs"], - ("CREATE TABLE [dogs] (\n" " [id] INTEGER,\n" " [name] TEXT\n" ")\n"), + ('CREATE TABLE "dogs" (\n' ' "id" INTEGER,\n' ' "name" TEXT\n' ")\n"), ), ( ["chickens", "dogs"], ( - "CREATE TABLE [chickens] (\n" - " [id] INTEGER,\n" - " [name] TEXT,\n" - " [breed] TEXT\n" + 'CREATE TABLE "chickens" (\n' + ' "id" INTEGER,\n' + ' "name" TEXT,\n' + ' "breed" TEXT\n' ")\n" - "CREATE TABLE [dogs] (\n" - " [id] INTEGER,\n" - " [name] TEXT\n" + 'CREATE TABLE "dogs" (\n' + ' "id" INTEGER,\n' + ' "name" TEXT\n' ")\n" ), ), @@ -2127,10 +2132,10 @@ def test_import_no_headers(tmpdir, args, tsv): db = Database(db_path) schema = db["creatures"].schema assert schema == ( - "CREATE TABLE [creatures] (\n" - " [untitled_1] TEXT,\n" - " [untitled_2] TEXT,\n" - " [untitled_3] TEXT\n" + 'CREATE TABLE "creatures" (\n' + ' "untitled_1" TEXT,\n' + ' "untitled_2" TEXT,\n' + ' "untitled_3" TEXT\n' ")" ) rows = list(db["creatures"].rows) @@ -2182,8 +2187,8 @@ def test_csv_insert_bom(tmpdir): db = Database(db_path) tables = db.execute("select name, sql from sqlite_master").fetchall() assert tables == [ - ("broken", "CREATE TABLE [broken] (\n [\ufeffname] TEXT,\n [age] TEXT\n)"), - ("fixed", "CREATE TABLE [fixed] (\n [name] TEXT,\n [age] TEXT\n)"), + ("broken", 'CREATE TABLE "broken" (\n "\ufeffname" TEXT,\n "age" TEXT\n)'), + ("fixed", 'CREATE TABLE "fixed" (\n "name" TEXT,\n "age" TEXT\n)'), ] @@ -2245,7 +2250,7 @@ def test_integer_overflow_error(tmpdir): assert result.exit_code == 1 assert result.output == ( "Error: Python int too large to convert to SQLite INTEGER\n\n" - "sql = INSERT INTO [items] ([bignumber]) VALUES (?)\n" + 'sql = INSERT INTO "items" ("bignumber") VALUES (?)\n' "parameters = [34223049823094832094802398430298048240]\n" ) diff --git a/tests/test_cli_convert.py b/tests/test_cli_convert.py index b2872803..45e6b7bf 100644 --- a/tests/test_cli_convert.py +++ b/tests/test_cli_convert.py @@ -406,9 +406,9 @@ def test_convert_multi_complex_column_types(fresh_db_and_path): {"id": 4, "is_str": None, "is_float": None, "is_int": None, "is_bytes": None}, ] assert db["rows"].schema == ( - "CREATE TABLE [rows] (\n" - " [id] INTEGER PRIMARY KEY\n" - ", [is_str] TEXT, [is_float] FLOAT, [is_int] INTEGER, [is_bytes] BLOB)" + 'CREATE TABLE "rows" (\n' + ' "id" INTEGER PRIMARY KEY\n' + ', "is_str" TEXT, "is_float" FLOAT, "is_int" INTEGER, "is_bytes" BLOB)' ) diff --git a/tests/test_cli_insert.py b/tests/test_cli_insert.py index bf2fa741..a699c944 100644 --- a/tests/test_cli_insert.py +++ b/tests/test_cli_insert.py @@ -127,12 +127,12 @@ def test_insert_multiple_with_compound_primary_key(db_path, tmpdir): assert dogs == list(db.query("select * from dogs order by breed, id")) assert {"breed", "id"} == set(db["dogs"].pks) assert ( - "CREATE TABLE [dogs] (\n" - " [breed] TEXT,\n" - " [id] INTEGER,\n" - " [name] TEXT,\n" - " [age] INTEGER,\n" - " PRIMARY KEY ([id], [breed])\n" + 'CREATE TABLE "dogs" (\n' + ' "breed" TEXT,\n' + ' "id" INTEGER,\n' + ' "name" TEXT,\n' + ' "age" INTEGER,\n' + ' PRIMARY KEY ("id", "breed")\n' ")" ) == db["dogs"].schema @@ -154,11 +154,11 @@ def test_insert_not_null_default(db_path, tmpdir): assert result.exit_code == 0 db = Database(db_path) assert ( - "CREATE TABLE [dogs] (\n" - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT NOT NULL,\n" - " [age] INTEGER NOT NULL DEFAULT '1',\n" - " [score] INTEGER DEFAULT '5'\n)" + 'CREATE TABLE "dogs" (\n' + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT NOT NULL,\n' + " \"age\" INTEGER NOT NULL DEFAULT '1',\n" + " \"score\" INTEGER DEFAULT '5'\n)" ) == db["dogs"].schema @@ -466,7 +466,7 @@ def test_insert_convert_text(db_path): ) assert result.exit_code == 0, result.output db = Database(db_path) - rows = list(db.query("select [text] from [text]")) + rows = list(db.query('select "text" from "text"')) assert rows == [{"text": "THIS IS TEXT\nWILL BE UPPER NOW"}] @@ -486,7 +486,7 @@ def test_insert_convert_text_returning_iterator(db_path): ) assert result.exit_code == 0, result.output db = Database(db_path) - rows = list(db.query("select [word] from [text]")) + rows = list(db.query('select "word" from "text"')) assert rows == [{"word": "A"}, {"word": "bunch"}, {"word": "of"}, {"word": "words"}] @@ -506,7 +506,7 @@ def test_insert_convert_lines(db_path): ) assert result.exit_code == 0, result.output db = Database(db_path) - rows = list(db.query("select [line] from [all]")) + rows = list(db.query('select "line" from "all"')) assert rows == [{"line": "THIS IS TEXT"}, {"line": "WILL BE UPPER NOW"}] diff --git a/tests/test_cli_memory.py b/tests/test_cli_memory.py index ac0a177d..572a13ec 100644 --- a/tests/test_cli_memory.py +++ b/tests/test_cli_memory.py @@ -167,13 +167,13 @@ def test_memory_dump(extra_args): expected = ( "BEGIN TRANSACTION;\n" 'CREATE TABLE IF NOT EXISTS "stdin" (\n' - " [id] INTEGER,\n" - " [name] TEXT\n" + ' "id" INTEGER,\n' + ' "name" TEXT\n' ");\n" "INSERT INTO \"stdin\" VALUES(1,'Cleo');\n" "INSERT INTO \"stdin\" VALUES(2,'Bants');\n" - "CREATE VIEW t1 AS select * from [stdin];\n" - "CREATE VIEW t AS select * from [stdin];\n" + 'CREATE VIEW "t1" AS select * from "stdin";\n' + 'CREATE VIEW "t" AS select * from "stdin";\n' "COMMIT;" ) # Using sqlite-dump it won't have IF NOT EXISTS @@ -191,11 +191,11 @@ def test_memory_schema(extra_args): assert result.exit_code == 0 assert result.output.strip() == ( 'CREATE TABLE "stdin" (\n' - " [id] INTEGER,\n" - " [name] TEXT\n" + ' "id" INTEGER,\n' + ' "name" TEXT\n' ");\n" - "CREATE VIEW t1 AS select * from [stdin];\n" - "CREATE VIEW t AS select * from [stdin];" + 'CREATE VIEW "t1" AS select * from "stdin";\n' + 'CREATE VIEW "t" AS select * from "stdin";' ) @@ -285,16 +285,16 @@ def test_memory_two_files_with_same_stem(tmpdir): assert result.exit_code == 0 assert result.output == ( 'CREATE TABLE "data" (\n' - " [id] INTEGER,\n" - " [name] TEXT\n" + ' "id" INTEGER,\n' + ' "name" TEXT\n' ");\n" - "CREATE VIEW t1 AS select * from [data];\n" - "CREATE VIEW t AS select * from [data];\n" + 'CREATE VIEW "t1" AS select * from "data";\n' + 'CREATE VIEW "t" AS select * from "data";\n' 'CREATE TABLE "data_2" (\n' - " [id] INTEGER,\n" - " [name] TEXT\n" + ' "id" INTEGER,\n' + ' "name" TEXT\n' ");\n" - "CREATE VIEW t2 AS select * from [data_2];\n" + 'CREATE VIEW "t2" AS select * from "data_2";\n' ) diff --git a/tests/test_create.py b/tests/test_create.py index 2ef18ff4..25015e77 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -50,13 +50,13 @@ def test_create_table(fresh_db): {"name": "datetime_col", "type": "TEXT"}, ] == [{"name": col.name, "type": col.type} for col in table.columns] assert ( - "CREATE TABLE [test_table] (\n" - " [text_col] TEXT,\n" - " [float_col] FLOAT,\n" - " [int_col] INTEGER,\n" - " [bool_col] INTEGER,\n" - " [bytes_col] BLOB,\n" - " [datetime_col] TEXT\n" + 'CREATE TABLE "test_table" (\n' + ' "text_col" TEXT,\n' + ' "float_col" FLOAT,\n' + ' "int_col" INTEGER,\n' + ' "bool_col" INTEGER,\n' + ' "bytes_col" BLOB,\n' + ' "datetime_col" TEXT\n' ")" ) == table.schema @@ -66,11 +66,11 @@ def test_create_table_compound_primary_key(fresh_db): "test_table", {"id1": str, "id2": str, "value": int}, pk=("id1", "id2") ) assert ( - "CREATE TABLE [test_table] (\n" - " [id1] TEXT,\n" - " [id2] TEXT,\n" - " [value] INTEGER,\n" - " PRIMARY KEY ([id1], [id2])\n" + 'CREATE TABLE "test_table" (\n' + ' "id1" TEXT,\n' + ' "id2" TEXT,\n' + ' "value" INTEGER,\n' + ' PRIMARY KEY ("id1", "id2")\n' ")" ) == table.schema assert ["id1", "id2"] == table.pks @@ -80,13 +80,17 @@ def test_create_table_compound_primary_key(fresh_db): def test_create_table_with_single_primary_key(fresh_db, pk): fresh_db["foo"].insert({"id": 1}, pk=pk) assert ( - fresh_db["foo"].schema == "CREATE TABLE [foo] (\n [id] INTEGER PRIMARY KEY\n)" + fresh_db["foo"].schema == 'CREATE TABLE "foo" (\n "id" INTEGER PRIMARY KEY\n)' ) -def test_create_table_with_invalid_column_characters(fresh_db): - with pytest.raises(AssertionError): - fresh_db.create_table("players", {"name[foo]": str}) +def test_create_table_with_special_column_characters(fresh_db): + # With double-quote escaping, columns with special characters are now valid + table = fresh_db.create_table("players", {"name[foo]": str}) + assert ["players"] == fresh_db.table_names() + assert [{"name": "name[foo]", "type": "TEXT"}] == [ + {"name": col.name, "type": col.type} for col in table.columns + ] def test_create_table_with_defaults(fresh_db): @@ -100,7 +104,7 @@ def test_create_table_with_defaults(fresh_db): {"name": col.name, "type": col.type} for col in table.columns ] assert ( - "CREATE TABLE [players] (\n [name] TEXT DEFAULT 'bob''''bob',\n [score] INTEGER DEFAULT 1\n)" + "CREATE TABLE \"players\" (\n \"name\" TEXT DEFAULT 'bob''''bob',\n \"score\" INTEGER DEFAULT 1\n)" ) == table.schema @@ -123,7 +127,7 @@ def test_create_table_with_not_null(fresh_db): {"name": col.name, "type": col.type} for col in table.columns ] assert ( - "CREATE TABLE [players] (\n [name] TEXT NOT NULL,\n [score] INTEGER NOT NULL DEFAULT 3\n)" + 'CREATE TABLE "players" (\n "name" TEXT NOT NULL,\n "score" INTEGER NOT NULL DEFAULT 3\n)' ) == table.schema @@ -145,7 +149,7 @@ def test_create_table_with_not_null(fresh_db): [{"name": "memoryview", "type": "BLOB"}], ), ({"uuid": uuid.uuid4()}, [{"name": "uuid", "type": "TEXT"}]), - ({"foo[bar]": 1}, [{"name": "foo_bar_", "type": "INTEGER"}]), + ({"foo[bar]": 1}, [{"name": "foo[bar]", "type": "INTEGER"}]), ( {"timedelta": datetime.timedelta(hours=1)}, [{"name": "timedelta", "type": "TEXT"}], @@ -307,9 +311,9 @@ def test_self_referential_foreign_key(fresh_db): foreign_keys=(("ref", "test_table", "id"),), ) assert ( - "CREATE TABLE [test_table] (\n" - " [id] INTEGER PRIMARY KEY,\n" - " [ref] INTEGER REFERENCES [test_table]([id])\n" + 'CREATE TABLE "test_table" (\n' + ' "id" INTEGER PRIMARY KEY,\n' + ' "ref" INTEGER REFERENCES "test_table"("id")\n' ")" ) == table.schema @@ -340,58 +344,58 @@ def test_create_error_if_invalid_self_referential_foreign_keys(fresh_db): "nickname", str, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [nickname] TEXT)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "nickname" TEXT)', ), ( "dob", datetime.date, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [dob] TEXT)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "dob" TEXT)', ), - ("age", int, None, "CREATE TABLE [dogs] (\n [name] TEXT\n, [age] INTEGER)"), + ("age", int, None, 'CREATE TABLE "dogs" (\n "name" TEXT\n, "age" INTEGER)'), ( "weight", float, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [weight] FLOAT)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "weight" FLOAT)', ), - ("text", "TEXT", None, "CREATE TABLE [dogs] (\n [name] TEXT\n, [text] TEXT)"), + ("text", "TEXT", None, 'CREATE TABLE "dogs" (\n "name" TEXT\n, "text" TEXT)'), ( "integer", "INTEGER", None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [integer] INTEGER)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "integer" INTEGER)', ), ( "float", "FLOAT", None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [float] FLOAT)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "float" FLOAT)', ), - ("blob", "blob", None, "CREATE TABLE [dogs] (\n [name] TEXT\n, [blob] BLOB)"), + ("blob", "blob", None, 'CREATE TABLE "dogs" (\n "name" TEXT\n, "blob" BLOB)'), ( "default_str", None, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [default_str] TEXT)", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "default_str" TEXT)', ), ( "nickname", str, "", - "CREATE TABLE [dogs] (\n [name] TEXT\n, [nickname] TEXT NOT NULL DEFAULT '')", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "nickname" TEXT NOT NULL DEFAULT \'\')', ), ( "nickname", str, "dawg's dawg", - "CREATE TABLE [dogs] (\n [name] TEXT\n, [nickname] TEXT NOT NULL DEFAULT 'dawg''s dawg')", + 'CREATE TABLE "dogs" (\n "name" TEXT\n, "nickname" TEXT NOT NULL DEFAULT \'dawg\'\'s dawg\')', ), ), ) def test_add_column(fresh_db, col_name, col_type, not_null_default, expected_schema): fresh_db.create_table("dogs", {"name": str}) - assert fresh_db["dogs"].schema == "CREATE TABLE [dogs] (\n [name] TEXT\n)" + assert fresh_db["dogs"].schema == 'CREATE TABLE "dogs" (\n "name" TEXT\n)' fresh_db["dogs"].add_column(col_name, col_type, not_null_default=not_null_default) assert fresh_db["dogs"].schema == expected_schema @@ -496,8 +500,8 @@ def test_add_column_foreign_key(fresh_db): fresh_db["dogs"].add_column("breed_id", fk="breeds") assert fresh_db["dogs"].schema == ( 'CREATE TABLE "dogs" (\n' - " [name] TEXT,\n" - " [breed_id] INTEGER REFERENCES [breeds]([rowid])\n" + ' "name" TEXT,\n' + ' "breed_id" INTEGER REFERENCES "breeds"("rowid")\n' ")" ) # And again with an explicit primary key column @@ -505,9 +509,9 @@ def test_add_column_foreign_key(fresh_db): fresh_db["dogs"].add_column("subbreed_id", fk="subbreeds") assert fresh_db["dogs"].schema == ( 'CREATE TABLE "dogs" (\n' - " [name] TEXT,\n" - " [breed_id] INTEGER REFERENCES [breeds]([rowid]),\n" - " [subbreed_id] TEXT REFERENCES [subbreeds]([primkey])\n" + ' "name" TEXT,\n' + ' "breed_id" INTEGER REFERENCES "breeds"("rowid"),\n' + ' "subbreed_id" TEXT REFERENCES "subbreeds"("primkey")\n' ")" ) @@ -519,8 +523,8 @@ def test_add_foreign_key_guess_table(fresh_db): fresh_db["dogs"].add_foreign_key("breed_id") assert fresh_db["dogs"].schema == ( 'CREATE TABLE "dogs" (\n' - " [name] TEXT,\n" - " [breed_id] INTEGER REFERENCES [breeds]([id])\n" + ' "name" TEXT,\n' + ' "breed_id" INTEGER REFERENCES "breeds"("id")\n' ")" ) @@ -591,7 +595,7 @@ def test_add_missing_columns_case_insensitive(fresh_db): table.add_missing_columns([{"Name": ".", "age": 4}]) assert ( table.schema - == "CREATE TABLE [foo] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [age] INTEGER)" + == 'CREATE TABLE "foo" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n, "age" INTEGER)' ) @@ -814,7 +818,7 @@ def test_create_index_desc(fresh_db): "select sql from sqlite_master where name='idx_dogs_age_name'" ).fetchone()[0] assert sql == ( - "CREATE INDEX [idx_dogs_age_name]\n" " ON [dogs] ([age] desc, [name])" + 'CREATE INDEX "idx_dogs_age_name"\n' ' ON "dogs" ("age" desc, "name")' ) @@ -1154,19 +1158,19 @@ def test_quote(fresh_db, input, expected): ( ( {"id": int}, - "[id] INTEGER", + '"id" INTEGER', ), ( {"col": dict}, - "[col] TEXT", + '"col" TEXT', ), ( {"col": tuple}, - "[col] TEXT", + '"col" TEXT', ), ( {"col": list}, - "[col] TEXT", + '"col" TEXT', ), ), ) @@ -1191,12 +1195,12 @@ def test_create(fresh_db): defaults={"integer": 0}, ) assert fresh_db["t"].schema == ( - "CREATE TABLE [t] (\n" - " [id] INTEGER PRIMARY KEY,\n" - " [float] FLOAT NOT NULL,\n" - " [text] TEXT,\n" - " [integer] INTEGER NOT NULL DEFAULT 0,\n" - " [bytes] BLOB\n" + 'CREATE TABLE "t" (\n' + ' "id" INTEGER PRIMARY KEY,\n' + ' "float" FLOAT NOT NULL,\n' + ' "text" TEXT,\n' + ' "integer" INTEGER NOT NULL DEFAULT 0,\n' + ' "bytes" BLOB\n' ")" ) @@ -1232,7 +1236,7 @@ def test_create_replace(fresh_db): fresh_db["t"].create({"id": int}) # This should not fresh_db["t"].create({"name": str}, replace=True) - assert fresh_db["t"].schema == ("CREATE TABLE [t] (\n" " [name] TEXT\n" ")") + assert fresh_db["t"].schema == ('CREATE TABLE "t" (\n' ' "name" TEXT\n' ")") @pytest.mark.parametrize( @@ -1242,58 +1246,58 @@ def test_create_replace(fresh_db): ( {"id": int, "name": str}, {"pk": "id"}, - "CREATE TABLE [demo] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n)", + 'CREATE TABLE "demo" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)', False, ), # Drop name column, remove primary key - ({"id": int}, {}, 'CREATE TABLE "demo" (\n [id] INTEGER\n)', True), + ({"id": int}, {}, 'CREATE TABLE "demo" (\n "id" INTEGER\n)', True), # Add a new column ( {"id": int, "name": str, "age": int}, {"pk": "id"}, - 'CREATE TABLE "demo" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] INTEGER\n)', + 'CREATE TABLE "demo" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" INTEGER\n)', True, ), # Change a column type ( {"id": int, "name": bytes}, {"pk": "id"}, - 'CREATE TABLE "demo" (\n [id] INTEGER PRIMARY KEY,\n [name] BLOB\n)', + 'CREATE TABLE "demo" (\n "id" INTEGER PRIMARY KEY,\n "name" BLOB\n)', True, ), # Change the primary key ( {"id": int, "name": str}, {"pk": "name"}, - 'CREATE TABLE "demo" (\n [id] INTEGER,\n [name] TEXT PRIMARY KEY\n)', + 'CREATE TABLE "demo" (\n "id" INTEGER,\n "name" TEXT PRIMARY KEY\n)', True, ), # Change in column order ( {"id": int, "name": str}, {"pk": "id", "column_order": ["name"]}, - 'CREATE TABLE "demo" (\n [name] TEXT,\n [id] INTEGER PRIMARY KEY\n)', + 'CREATE TABLE "demo" (\n "name" TEXT,\n "id" INTEGER PRIMARY KEY\n)', True, ), # Same column order is ignored ( {"id": int, "name": str}, {"pk": "id", "column_order": ["id", "name"]}, - "CREATE TABLE [demo] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n)", + 'CREATE TABLE "demo" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)', False, ), # Change not null ( {"id": int, "name": str}, {"pk": "id", "not_null": {"name"}}, - 'CREATE TABLE "demo" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT NOT NULL\n)', + 'CREATE TABLE "demo" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT NOT NULL\n)', True, ), # Change default values ( {"id": int, "name": str}, {"pk": "id", "defaults": {"id": 0, "name": "Bob"}}, - "CREATE TABLE \"demo\" (\n [id] INTEGER PRIMARY KEY DEFAULT 0,\n [name] TEXT DEFAULT 'Bob'\n)", + 'CREATE TABLE "demo" (\n "id" INTEGER PRIMARY KEY DEFAULT 0,\n "name" TEXT DEFAULT \'Bob\'\n)', True, ), ), @@ -1356,11 +1360,11 @@ def test_insert_upsert_strict(fresh_db, method_name, strict): def test_create_table_strict(fresh_db, strict): table = fresh_db.create_table("t", {"id": int, "f": float}, strict=strict) assert table.strict == strict or not fresh_db.supports_strict - expected_schema = "CREATE TABLE [t] (\n" " [id] INTEGER,\n" " [f] FLOAT\n" ")" + expected_schema = 'CREATE TABLE "t" (\n' ' "id" INTEGER,\n' ' "f" FLOAT\n' ")" if strict and not fresh_db.supports_strict: return if strict: - expected_schema = "CREATE TABLE [t] (\n [id] INTEGER,\n [f] REAL\n) STRICT" + expected_schema = 'CREATE TABLE "t" (\n "id" INTEGER,\n "f" REAL\n) STRICT' assert table.schema == expected_schema diff --git a/tests/test_duplicate.py b/tests/test_duplicate.py index 87996efd..552f697c 100644 --- a/tests/test_duplicate.py +++ b/tests/test_duplicate.py @@ -6,12 +6,12 @@ def test_duplicate(fresh_db): # Create table using native Sqlite statement: fresh_db.execute( - """CREATE TABLE [table1] ( - [text_col] TEXT, - [real_col] REAL, - [int_col] INTEGER, - [bool_col] INTEGER, - [datetime_col] TEXT)""" + """CREATE TABLE "table1" ( + "text_col" TEXT, + "real_col" REAL, + "int_col" INTEGER, + "bool_col" INTEGER, + "datetime_col" TEXT)""" ) # Insert one row of mock data: dt = datetime.datetime.now() diff --git a/tests/test_enable_counts.py b/tests/test_enable_counts.py index ad0ba3f7..2f6b0db1 100644 --- a/tests/test_enable_counts.py +++ b/tests/test_enable_counts.py @@ -15,25 +15,25 @@ def test_enable_counts_specific_table(fresh_db): foo.enable_counts() assert foo.triggers_dict == { "foo_counts_insert": ( - "CREATE TRIGGER [foo_counts_insert] AFTER INSERT ON [foo]\n" + 'CREATE TRIGGER "foo_counts_insert" AFTER INSERT ON "foo"\n' "BEGIN\n" - " INSERT OR REPLACE INTO [_counts]\n" + ' INSERT OR REPLACE INTO "_counts"\n' " VALUES (\n 'foo',\n" " COALESCE(\n" - " (SELECT count FROM [_counts] WHERE [table] = 'foo'),\n" + ' (SELECT count FROM "_counts" WHERE "table" = \'foo\'),\n' " 0\n" " ) + 1\n" " );\n" "END" ), "foo_counts_delete": ( - "CREATE TRIGGER [foo_counts_delete] AFTER DELETE ON [foo]\n" + 'CREATE TRIGGER "foo_counts_delete" AFTER DELETE ON "foo"\n' "BEGIN\n" - " INSERT OR REPLACE INTO [_counts]\n" + ' INSERT OR REPLACE INTO "_counts"\n' " VALUES (\n" " 'foo',\n" " COALESCE(\n" - " (SELECT count FROM [_counts] WHERE [table] = 'foo'),\n" + ' (SELECT count FROM "_counts" WHERE "table" = \'foo\'),\n' " 0\n" " ) - 1\n" " );\n" @@ -132,7 +132,7 @@ def test_uses_counts_after_enable_counts(counts_db_path): assert db.table("foo").count == 1 assert logged == [ ("select name from sqlite_master where type = 'view'", None), - ("select count(*) from [foo]", []), + ('select count(*) from "foo"', []), ] logged.clear() assert not db.use_counts_table @@ -141,7 +141,7 @@ def test_uses_counts_after_enable_counts(counts_db_path): assert db.table("foo").count == 1 assert logged == [ ( - "CREATE TABLE IF NOT EXISTS [_counts](\n [table] TEXT PRIMARY KEY,\n count INTEGER DEFAULT 0\n);", + 'CREATE TABLE IF NOT EXISTS "_counts"(\n "table" TEXT PRIMARY KEY,\n count INTEGER DEFAULT 0\n);', None, ), ("select name from sqlite_master where type = 'table'", None), @@ -157,7 +157,7 @@ def test_uses_counts_after_enable_counts(counts_db_path): ("SELECT quote(:value)", {"value": "baz"}), ("select sql from sqlite_master where name = ?", ("_counts",)), ("select name from sqlite_master where type = 'view'", None), - ("select [table], count from _counts where [table] in (?)", ["foo"]), + ('select "table", count from _counts where "table" in (?)', ["foo"]), ] diff --git a/tests/test_extract.py b/tests/test_extract.py index 7a663c56..435c1aea 100644 --- a/tests/test_extract.py +++ b/tests/test_extract.py @@ -24,16 +24,16 @@ def test_extract_single_column(fresh_db, table, fk_column): fresh_db["tree"].extract("species", table=table, fk_column=fk_column) assert fresh_db["tree"].schema == ( 'CREATE TABLE "tree" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [{}] INTEGER REFERENCES [{}]([id]),\n".format(expected_fk, expected_table) - + " [end] INTEGER\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "{}" INTEGER REFERENCES "{}"("id"),\n'.format(expected_fk, expected_table) + + ' "end" INTEGER\n' + ")" ) assert fresh_db[expected_table].schema == ( - "CREATE TABLE [{}] (\n".format(expected_table) - + " [id] INTEGER PRIMARY KEY,\n" - " [species] TEXT\n" + 'CREATE TABLE "{}" (\n'.format(expected_table) + + ' "id" INTEGER PRIMARY KEY,\n' + ' "species" TEXT\n' ")" ) assert list(fresh_db[expected_table].rows) == [ @@ -71,16 +71,16 @@ def test_extract_multiple_columns_with_rename(fresh_db): ) assert fresh_db["tree"].schema == ( 'CREATE TABLE "tree" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [common_name_latin_name_id] INTEGER REFERENCES [common_name_latin_name]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n' ")" ) assert fresh_db["common_name_latin_name"].schema == ( - "CREATE TABLE [common_name_latin_name] (\n" - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [latin_name] TEXT\n" + 'CREATE TABLE "common_name_latin_name" (\n' + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "latin_name" TEXT\n' ")" ) assert list(fresh_db["common_name_latin_name"].rows) == [ @@ -122,8 +122,8 @@ def test_extract_rowid_table(fresh_db): fresh_db["tree"].extract(["common_name", "latin_name"]) assert fresh_db["tree"].schema == ( 'CREATE TABLE "tree" (\n' - " [name] TEXT,\n" - " [common_name_latin_name_id] INTEGER REFERENCES [common_name_latin_name]([id])\n" + ' "name" TEXT,\n' + ' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n' ")" ) assert ( @@ -152,15 +152,15 @@ def test_reuse_lookup_table(fresh_db): fresh_db["individuals"].extract("species", rename={"species": "name"}) assert fresh_db["sightings"].schema == ( 'CREATE TABLE "sightings" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [species_id] INTEGER REFERENCES [species]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "species_id" INTEGER REFERENCES "species"("id")\n' ")" ) assert fresh_db["individuals"].schema == ( 'CREATE TABLE "individuals" (\n' - " [id] INTEGER PRIMARY KEY,\n" - " [name] TEXT,\n" - " [species_id] INTEGER REFERENCES [species]([id])\n" + ' "id" INTEGER PRIMARY KEY,\n' + ' "name" TEXT,\n' + ' "species_id" INTEGER REFERENCES "species"("id")\n' ")" ) assert list(fresh_db["species"].rows) == [ diff --git a/tests/test_extracts.py b/tests/test_extracts.py index f24e448c..eb4f37ee 100644 --- a/tests/test_extracts.py +++ b/tests/test_extracts.py @@ -30,13 +30,13 @@ def test_extracts(fresh_db, kwargs, expected_table, use_table_factory): # Should now have two tables: Trees and Species assert {expected_table, "Trees"} == set(fresh_db.table_names()) assert ( - "CREATE TABLE [{}] (\n [id] INTEGER PRIMARY KEY,\n [value] TEXT\n)".format( + 'CREATE TABLE "{}" (\n "id" INTEGER PRIMARY KEY,\n "value" TEXT\n)'.format( expected_table ) == fresh_db[expected_table].schema ) assert ( - "CREATE TABLE [Trees] (\n [id] INTEGER,\n [species_id] INTEGER REFERENCES [{}]([id])\n)".format( + 'CREATE TABLE "Trees" (\n "id" INTEGER,\n "species_id" INTEGER REFERENCES "{}"("id")\n)'.format( expected_table ) == fresh_db["Trees"].schema diff --git a/tests/test_fts.py b/tests/test_fts.py index a35b9eef..f7219bd5 100644 --- a/tests/test_fts.py +++ b/tests/test_fts.py @@ -435,40 +435,40 @@ def test_enable_fts_error_message_on_views(): {}, "FTS5", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [books]\n" + ' from "books"\n' ")\n" "select\n" - " [original].*\n" + ' "original".*\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " [books_fts].rank" + ' "books_fts".rank' ), ), ( {"columns": ["title"], "order_by": "rowid", "limit": 10}, "FTS5", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" - " [title]\n" - " from [books]\n" + ' "title"\n' + ' from "books"\n' ")\n" "select\n" - " [original].[title]\n" + ' "original"."title"\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" " rowid\n" "limit 10" @@ -478,64 +478,64 @@ def test_enable_fts_error_message_on_views(): {"where": "author = :author"}, "FTS5", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [books]\n" + ' from "books"\n' " where author = :author\n" ")\n" "select\n" - " [original].*\n" + ' "original".*\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " [books_fts].rank" + ' "books_fts".rank' ), ), ( {"columns": ["title"]}, "FTS4", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" - " [title]\n" - " from [books]\n" + ' "title"\n' + ' from "books"\n' ")\n" "select\n" - " [original].[title]\n" + ' "original"."title"\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " rank_bm25(matchinfo([books_fts], 'pcnalx'))" + " rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))" ), ), ( {"offset": 1, "limit": 1}, "FTS4", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [books]\n" + ' from "books"\n' ")\n" "select\n" - " [original].*\n" + ' "original".*\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " rank_bm25(matchinfo([books_fts], 'pcnalx'))\n" + " rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))\n" "limit 1 offset 1" ), ), @@ -543,21 +543,21 @@ def test_enable_fts_error_message_on_views(): {"limit": 2}, "FTS4", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [books]\n" + ' from "books"\n' ")\n" "select\n" - " [original].*\n" + ' "original".*\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " rank_bm25(matchinfo([books_fts], 'pcnalx'))\n" + " rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))\n" "limit 2" ), ), @@ -565,66 +565,66 @@ def test_enable_fts_error_message_on_views(): {"where": "author = :author"}, "FTS4", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [books]\n" + ' from "books"\n' " where author = :author\n" ")\n" "select\n" - " [original].*\n" + ' "original".*\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " rank_bm25(matchinfo([books_fts], 'pcnalx'))" + " rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))" ), ), ( {"include_rank": True}, "FTS5", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [books]\n" + ' from "books"\n' ")\n" "select\n" - " [original].*,\n" - " [books_fts].rank rank\n" + ' "original".*,\n' + ' "books_fts".rank rank\n' "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " [books_fts].rank" + ' "books_fts".rank' ), ), ( {"include_rank": True}, "FTS4", ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [books]\n" + ' from "books"\n' ")\n" "select\n" - " [original].*,\n" - " rank_bm25(matchinfo([books_fts], 'pcnalx')) rank\n" + ' "original".*,\n' + " rank_bm25(matchinfo(\"books_fts\", 'pcnalx')) rank\n" "from\n" - " [original]\n" - " join [books_fts] on [original].rowid = [books_fts].rowid\n" + ' "original"\n' + ' join "books_fts" on "original".rowid = "books_fts".rowid\n' "where\n" - " [books_fts] match :query\n" + ' "books_fts" match :query\n' "order by\n" - " rank_bm25(matchinfo([books_fts], 'pcnalx'))" + " rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))" ), ), ], diff --git a/tests/test_introspect.py b/tests/test_introspect.py index f6683444..3df169d8 100644 --- a/tests/test_introspect.py +++ b/tests/test_introspect.py @@ -200,19 +200,19 @@ def test_triggers_and_triggers_dict(fresh_db): } expected_triggers = { "authors_ai": ( - "CREATE TRIGGER [authors_ai] AFTER INSERT ON [authors] BEGIN\n" - " INSERT INTO [authors_fts] (rowid, [name], [famous_works]) VALUES (new.rowid, new.[name], new.[famous_works]);\n" + 'CREATE TRIGGER "authors_ai" AFTER INSERT ON "authors" BEGIN\n' + ' INSERT INTO "authors_fts" (rowid, "name", "famous_works") VALUES (new.rowid, new."name", new."famous_works");\n' "END" ), "authors_ad": ( - "CREATE TRIGGER [authors_ad] AFTER DELETE ON [authors] BEGIN\n" - " INSERT INTO [authors_fts] ([authors_fts], rowid, [name], [famous_works]) VALUES('delete', old.rowid, old.[name], old.[famous_works]);\n" + 'CREATE TRIGGER "authors_ad" AFTER DELETE ON "authors" BEGIN\n' + ' INSERT INTO "authors_fts" ("authors_fts", rowid, "name", "famous_works") VALUES(\'delete\', old.rowid, old."name", old."famous_works");\n' "END" ), "authors_au": ( - "CREATE TRIGGER [authors_au] AFTER UPDATE ON [authors] BEGIN\n" - " INSERT INTO [authors_fts] ([authors_fts], rowid, [name], [famous_works]) VALUES('delete', old.rowid, old.[name], old.[famous_works]);\n" - " INSERT INTO [authors_fts] (rowid, [name], [famous_works]) VALUES (new.rowid, new.[name], new.[famous_works]);\nEND" + 'CREATE TRIGGER "authors_au" AFTER UPDATE ON "authors" BEGIN\n' + ' INSERT INTO "authors_fts" ("authors_fts", rowid, "name", "famous_works") VALUES(\'delete\', old.rowid, old."name", old."famous_works");\n' + ' INSERT INTO "authors_fts" (rowid, "name", "famous_works") VALUES (new.rowid, new."name", new."famous_works");\nEND' ), } assert authors.triggers_dict == expected_triggers diff --git a/tests/test_lookup.py b/tests/test_lookup.py index b5ae61fa..a36b464d 100644 --- a/tests/test_lookup.py +++ b/tests/test_lookup.py @@ -114,18 +114,18 @@ def test_lookup_with_extra_insert_parameters(fresh_db): columns={"make_this_integer": int}, ) assert species.schema == ( - "CREATE TABLE [species] (\n" - " [renamed_id] INTEGER PRIMARY KEY,\n" - " [this_at_front] INTEGER,\n" - " [name] TEXT,\n" - " [type] TEXT,\n" - " [first_seen] TEXT,\n" - " [make_not_null] INTEGER NOT NULL,\n" - " [fk_to_other] INTEGER REFERENCES [other_table]([id]),\n" - " [default_is_dog] TEXT DEFAULT 'dog',\n" - " [extract_this] INTEGER REFERENCES [extract_this]([id]),\n" - " [convert_to_upper] TEXT,\n" - " [make_this_integer] INTEGER\n" + 'CREATE TABLE "species" (\n' + ' "renamed_id" INTEGER PRIMARY KEY,\n' + ' "this_at_front" INTEGER,\n' + ' "name" TEXT,\n' + ' "type" TEXT,\n' + ' "first_seen" TEXT,\n' + ' "make_not_null" INTEGER NOT NULL,\n' + ' "fk_to_other" INTEGER REFERENCES "other_table"("id"),\n' + " \"default_is_dog\" TEXT DEFAULT 'dog',\n" + ' "extract_this" INTEGER REFERENCES "extract_this"("id"),\n' + ' "convert_to_upper" TEXT,\n' + ' "make_this_integer" INTEGER\n' ")" ) assert species.get(id) == { diff --git a/tests/test_tracer.py b/tests/test_tracer.py index 3e60cd1d..9b44fce7 100644 --- a/tests/test_tracer.py +++ b/tests/test_tracer.py @@ -18,15 +18,15 @@ def test_tracer(): ("select name from sqlite_master where type = 'view'", None), ("select name from sqlite_master where type = 'table'", None), ("select name from sqlite_master where type = 'view'", None), - ("CREATE TABLE [dogs] (\n [name] TEXT\n);\n ", None), + ('CREATE TABLE "dogs" (\n "name" TEXT\n);\n ', None), ("select name from sqlite_master where type = 'view'", None), - ("INSERT INTO [dogs] ([name]) VALUES (?)", ["Cleopaws"]), + ('INSERT INTO "dogs" ("name") VALUES (?)', ["Cleopaws"]), ( - "CREATE VIRTUAL TABLE [dogs_fts] USING FTS5 (\n [name],\n content=[dogs]\n)", + 'CREATE VIRTUAL TABLE "dogs_fts" USING FTS5 (\n "name",\n content="dogs"\n)', None, ), ( - "INSERT INTO [dogs_fts] (rowid, [name])\n SELECT rowid, [name] FROM [dogs];", + 'INSERT INTO "dogs_fts" (rowid, "name")\n SELECT rowid, "name" FROM "dogs";', None, ), ] @@ -64,7 +64,7 @@ def tracer(sql, params): " )\n" " )", { - "like": "%VIRTUAL TABLE%USING FTS%content=[dogs]%", + "like": '%VIRTUAL TABLE%USING FTS%content="dogs"%', "like2": '%VIRTUAL TABLE%USING FTS%content="dogs"%', "table": "dogs", }, @@ -73,21 +73,21 @@ def tracer(sql, params): ("select name from sqlite_master where type = 'view'", None), ("select sql from sqlite_master where name = ?", ("dogs_fts",)), ( - "with original as (\n" + 'with "original" as (\n' " select\n" " rowid,\n" " *\n" - " from [dogs]\n" + ' from "dogs"\n' ")\n" "select\n" - " [original].*\n" + ' "original".*\n' "from\n" - " [original]\n" - " join [dogs_fts] on [original].rowid = [dogs_fts].rowid\n" + ' "original"\n' + ' join "dogs_fts" on "original".rowid = "dogs_fts".rowid\n' "where\n" - " [dogs_fts] match :query\n" + ' "dogs_fts" match :query\n' "order by\n" - " [dogs_fts].rank", + ' "dogs_fts".rank', {"query": "Cleopaws"}, ), ] diff --git a/tests/test_transform.py b/tests/test_transform.py index 7ced9d96..e763a6cd 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -10,90 +10,90 @@ ( {}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Change column type ( {"types": {"age": int}}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] INTEGER\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" INTEGER\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Rename a column ( {"rename": {"age": "dog_age"}}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [dog_age] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [dog_age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "dog_age" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Drop a column ( {"drop": ["age"]}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name])\n SELECT [rowid], [id], [name] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name")\n SELECT "rowid", "id", "name" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Convert type AND rename column ( {"types": {"age": int}, "rename": {"age": "dog_age"}}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [dog_age] INTEGER\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [dog_age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "dog_age" INTEGER\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Change primary key ( {"pk": "age"}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT PRIMARY KEY\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER,\n "name" TEXT,\n "age" TEXT PRIMARY KEY\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Change primary key to a compound pk ( {"pk": ("age", "name")}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT,\n PRIMARY KEY ([age], [name])\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER,\n "name" TEXT,\n "age" TEXT,\n PRIMARY KEY ("age", "name")\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Remove primary key, creating a rowid table ( {"pk": None}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER,\n "name" TEXT,\n "age" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Keeping the table ( {"drop": ["age"], "keep_table": "kept_table"}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name])\n SELECT [rowid], [id], [name] FROM [dogs];", - "ALTER TABLE [dogs] RENAME TO [kept_table];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name")\n SELECT "rowid", "id", "name" FROM "dogs";', + 'ALTER TABLE "dogs" RENAME TO "kept_table";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), ], @@ -133,40 +133,40 @@ def tracer(sql, params): ( {}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER,\n "name" TEXT,\n "age" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Change column type ( {"types": {"age": int}}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] INTEGER\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER,\n "name" TEXT,\n "age" INTEGER\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Rename a column ( {"rename": {"age": "dog_age"}}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [dog_age] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [dog_age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER,\n "name" TEXT,\n "dog_age" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), # Make ID a primary key ( {"pk": "id"}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", + 'CREATE TABLE "dogs_new_suffix" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" TEXT\n);', + 'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";', + 'DROP TABLE "dogs";', + 'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";', ], ), ], @@ -204,13 +204,13 @@ def test_transform_sql_with_no_primary_key_to_primary_key_of_id(fresh_db): dogs.insert({"id": 1, "name": "Cleo", "age": "5"}) assert ( dogs.schema - == "CREATE TABLE [dogs] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT\n)" + == 'CREATE TABLE "dogs" (\n "id" INTEGER,\n "name" TEXT,\n "age" TEXT\n)' ) dogs.transform(pk="id") # Slight oddity: [dogs] becomes "dogs" during the rename: assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] TEXT\n)' + == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" TEXT\n)' ) @@ -220,7 +220,7 @@ def test_transform_rename_pk(fresh_db): dogs.transform(rename={"id": "pk"}) assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [pk] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] TEXT\n)' + == 'CREATE TABLE "dogs" (\n "pk" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" TEXT\n)' ) @@ -230,7 +230,7 @@ def test_transform_not_null(fresh_db): dogs.transform(not_null={"name"}) assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT NOT NULL,\n [age] TEXT\n)' + == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT NOT NULL,\n "age" TEXT\n)' ) @@ -240,7 +240,7 @@ def test_transform_remove_a_not_null(fresh_db): dogs.transform(not_null={"name": True, "age": False}) assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT NOT NULL,\n [age] TEXT\n)' + == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT NOT NULL,\n "age" TEXT\n)' ) @@ -251,7 +251,7 @@ def test_transform_add_not_null_with_rename(fresh_db, not_null): dogs.transform(not_null=not_null, rename={"age": "dog_age"}) assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [dog_age] TEXT NOT NULL\n)' + == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "dog_age" TEXT NOT NULL\n)' ) @@ -261,7 +261,7 @@ def test_transform_defaults(fresh_db): dogs.transform(defaults={"age": 1}) assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] INTEGER DEFAULT 1\n)' + == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" INTEGER DEFAULT 1\n)' ) @@ -271,7 +271,7 @@ def test_transform_defaults_and_rename_column(fresh_db): dogs.transform(rename={"age": "dog_age"}, defaults={"age": 1}) assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [dog_age] INTEGER DEFAULT 1\n)' + == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "dog_age" INTEGER DEFAULT 1\n)' ) @@ -281,7 +281,7 @@ def test_remove_defaults(fresh_db): dogs.transform(defaults={"age": None}) assert ( dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] INTEGER\n)' + == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" INTEGER\n)' ) @@ -391,7 +391,7 @@ def test_transform_verify_foreign_keys(fresh_db): # This should have rolled us back assert ( fresh_db["authors"].schema - == "CREATE TABLE [authors] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n)" + == 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)' ) assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0] @@ -420,11 +420,11 @@ def test_transform_add_foreign_keys_from_scratch(fresh_db): ] assert fresh_db["places"].schema == ( 'CREATE TABLE "places" (\n' - " [id] INTEGER,\n" - " [name] TEXT,\n" - " [country] INTEGER REFERENCES [country]([id]),\n" - " [continent] INTEGER REFERENCES [continent]([id]),\n" - " [city] INTEGER REFERENCES [city]([id])\n" + ' "id" INTEGER,\n' + ' "name" TEXT,\n' + ' "country" INTEGER REFERENCES "country"("id"),\n' + ' "continent" INTEGER REFERENCES "continent"("id"),\n' + ' "city" INTEGER REFERENCES "city"("id")\n' ")" ) @@ -491,11 +491,11 @@ def test_transform_replace_foreign_keys(fresh_db, foreign_keys): fresh_db["places"].transform(foreign_keys=foreign_keys) assert fresh_db["places"].schema == ( 'CREATE TABLE "places" (\n' - " [id] INTEGER,\n" - " [name] TEXT,\n" - " [country] INTEGER REFERENCES [country]([id]),\n" - " [continent] INTEGER REFERENCES [continent]([id]),\n" - " [city] INTEGER\n" + ' "id" INTEGER,\n' + ' "name" TEXT,\n' + ' "country" INTEGER REFERENCES "country"("id"),\n' + ' "continent" INTEGER REFERENCES "continent"("id"),\n' + ' "city" INTEGER\n' ")" ) diff --git a/tests/test_update.py b/tests/test_update.py index 916eb19b..03bec114 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -70,11 +70,12 @@ def test_update_alter(fresh_db): ] == list(table.rows) -def test_update_alter_with_invalid_column_characters(fresh_db): +def test_update_alter_with_special_column_characters(fresh_db): + # With double-quote escaping, columns with special characters are now valid table = fresh_db["table"] rowid = table.insert({"foo": "bar"}).last_pk - with pytest.raises(AssertionError): - table.update(rowid, {"new_col[abc]": 1.2}, alter=True) + table.update(rowid, {"new_col[abc]": 1.2}, alter=True) + assert list(table.rows) == [{"foo": "bar", "new_col[abc]": 1.2}] def test_update_with_no_values_sets_last_pk(fresh_db):