mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-07-25 02:14:31 +02:00
parent
bf1ac778a3
commit
fb93452ea8
20 changed files with 910 additions and 849 deletions
272
docs/cli.rst
272
docs/cli.rst
|
|
@ -547,13 +547,13 @@ To see the in-memory database schema that would be used for a file or for multip
|
|||
|
||||
.. code-block:: output
|
||||
|
||||
CREATE TABLE [dogs] (
|
||||
[id] INTEGER,
|
||||
[age] INTEGER,
|
||||
[name] TEXT
|
||||
CREATE TABLE "dogs" (
|
||||
"id" INTEGER,
|
||||
"age" INTEGER,
|
||||
"name" TEXT
|
||||
);
|
||||
CREATE VIEW t1 AS select * from [dogs];
|
||||
CREATE VIEW t AS select * from [dogs];
|
||||
CREATE VIEW "t1" AS select * from "dogs";
|
||||
CREATE VIEW "t" AS select * from "dogs";
|
||||
|
||||
You can run the equivalent of the :ref:`analyze-tables <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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"}]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"]),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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) == [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'))"
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) == {
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
")"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue