Use double quotes not braces for tables and columns (#678)

Closes #677
This commit is contained in:
Simon Willison 2025-11-23 20:43:26 -08:00 committed by GitHub
commit fb93452ea8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 910 additions and 849 deletions

View file

@ -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 <cli_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 <https://www.sqlite.org/stricttables.html>`__ using ``--strict``:
@ -1988,9 +1988,9 @@ You can create a table in `SQLite STRICT mode <https://www.sqlite.org/stricttabl
table schema
------- ------------------------
mytable CREATE TABLE [mytable] (
[id] INTEGER,
[name] TEXT
mytable CREATE TABLE "mytable" (
"id" INTEGER,
"name" TEXT
) STRICT
If a table with the same name already exists, you will get an error. You can choose to silently ignore this error with ``--ignore``, or you can replace the existing table with a new, empty table using ``--replace``.
@ -2100,16 +2100,16 @@ If you want to see the SQL that will be executed to make the change without actu
.. code-block:: output
CREATE TABLE [roadside_attractions_new_4033a60276b9] (
[id] INTEGER PRIMARY KEY,
[longitude] FLOAT,
[latitude] FLOAT,
[name] TEXT DEFAULT 'Untitled'
CREATE TABLE "roadside_attractions_new_4033a60276b9" (
"id" INTEGER PRIMARY KEY,
"longitude" FLOAT,
"latitude" FLOAT,
"name" TEXT DEFAULT 'Untitled'
);
INSERT INTO [roadside_attractions_new_4033a60276b9] ([longitude], [latitude], [id], [name])
SELECT [longitude], [latitude], [pk], [name] FROM [roadside_attractions];
DROP TABLE [roadside_attractions];
ALTER TABLE [roadside_attractions_new_4033a60276b9] RENAME TO [roadside_attractions];
INSERT INTO "roadside_attractions_new_4033a60276b9" ("longitude", "latitude", "id", "name")
SELECT "longitude", "latitude", "pk", "name" FROM "roadside_attractions";
DROP TABLE "roadside_attractions";
ALTER TABLE "roadside_attractions_new_4033a60276b9" RENAME TO "roadside_attractions";
.. _cli_transform_table_add_primary_key_to_rowid:
@ -2126,8 +2126,8 @@ SQLite tables that are created without an explicit primary key are created as `r
.. code-block:: output
CREATE TABLE [chickens] (
[name] TEXT
CREATE TABLE "chickens" (
"name" TEXT
);
.. code-block:: bash
@ -2161,8 +2161,8 @@ You can use ``sqlite-utils transform ... --pk id`` to add a primary key column c
.. code-block:: output
CREATE TABLE "chickens" (
[id] INTEGER PRIMARY KEY,
[name] TEXT
"id" INTEGER PRIMARY KEY,
"name" TEXT
);
.. code-block:: bash
@ -2208,17 +2208,17 @@ This would produce 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,
[species] TEXT
CREATE TABLE "species" (
"id" INTEGER PRIMARY KEY,
"species" TEXT
);
CREATE UNIQUE INDEX [idx_species_species]
ON [species] ([species]);
CREATE UNIQUE INDEX "idx_species_species"
ON "species" ("species");
The command takes the following options:
@ -2251,40 +2251,40 @@ After running the above, the command ``sqlite-utils schema global.db`` reveals t
.. code-block:: sql
CREATE TABLE [countries] (
[id] INTEGER PRIMARY KEY,
[country] TEXT,
[name] TEXT
CREATE TABLE "countries" (
"id" INTEGER PRIMARY KEY,
"country" TEXT,
"name" TEXT
);
CREATE TABLE "power_plants" (
[country_id] INTEGER,
[name] TEXT,
[gppd_idnr] TEXT,
[capacity_mw] TEXT,
[latitude] TEXT,
[longitude] TEXT,
[primary_fuel] TEXT,
[other_fuel1] TEXT,
[other_fuel2] TEXT,
[other_fuel3] TEXT,
[commissioning_year] TEXT,
[owner] TEXT,
[source] TEXT,
[url] TEXT,
[geolocation_source] TEXT,
[wepp_id] TEXT,
[year_of_capacity_data] TEXT,
[generation_gwh_2013] TEXT,
[generation_gwh_2014] TEXT,
[generation_gwh_2015] TEXT,
[generation_gwh_2016] TEXT,
[generation_gwh_2017] TEXT,
[generation_data_source] TEXT,
[estimated_generation_gwh] TEXT,
FOREIGN KEY([country_id]) REFERENCES [countries]([id])
"country_id" INTEGER,
"name" TEXT,
"gppd_idnr" TEXT,
"capacity_mw" TEXT,
"latitude" TEXT,
"longitude" TEXT,
"primary_fuel" TEXT,
"other_fuel1" TEXT,
"other_fuel2" TEXT,
"other_fuel3" TEXT,
"commissioning_year" TEXT,
"owner" TEXT,
"source" TEXT,
"url" TEXT,
"geolocation_source" TEXT,
"wepp_id" TEXT,
"year_of_capacity_data" TEXT,
"generation_gwh_2013" TEXT,
"generation_gwh_2014" TEXT,
"generation_gwh_2015" TEXT,
"generation_gwh_2016" TEXT,
"generation_gwh_2017" TEXT,
"generation_data_source" TEXT,
"estimated_generation_gwh" TEXT,
FOREIGN KEY("country_id") REFERENCES "countries"("id")
);
CREATE UNIQUE INDEX [idx_countries_country_name]
ON [countries] ([country], [name]);
CREATE UNIQUE INDEX "idx_countries_country_name"
ON "countries" ("country", "name");
.. _cli_create_view:
@ -2567,17 +2567,17 @@ Use the ``--sql`` option to output the SQL that would be executed, rather than r
select
rowid,
*
from [documents]
from "documents"
)
select
[original].*
"original".*
from
[original]
join [documents_fts] on [original].rowid = [documents_fts].rowid
"original"
join "documents_fts" on "original".rowid = "documents_fts".rowid
where
[documents_fts] match :query
"documents_fts" match :query
order by
[documents_fts].rank
"documents_fts".rank
.. _cli_enable_counts:

View file

@ -459,8 +459,8 @@ The ``db.schema`` property returns the full SQL schema for the database as a str
>>> 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:

View file

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

View file

@ -6,7 +6,13 @@ import hashlib
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(

View file

@ -70,6 +70,16 @@ USING\s+(?P<using>\w+) # for example USING FTS5
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 @@ class BadMultiValues(Exception):
_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 @@ class Database:
: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 @@ class Database:
: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 @@ class Database:
), "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 @@ class Database:
)
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 @@ class Database:
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 @@ class Database:
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 @@ class Database:
: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 @@ class Database:
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 @@ class Database:
"""
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 @@ class Queryable:
: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 @@ class Queryable:
"""
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 @@ class Queryable:
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 @@ class Queryable:
"List of :ref:`Columns <reference_db_other_column>` 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 @@ class Table(Queryable):
)
)
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 @@ class Table(Queryable):
"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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
"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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
# 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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
: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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
: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 @@ class Table(Queryable):
"""
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 @@ class Table(Queryable):
# 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 @@ class Table(Queryable):
"""
).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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
"""
# 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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
"""
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
if fn_name == "<lambda>":
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 @@ class Table(Queryable):
) 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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
# 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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
# 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 @@ class Table(Queryable):
# 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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class Table(Queryable):
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 @@ class View(Queryable):
"""
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

View file

@ -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 @@ _common_other_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 @@ _common_other_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 @@ _common_other_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"
)

View file

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

View file

@ -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"}]

View file

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

View file

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

View file

@ -6,12 +6,12 @@ import pytest
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()

View file

@ -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"]),
]

View file

@ -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) == [

View file

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

View file

@ -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'))"
),
),
],

View file

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

View file

@ -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) == {

View file

@ -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 test_with_tracer():
" )\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 test_with_tracer():
("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"},
),
]

View file

@ -10,90 +10,90 @@ import pytest
(
{},
[
"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 test_transform_sql_table_with_primary_key(
(
{},
[
"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'
")"
)

View file

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