mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-09-27 20:34:12 +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
|
.. code-block:: output
|
||||||
|
|
||||||
CREATE TABLE [dogs] (
|
CREATE TABLE "dogs" (
|
||||||
[id] INTEGER,
|
"id" INTEGER,
|
||||||
[age] INTEGER,
|
"age" INTEGER,
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
);
|
);
|
||||||
CREATE VIEW t1 AS select * from [dogs];
|
CREATE VIEW "t1" AS select * from "dogs";
|
||||||
CREATE VIEW t 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``:
|
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
|
.. code-block:: output
|
||||||
|
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
CREATE TABLE [dogs] (
|
CREATE TABLE "dogs" (
|
||||||
[id] INTEGER,
|
"id" INTEGER,
|
||||||
[age] INTEGER,
|
"age" INTEGER,
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
);
|
);
|
||||||
INSERT INTO "dogs" VALUES('1','4','Cleo');
|
INSERT INTO "dogs" VALUES('1','4','Cleo');
|
||||||
INSERT INTO "dogs" VALUES('2','2','Pancakes');
|
INSERT INTO "dogs" VALUES('2','2','Pancakes');
|
||||||
CREATE VIEW t1 AS select * from [dogs];
|
CREATE VIEW "t1" AS select * from "dogs";
|
||||||
CREATE VIEW t AS select * from [dogs];
|
CREATE VIEW "t" AS select * from "dogs";
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
Passing ``--save other.db`` will instead use that SQL to populate a new database file:
|
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)
|
Gosh CREATE TABLE Gosh (c1 text, c2 text, c3 text)
|
||||||
Gosh2 CREATE TABLE Gosh2 (c1 text, c2 text, c3 text)
|
Gosh2 CREATE TABLE Gosh2 (c1 text, c2 text, c3 text)
|
||||||
dogs CREATE TABLE [dogs] (
|
dogs CREATE TABLE "dogs" (
|
||||||
[id] INTEGER,
|
"id" INTEGER,
|
||||||
[age] INTEGER,
|
"age" INTEGER,
|
||||||
[name] TEXT)
|
"name" TEXT)
|
||||||
|
|
||||||
The ``--nl``, ``--csv``, ``--tsv``, ``--table`` and ``--fmt`` options are also available.
|
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
|
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
|
BEGIN
|
||||||
INSERT OR REPLACE INTO [_counts]
|
INSERT OR REPLACE INTO "_counts"
|
||||||
VALUES (
|
VALUES (
|
||||||
'plants',
|
'plants',
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT count FROM [_counts] WHERE [table] = 'plants'),
|
(SELECT count FROM "_counts" WHERE "table" = 'plants'),
|
||||||
0
|
0
|
||||||
) + 1
|
) + 1
|
||||||
);
|
);
|
||||||
|
|
@ -876,8 +876,8 @@ The ``sqlite-utils schema`` command shows the full SQL schema for the database:
|
||||||
.. code-block:: output
|
.. code-block:: output
|
||||||
|
|
||||||
CREATE TABLE "dogs" (
|
CREATE TABLE "dogs" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[name] TEXT
|
"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:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [_analyze_tables_] (
|
CREATE TABLE "_analyze_tables_" (
|
||||||
[table] TEXT,
|
"table" TEXT,
|
||||||
[column] TEXT,
|
"column" TEXT,
|
||||||
[total_rows] INTEGER,
|
"total_rows" INTEGER,
|
||||||
[num_null] INTEGER,
|
"num_null" INTEGER,
|
||||||
[num_blank] INTEGER,
|
"num_blank" INTEGER,
|
||||||
[num_distinct] INTEGER,
|
"num_distinct" INTEGER,
|
||||||
[most_common] TEXT,
|
"most_common" TEXT,
|
||||||
[least_common] TEXT,
|
"least_common" TEXT,
|
||||||
PRIMARY KEY ([table], [column])
|
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:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [logs] (
|
CREATE TABLE "logs" (
|
||||||
[httpRequest] TEXT,
|
"httpRequest" TEXT,
|
||||||
[insertId] TEXT,
|
"insertId" TEXT,
|
||||||
[labels] 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:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [logs] (
|
CREATE TABLE "logs" (
|
||||||
[httpRequest_latency] TEXT,
|
"httpRequest_latency" TEXT,
|
||||||
[httpRequest_requestMethod] TEXT,
|
"httpRequest_requestMethod" TEXT,
|
||||||
[httpRequest_requestSize] TEXT,
|
"httpRequest_requestSize" TEXT,
|
||||||
[httpRequest_status] INTEGER,
|
"httpRequest_status" INTEGER,
|
||||||
[insertId] TEXT,
|
"insertId" TEXT,
|
||||||
[labels_service] TEXT
|
"labels_service" TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
.. _cli_insert_csv_tsv:
|
.. _cli_insert_csv_tsv:
|
||||||
|
|
@ -1272,9 +1272,9 @@ Will produce this schema:
|
||||||
.. code-block:: output
|
.. code-block:: output
|
||||||
|
|
||||||
CREATE TABLE "creatures" (
|
CREATE TABLE "creatures" (
|
||||||
[name] TEXT,
|
"name" TEXT,
|
||||||
[age] INTEGER,
|
"age" INTEGER,
|
||||||
[weight] FLOAT
|
"weight" FLOAT
|
||||||
);
|
);
|
||||||
|
|
||||||
You can set the ``SQLITE_UTILS_DETECT_TYPES`` environment variable if you want ``--detect-types`` to be the default behavior:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [loglines] (
|
CREATE TABLE "loglines" (
|
||||||
[line] TEXT
|
"line" TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
You can also insert the entire contents of the file into a single column called ``text`` using ``--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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [content] (
|
CREATE TABLE "content" (
|
||||||
[text] TEXT
|
"text" TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
.. _cli_insert_convert:
|
.. _cli_insert_convert:
|
||||||
|
|
@ -1462,8 +1462,8 @@ The result looks like this:
|
||||||
.. code-block:: output
|
.. code-block:: output
|
||||||
|
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
CREATE TABLE [words] (
|
CREATE TABLE "words" (
|
||||||
[word] TEXT
|
"word" TEXT
|
||||||
);
|
);
|
||||||
INSERT INTO "words" VALUES('A');
|
INSERT INTO "words" VALUES('A');
|
||||||
INSERT INTO "words" VALUES('bunch');
|
INSERT INTO "words" VALUES('bunch');
|
||||||
|
|
@ -1568,10 +1568,10 @@ By default this command will create a table with the following schema:
|
||||||
|
|
||||||
.. code-block:: sql
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [images] (
|
CREATE TABLE "images" (
|
||||||
[path] TEXT PRIMARY KEY,
|
"path" TEXT PRIMARY KEY,
|
||||||
[content] BLOB,
|
"content" BLOB,
|
||||||
[size] INTEGER
|
"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.
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [images] (
|
CREATE TABLE "images" (
|
||||||
[path] TEXT PRIMARY KEY,
|
"path" TEXT PRIMARY KEY,
|
||||||
[md5] TEXT,
|
"md5" TEXT,
|
||||||
[mtime] FLOAT
|
"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.
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [places] (
|
CREATE TABLE "places" (
|
||||||
[location] TEXT,
|
"location" TEXT,
|
||||||
[latitude] FLOAT,
|
"latitude" FLOAT,
|
||||||
[longitude] 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``.
|
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
|
table schema
|
||||||
------- --------------------------------
|
------- --------------------------------
|
||||||
mytable CREATE TABLE [mytable] (
|
mytable CREATE TABLE "mytable" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[name] TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
[age] INTEGER NOT NULL,
|
"age" INTEGER NOT NULL,
|
||||||
[is_good] INTEGER DEFAULT '1'
|
"is_good" INTEGER DEFAULT '1'
|
||||||
)
|
)
|
||||||
|
|
||||||
You can specify foreign key relationships between the tables you are creating using ``--fk colname othertable othercolumn``:
|
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
|
table schema
|
||||||
------- -------------------------------------------------
|
------- -------------------------------------------------
|
||||||
authors CREATE TABLE [authors] (
|
authors CREATE TABLE "authors" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
)
|
)
|
||||||
books CREATE TABLE [books] (
|
books CREATE TABLE "books" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[title] TEXT,
|
"title" TEXT,
|
||||||
[author_id] INTEGER REFERENCES [authors]([id])
|
"author_id" INTEGER REFERENCES "authors"("id")
|
||||||
)
|
)
|
||||||
|
|
||||||
You can create a table in `SQLite STRICT mode <https://www.sqlite.org/stricttables.html>`__ using ``--strict``:
|
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
|
table schema
|
||||||
------- ------------------------
|
------- ------------------------
|
||||||
mytable CREATE TABLE [mytable] (
|
mytable CREATE TABLE "mytable" (
|
||||||
[id] INTEGER,
|
"id" INTEGER,
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
) STRICT
|
) 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``.
|
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
|
.. code-block:: output
|
||||||
|
|
||||||
CREATE TABLE [roadside_attractions_new_4033a60276b9] (
|
CREATE TABLE "roadside_attractions_new_4033a60276b9" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[longitude] FLOAT,
|
"longitude" FLOAT,
|
||||||
[latitude] FLOAT,
|
"latitude" FLOAT,
|
||||||
[name] TEXT DEFAULT 'Untitled'
|
"name" TEXT DEFAULT 'Untitled'
|
||||||
);
|
);
|
||||||
INSERT INTO [roadside_attractions_new_4033a60276b9] ([longitude], [latitude], [id], [name])
|
INSERT INTO "roadside_attractions_new_4033a60276b9" ("longitude", "latitude", "id", "name")
|
||||||
SELECT [longitude], [latitude], [pk], [name] FROM [roadside_attractions];
|
SELECT "longitude", "latitude", "pk", "name" FROM "roadside_attractions";
|
||||||
DROP TABLE [roadside_attractions];
|
DROP TABLE "roadside_attractions";
|
||||||
ALTER TABLE [roadside_attractions_new_4033a60276b9] RENAME TO [roadside_attractions];
|
ALTER TABLE "roadside_attractions_new_4033a60276b9" RENAME TO "roadside_attractions";
|
||||||
|
|
||||||
.. _cli_transform_table_add_primary_key_to_rowid:
|
.. _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
|
.. code-block:: output
|
||||||
|
|
||||||
CREATE TABLE [chickens] (
|
CREATE TABLE "chickens" (
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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
|
.. code-block:: output
|
||||||
|
|
||||||
CREATE TABLE "chickens" (
|
CREATE TABLE "chickens" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
@ -2208,17 +2208,17 @@ This would produce the following schema:
|
||||||
.. code-block:: sql
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE "trees" (
|
CREATE TABLE "trees" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[TreeAddress] TEXT,
|
"TreeAddress" TEXT,
|
||||||
[species_id] INTEGER,
|
"species_id" INTEGER,
|
||||||
FOREIGN KEY(species_id) REFERENCES species(id)
|
FOREIGN KEY(species_id) REFERENCES species(id)
|
||||||
);
|
);
|
||||||
CREATE TABLE [species] (
|
CREATE TABLE "species" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[species] TEXT
|
"species" TEXT
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX [idx_species_species]
|
CREATE UNIQUE INDEX "idx_species_species"
|
||||||
ON [species] ([species]);
|
ON "species" ("species");
|
||||||
|
|
||||||
The command takes the following options:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [countries] (
|
CREATE TABLE "countries" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[country] TEXT,
|
"country" TEXT,
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
);
|
);
|
||||||
CREATE TABLE "power_plants" (
|
CREATE TABLE "power_plants" (
|
||||||
[country_id] INTEGER,
|
"country_id" INTEGER,
|
||||||
[name] TEXT,
|
"name" TEXT,
|
||||||
[gppd_idnr] TEXT,
|
"gppd_idnr" TEXT,
|
||||||
[capacity_mw] TEXT,
|
"capacity_mw" TEXT,
|
||||||
[latitude] TEXT,
|
"latitude" TEXT,
|
||||||
[longitude] TEXT,
|
"longitude" TEXT,
|
||||||
[primary_fuel] TEXT,
|
"primary_fuel" TEXT,
|
||||||
[other_fuel1] TEXT,
|
"other_fuel1" TEXT,
|
||||||
[other_fuel2] TEXT,
|
"other_fuel2" TEXT,
|
||||||
[other_fuel3] TEXT,
|
"other_fuel3" TEXT,
|
||||||
[commissioning_year] TEXT,
|
"commissioning_year" TEXT,
|
||||||
[owner] TEXT,
|
"owner" TEXT,
|
||||||
[source] TEXT,
|
"source" TEXT,
|
||||||
[url] TEXT,
|
"url" TEXT,
|
||||||
[geolocation_source] TEXT,
|
"geolocation_source" TEXT,
|
||||||
[wepp_id] TEXT,
|
"wepp_id" TEXT,
|
||||||
[year_of_capacity_data] TEXT,
|
"year_of_capacity_data" TEXT,
|
||||||
[generation_gwh_2013] TEXT,
|
"generation_gwh_2013" TEXT,
|
||||||
[generation_gwh_2014] TEXT,
|
"generation_gwh_2014" TEXT,
|
||||||
[generation_gwh_2015] TEXT,
|
"generation_gwh_2015" TEXT,
|
||||||
[generation_gwh_2016] TEXT,
|
"generation_gwh_2016" TEXT,
|
||||||
[generation_gwh_2017] TEXT,
|
"generation_gwh_2017" TEXT,
|
||||||
[generation_data_source] TEXT,
|
"generation_data_source" TEXT,
|
||||||
[estimated_generation_gwh] TEXT,
|
"estimated_generation_gwh" TEXT,
|
||||||
FOREIGN KEY([country_id]) REFERENCES [countries]([id])
|
FOREIGN KEY("country_id") REFERENCES "countries"("id")
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX [idx_countries_country_name]
|
CREATE UNIQUE INDEX "idx_countries_country_name"
|
||||||
ON [countries] ([country], [name]);
|
ON "countries" ("country", "name");
|
||||||
|
|
||||||
.. _cli_create_view:
|
.. _cli_create_view:
|
||||||
|
|
||||||
|
|
@ -2567,17 +2567,17 @@ Use the ``--sql`` option to output the SQL that would be executed, rather than r
|
||||||
select
|
select
|
||||||
rowid,
|
rowid,
|
||||||
*
|
*
|
||||||
from [documents]
|
from "documents"
|
||||||
)
|
)
|
||||||
select
|
select
|
||||||
[original].*
|
"original".*
|
||||||
from
|
from
|
||||||
[original]
|
"original"
|
||||||
join [documents_fts] on [original].rowid = [documents_fts].rowid
|
join "documents_fts" on "original".rowid = "documents_fts".rowid
|
||||||
where
|
where
|
||||||
[documents_fts] match :query
|
"documents_fts" match :query
|
||||||
order by
|
order by
|
||||||
[documents_fts].rank
|
"documents_fts".rank
|
||||||
|
|
||||||
.. _cli_enable_counts:
|
.. _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")
|
>>> db = sqlite_utils.Database("dogs.db")
|
||||||
>>> print(db.schema)
|
>>> print(db.schema)
|
||||||
CREATE TABLE "dogs" (
|
CREATE TABLE "dogs" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[name] TEXT
|
"name" TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
.. _python_api_creating_tables:
|
.. _python_api_creating_tables:
|
||||||
|
|
@ -544,11 +544,11 @@ This will create a table with the following schema:
|
||||||
|
|
||||||
.. code-block:: sql
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [dogs] (
|
CREATE TABLE "dogs" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[name] TEXT,
|
"name" TEXT,
|
||||||
[age] INTEGER,
|
"age" INTEGER,
|
||||||
[weight] FLOAT
|
"weight" FLOAT
|
||||||
)
|
)
|
||||||
|
|
||||||
.. _python_api_explicit_create:
|
.. _python_api_explicit_create:
|
||||||
|
|
@ -729,10 +729,10 @@ Here's an example that uses these features:
|
||||||
# {'id': 3, 'name': 'Dharma', 'score': 1}]
|
# {'id': 3, 'name': 'Dharma', 'score': 1}]
|
||||||
print(db.table("authors").schema)
|
print(db.table("authors").schema)
|
||||||
# Outputs:
|
# Outputs:
|
||||||
# CREATE TABLE [authors] (
|
# CREATE TABLE "authors" (
|
||||||
# [id] INTEGER PRIMARY KEY,
|
# "id" INTEGER PRIMARY KEY,
|
||||||
# [name] TEXT NOT NULL,
|
# "name" TEXT NOT NULL,
|
||||||
# [score] INTEGER NOT NULL DEFAULT 1
|
# "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)
|
>>> list(db.table("characteristics_dogs").rows)
|
||||||
[{'characteristics_id': 1, 'dogs_id': 1}, {'characteristics_id': 2, 'dogs_id': 1}]
|
[{'characteristics_id': 1, 'dogs_id': 1}, {'characteristics_id': 2, 'dogs_id': 1}]
|
||||||
>>> print(db.table("characteristics_dogs").schema)
|
>>> print(db.table("characteristics_dogs").schema)
|
||||||
CREATE TABLE [characteristics_dogs] (
|
CREATE TABLE "characteristics_dogs" (
|
||||||
[characteristics_id] INTEGER REFERENCES [characteristics]([id]),
|
"characteristics_id" INTEGER REFERENCES "characteristics"("id"),
|
||||||
[dogs_id] INTEGER REFERENCES [dogs]([id]),
|
"dogs_id" INTEGER REFERENCES "dogs"("id"),
|
||||||
PRIMARY KEY ([characteristics_id], [dogs_id])
|
PRIMARY KEY ("characteristics_id", "dogs_id")
|
||||||
)
|
)
|
||||||
|
|
||||||
.. _python_api_analyze_column:
|
.. _python_api_analyze_column:
|
||||||
|
|
@ -1648,10 +1648,10 @@ The schema of the above table is:
|
||||||
|
|
||||||
.. code-block:: sql
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [Trees] (
|
CREATE TABLE "Trees" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[TreeAddress] TEXT,
|
"TreeAddress" TEXT,
|
||||||
[Species] TEXT
|
"Species" TEXT
|
||||||
)
|
)
|
||||||
|
|
||||||
Here's how to extract the ``Species`` column using ``.extract()``:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE "Trees" (
|
CREATE TABLE "Trees" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[TreeAddress] TEXT,
|
"TreeAddress" TEXT,
|
||||||
[Species_id] INTEGER,
|
"Species_id" INTEGER,
|
||||||
FOREIGN KEY(Species_id) REFERENCES Species(id)
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [Species] (
|
CREATE TABLE "Species" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[Species] TEXT
|
"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``.
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE "Trees" (
|
CREATE TABLE "Trees" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[TreeAddress] TEXT,
|
"TreeAddress" TEXT,
|
||||||
[tree_species_id] INTEGER,
|
"tree_species_id" INTEGER,
|
||||||
FOREIGN KEY(tree_species_id) REFERENCES tree_species(id)
|
FOREIGN KEY(tree_species_id) REFERENCES tree_species(id)
|
||||||
)
|
)
|
||||||
|
|
||||||
CREATE TABLE [tree_species] (
|
CREATE TABLE "tree_species" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[Species] TEXT
|
"Species" TEXT
|
||||||
)
|
)
|
||||||
|
|
||||||
You can also extract multiple columns into the same external table. Say for example you have a table like this:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE "Trees" (
|
CREATE TABLE "Trees" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[TreeAddress] TEXT,
|
"TreeAddress" TEXT,
|
||||||
[CommonName_LatinName_id] INTEGER,
|
"CommonName_LatinName_id" INTEGER,
|
||||||
FOREIGN KEY(CommonName_LatinName_id) REFERENCES CommonName_LatinName(id)
|
FOREIGN KEY(CommonName_LatinName_id) REFERENCES CommonName_LatinName(id)
|
||||||
)
|
)
|
||||||
CREATE TABLE [CommonName_LatinName] (
|
CREATE TABLE "CommonName_LatinName" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[CommonName] TEXT,
|
"CommonName" TEXT,
|
||||||
[LatinName] 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:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE "Trees" (
|
CREATE TABLE "Trees" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[TreeAddress] TEXT,
|
"TreeAddress" TEXT,
|
||||||
[species_id] INTEGER,
|
"species_id" INTEGER,
|
||||||
FOREIGN KEY(species_id) REFERENCES Species(id)
|
FOREIGN KEY(species_id) REFERENCES Species(id)
|
||||||
)
|
)
|
||||||
CREATE TABLE [Species] (
|
CREATE TABLE "Species" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[CommonName] TEXT,
|
"CommonName" TEXT,
|
||||||
[LatinName] 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:
|
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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [Species] (
|
CREATE TABLE "Species" (
|
||||||
[id] INTEGER PRIMARY KEY,
|
"id" INTEGER PRIMARY KEY,
|
||||||
[name] TEXT,
|
"name" TEXT,
|
||||||
[latin] TEXT
|
"latin" TEXT
|
||||||
)
|
)
|
||||||
|
|
||||||
.. _python_api_hash:
|
.. _python_api_hash:
|
||||||
|
|
@ -2100,12 +2100,12 @@ The ``.schema`` property outputs the table's schema as a SQL string::
|
||||||
"Longitude" REAL,
|
"Longitude" REAL,
|
||||||
"Location" TEXT
|
"Location" TEXT
|
||||||
,
|
,
|
||||||
FOREIGN KEY ("PlantType") REFERENCES [PlantType](id),
|
FOREIGN KEY ("PlantType") REFERENCES "PlantType"(id),
|
||||||
FOREIGN KEY ("qCaretaker") REFERENCES [qCaretaker](id),
|
FOREIGN KEY ("qCaretaker") REFERENCES "qCaretaker"(id),
|
||||||
FOREIGN KEY ("qSpecies") REFERENCES [qSpecies](id),
|
FOREIGN KEY ("qSpecies") REFERENCES "qSpecies"(id),
|
||||||
FOREIGN KEY ("qSiteInfo") REFERENCES [qSiteInfo](id),
|
FOREIGN KEY ("qSiteInfo") REFERENCES "qSiteInfo"(id),
|
||||||
FOREIGN KEY ("qCareAssistant") REFERENCES [qCareAssistant](id),
|
FOREIGN KEY ("qCareAssistant") REFERENCES "qCareAssistant"(id),
|
||||||
FOREIGN KEY ("qLegalStatus") REFERENCES [qLegalStatus](id))
|
FOREIGN KEY ("qLegalStatus") REFERENCES "qLegalStatus"(id))
|
||||||
|
|
||||||
.. _python_api_introspection_strict:
|
.. _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
|
.. code-block:: sql
|
||||||
|
|
||||||
CREATE TABLE [_counts] (
|
CREATE TABLE "_counts" (
|
||||||
[table] TEXT PRIMARY KEY,
|
"table" TEXT PRIMARY KEY,
|
||||||
[count] INTEGER DEFAULT 0
|
"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:
|
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:
|
# The table schema looks like this:
|
||||||
# print(db.table("cats").schema)
|
# print(db.table("cats").schema)
|
||||||
# CREATE TABLE [cats] (
|
# CREATE TABLE "cats" (
|
||||||
# [id] INTEGER PRIMARY KEY,
|
# "id" INTEGER PRIMARY KEY,
|
||||||
# [name] TEXT,
|
# "name" TEXT,
|
||||||
# [age] INTEGER,
|
# "age" INTEGER,
|
||||||
# [thumbnail] BLOB
|
# "thumbnail" BLOB
|
||||||
# )
|
# )
|
||||||
|
|
||||||
.. _python_api_register_function:
|
.. _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)
|
db.table("creatures").insert_all(rows)
|
||||||
print(db.schema)
|
print(db.schema)
|
||||||
# Outputs:
|
# Outputs:
|
||||||
# CREATE TABLE [creatures] (
|
# CREATE TABLE "creatures" (
|
||||||
# [id] TEXT,
|
# "id" TEXT,
|
||||||
# [name] TEXT
|
# "name" TEXT
|
||||||
# );
|
# );
|
||||||
|
|
||||||
We can detect the best column types using a ``TypeTracker`` instance:
|
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)
|
db.table("creatures2").transform(types=tracker.types)
|
||||||
print(db.table("creatures2").schema)
|
print(db.table("creatures2").schema)
|
||||||
# Outputs:
|
# Outputs:
|
||||||
# CREATE TABLE [creatures2] (
|
# CREATE TABLE "creatures2" (
|
||||||
# [id] INTEGER,
|
# "id" INTEGER,
|
||||||
# [name] TEXT
|
# "name" TEXT
|
||||||
# );
|
# );
|
||||||
|
|
||||||
.. _python_api_gis:
|
.. _python_api_gis:
|
||||||
|
|
|
||||||
|
|
@ -180,10 +180,10 @@
|
||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"CREATE TABLE [creatures] (\n",
|
"CREATE TABLE \"creatures\" (\n",
|
||||||
" [name] TEXT,\n",
|
" \"name\" TEXT,\n",
|
||||||
" [species] TEXT,\n",
|
" \"species\" TEXT,\n",
|
||||||
" [age] FLOAT\n",
|
" \"age\" FLOAT\n",
|
||||||
")\n"
|
")\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -534,11 +534,11 @@
|
||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"CREATE TABLE [creatures] (\n",
|
"CREATE TABLE \"creatures\" (\n",
|
||||||
" [id] INTEGER PRIMARY KEY,\n",
|
" \"id\" INTEGER PRIMARY KEY,\n",
|
||||||
" [name] TEXT,\n",
|
" \"name\" TEXT,\n",
|
||||||
" [species] TEXT,\n",
|
" \"species\" TEXT,\n",
|
||||||
" [age] FLOAT\n",
|
" \"age\" FLOAT\n",
|
||||||
")\n"
|
")\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -929,11 +929,11 @@
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"CREATE TABLE \"creatures\" (\n",
|
"CREATE TABLE \"creatures\" (\n",
|
||||||
" [id] INTEGER PRIMARY KEY,\n",
|
" \"id\" INTEGER PRIMARY KEY,\n",
|
||||||
" [name] TEXT,\n",
|
" \"name\" TEXT,\n",
|
||||||
" [species_id] INTEGER,\n",
|
" \"species_id\" INTEGER,\n",
|
||||||
" [age] FLOAT,\n",
|
" \"age\" FLOAT,\n",
|
||||||
" FOREIGN KEY([species_id]) REFERENCES [species]([id])\n",
|
" FOREIGN KEY(\"species_id\") REFERENCES \"species\"(\"id\")\n",
|
||||||
")\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"
|
"[{'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",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"CREATE TABLE [species] (\n",
|
"CREATE TABLE \"species\" (\n",
|
||||||
" [id] INTEGER PRIMARY KEY,\n",
|
" \"id\" INTEGER PRIMARY KEY,\n",
|
||||||
" [species] TEXT\n",
|
" \"species\" TEXT\n",
|
||||||
")\n",
|
")\n",
|
||||||
"[{'id': 1, 'species': 'dog'}, {'id': 2, 'species': 'chicken'}]\n"
|
"[{'id': 1, 'species': 'dog'}, {'id': 2, 'species': 'chicken'}]\n"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,13 @@ import hashlib
|
||||||
import pathlib
|
import pathlib
|
||||||
from runpy import run_module
|
from runpy import run_module
|
||||||
import sqlite_utils
|
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.plugins import pm, get_plugins
|
||||||
from sqlite_utils.utils import maximize_csv_field_size_limit
|
from sqlite_utils.utils import maximize_csv_field_size_limit
|
||||||
from sqlite_utils import recipes
|
from sqlite_utils import recipes
|
||||||
|
|
@ -1967,7 +1973,9 @@ def memory(
|
||||||
view_names.append("t")
|
view_names.append("t")
|
||||||
for view_name in view_names:
|
for view_name in view_names:
|
||||||
if not db[view_name].exists():
|
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:
|
if fp:
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
@ -2230,8 +2238,8 @@ def rows(
|
||||||
"""
|
"""
|
||||||
columns = "*"
|
columns = "*"
|
||||||
if column:
|
if column:
|
||||||
columns = ", ".join("[{}]".format(c) for c in column)
|
columns = ", ".join(quote_identifier(c) for c in column)
|
||||||
sql = "select {} from [{}]".format(columns, dbtable)
|
sql = "select {} from {}".format(columns, quote_identifier(dbtable))
|
||||||
if where:
|
if where:
|
||||||
sql += " where " + where
|
sql += " where " + where
|
||||||
if order:
|
if order:
|
||||||
|
|
@ -2288,10 +2296,10 @@ def triggers(
|
||||||
\b
|
\b
|
||||||
sqlite-utils triggers trees.db
|
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:
|
if tables:
|
||||||
quote = sqlite_utils.Database(memory=True).quote
|
quote = sqlite_utils.Database(memory=True).quote
|
||||||
sql += " and [table] in ({})".format(
|
sql += ' and "table" in ({})'.format(
|
||||||
", ".join(quote(table) for table in tables)
|
", ".join(quote(table) for table in tables)
|
||||||
)
|
)
|
||||||
ctx.invoke(
|
ctx.invoke(
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,16 @@ USING\s+(?P<using>\w+) # for example USING FTS5
|
||||||
re.VERBOSE | re.IGNORECASE,
|
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:
|
try:
|
||||||
import pandas as pd # type: ignore
|
import pandas as pd # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -280,8 +290,8 @@ class BadMultiValues(Exception):
|
||||||
|
|
||||||
|
|
||||||
_COUNTS_TABLE_CREATE_SQL = """
|
_COUNTS_TABLE_CREATE_SQL = """
|
||||||
CREATE TABLE IF NOT EXISTS [{}](
|
CREATE TABLE IF NOT EXISTS "{}"(
|
||||||
[table] TEXT PRIMARY KEY,
|
"table" TEXT PRIMARY KEY,
|
||||||
count INTEGER DEFAULT 0
|
count INTEGER DEFAULT 0
|
||||||
);
|
);
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
@ -500,9 +510,9 @@ class Database:
|
||||||
:param filepath: Path to SQLite database file on disk
|
:param filepath: Path to SQLite database file on disk
|
||||||
"""
|
"""
|
||||||
attach_sql = """
|
attach_sql = """
|
||||||
ATTACH DATABASE '{}' AS [{}];
|
ATTACH DATABASE '{}' AS {};
|
||||||
""".format(
|
""".format(
|
||||||
str(pathlib.Path(filepath).resolve()), alias
|
str(pathlib.Path(filepath).resolve()), quote_identifier(alias)
|
||||||
).strip()
|
).strip()
|
||||||
self.execute(attach_sql)
|
self.execute(attach_sql)
|
||||||
|
|
||||||
|
|
@ -786,9 +796,9 @@ class Database:
|
||||||
|
|
||||||
:param tables: Subset list of tables to return counts for.
|
: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:
|
if tables:
|
||||||
sql += " where [table] in ({})".format(", ".join("?" for table in tables))
|
sql += ' where "table" in ({})'.format(", ".join("?" for table in tables))
|
||||||
try:
|
try:
|
||||||
return {r[0]: r[1] for r in self.execute(sql, tables).fetchall()}
|
return {r[0]: r[1] for r in self.execute(sql, tables).fetchall()}
|
||||||
except OperationalError:
|
except OperationalError:
|
||||||
|
|
@ -933,7 +943,6 @@ class Database:
|
||||||
), "defaults set {} includes items not in columns {}".format(
|
), "defaults set {} includes items not in columns {}".format(
|
||||||
repr(set(defaults)), repr(set(columns.keys()))
|
repr(set(defaults)), repr(set(columns.keys()))
|
||||||
)
|
)
|
||||||
validate_column_names(columns.keys())
|
|
||||||
column_items = list(columns.items())
|
column_items = list(columns.items())
|
||||||
if column_order is not None:
|
if column_order is not None:
|
||||||
|
|
||||||
|
|
@ -976,9 +985,13 @@ class Database:
|
||||||
)
|
)
|
||||||
if column_name in foreign_keys_by_column:
|
if column_name in foreign_keys_by_column:
|
||||||
column_extras.append(
|
column_extras.append(
|
||||||
"REFERENCES [{other_table}]([{other_column}])".format(
|
"REFERENCES {}({})".format(
|
||||||
other_table=foreign_keys_by_column[column_name].other_table,
|
quote_identifier(
|
||||||
other_column=foreign_keys_by_column[column_name].other_column,
|
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]
|
column_type_str = COLUMN_TYPE_MAPPING[column_type]
|
||||||
|
|
@ -987,8 +1000,8 @@ class Database:
|
||||||
if strict and column_type_str == "FLOAT":
|
if strict and column_type_str == "FLOAT":
|
||||||
column_type_str = "REAL"
|
column_type_str = "REAL"
|
||||||
column_defs.append(
|
column_defs.append(
|
||||||
" [{column_name}] {column_type}{column_extras}".format(
|
" {} {column_type}{column_extras}".format(
|
||||||
column_name=column_name,
|
quote_identifier(column_name),
|
||||||
column_type=column_type_str,
|
column_type=column_type_str,
|
||||||
column_extras=(
|
column_extras=(
|
||||||
(" " + " ".join(column_extras)) if column_extras else ""
|
(" " + " ".join(column_extras)) if column_extras else ""
|
||||||
|
|
@ -998,15 +1011,15 @@ class Database:
|
||||||
extra_pk = ""
|
extra_pk = ""
|
||||||
if single_pk is None and pk and len(pk) > 1:
|
if single_pk is None and pk and len(pk) > 1:
|
||||||
extra_pk = ",\n PRIMARY KEY ({pks})".format(
|
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)
|
columns_sql = ",\n".join(column_defs)
|
||||||
sql = """CREATE TABLE {if_not_exists}[{table}] (
|
sql = """CREATE TABLE {if_not_exists}{table} (
|
||||||
{columns_sql}{extra_pk}
|
{columns_sql}{extra_pk}
|
||||||
){strict};
|
){strict};
|
||||||
""".format(
|
""".format(
|
||||||
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
|
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
|
||||||
table=name,
|
table=quote_identifier(name),
|
||||||
columns_sql=columns_sql,
|
columns_sql=columns_sql,
|
||||||
extra_pk=extra_pk,
|
extra_pk=extra_pk,
|
||||||
strict=" STRICT" if strict and self.supports_strict else "",
|
strict=" STRICT" if strict and self.supports_strict else "",
|
||||||
|
|
@ -1144,8 +1157,8 @@ class Database:
|
||||||
:param new_name: Name to rename it to
|
:param new_name: Name to rename it to
|
||||||
"""
|
"""
|
||||||
self.execute(
|
self.execute(
|
||||||
"ALTER TABLE [{name}] RENAME TO [{new_name}]".format(
|
"ALTER TABLE {} RENAME TO {}".format(
|
||||||
name=name, new_name=new_name
|
quote_identifier(name), quote_identifier(new_name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1163,7 +1176,9 @@ class Database:
|
||||||
assert not (
|
assert not (
|
||||||
ignore and replace
|
ignore and replace
|
||||||
), "Use one or the other of ignore/replace, not both"
|
), "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:
|
if ignore or replace:
|
||||||
# Does view exist already?
|
# Does view exist already?
|
||||||
if name in self.view_names():
|
if name in self.view_names():
|
||||||
|
|
@ -1270,7 +1285,7 @@ class Database:
|
||||||
"""
|
"""
|
||||||
sql = "ANALYZE"
|
sql = "ANALYZE"
|
||||||
if name is not None:
|
if name is not None:
|
||||||
sql += " [{}]".format(name)
|
sql += " {}".format(quote_identifier(name))
|
||||||
self.execute(sql)
|
self.execute(sql)
|
||||||
|
|
||||||
def iterdump(self) -> Generator[str, None, None]:
|
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 > ?``
|
:param where_args: Parameters to use with that fragment - an iterable for ``id > ?``
|
||||||
parameters, or a dictionary for ``id > :id``
|
parameters, or a dictionary for ``id > :id``
|
||||||
"""
|
"""
|
||||||
sql = "select count(*) from [{}]".format(self.name)
|
sql = "select count(*) from {}".format(quote_identifier(self.name))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
sql += " where " + where
|
sql += " where " + where
|
||||||
return self.db.execute(sql, where_args or []).fetchone()[0]
|
return self.db.execute(sql, where_args or []).fetchone()[0]
|
||||||
|
|
@ -1391,7 +1406,7 @@ class Queryable:
|
||||||
"""
|
"""
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
return
|
return
|
||||||
sql = "select {} from [{}]".format(select, self.name)
|
sql = "select {} from {}".format(select, quote_identifier(self.name))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
sql += " where " + where
|
sql += " where " + where
|
||||||
if order_by is not None:
|
if order_by is not None:
|
||||||
|
|
@ -1429,7 +1444,7 @@ class Queryable:
|
||||||
if not pks:
|
if not pks:
|
||||||
column_names.insert(0, "rowid")
|
column_names.insert(0, "rowid")
|
||||||
pks = ["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(
|
for row in self.rows_where(
|
||||||
select=select,
|
select=select,
|
||||||
where=where,
|
where=where,
|
||||||
|
|
@ -1448,7 +1463,9 @@ class Queryable:
|
||||||
"List of :ref:`Columns <reference_db_other_column>` representing the columns in this table or view."
|
"List of :ref:`Columns <reference_db_other_column>` representing the columns in this table or view."
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
return []
|
return []
|
||||||
rows = self.db.execute("PRAGMA table_info([{}])".format(self.name)).fetchall()
|
rows = self.db.execute(
|
||||||
|
"PRAGMA table_info({})".format(quote_identifier(self.name))
|
||||||
|
).fetchall()
|
||||||
return [Column(*row) for row in rows]
|
return [Column(*row) for row in rows]
|
||||||
|
|
||||||
@property
|
@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)
|
rows = self.rows_where(" and ".join(wheres), pk_values)
|
||||||
try:
|
try:
|
||||||
row = list(rows)[0]
|
row = list(rows)[0]
|
||||||
|
|
@ -1602,7 +1619,7 @@ class Table(Queryable):
|
||||||
"List of foreign keys defined on this table."
|
"List of foreign keys defined on this table."
|
||||||
fks = []
|
fks = []
|
||||||
for row in self.db.execute(
|
for row in self.db.execute(
|
||||||
"PRAGMA foreign_key_list([{}])".format(self.name)
|
"PRAGMA foreign_key_list({})".format(quote_identifier(self.name))
|
||||||
).fetchall():
|
).fetchall():
|
||||||
if row is not None:
|
if row is not None:
|
||||||
id, seq, table_name, from_, to_, on_update, on_delete, match = row
|
id, seq, table_name, from_, to_, on_update, on_delete, match = row
|
||||||
|
|
@ -1799,9 +1816,9 @@ class Table(Queryable):
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
raise NoTable(f"Table {self.name} does not exist")
|
raise NoTable(f"Table {self.name} does not exist")
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
sql = "CREATE TABLE [{new_table}] AS SELECT * FROM [{table}];".format(
|
sql = "CREATE TABLE {} AS SELECT * FROM {};".format(
|
||||||
new_table=new_name,
|
quote_identifier(new_name),
|
||||||
table=self.name,
|
quote_identifier(self.name),
|
||||||
)
|
)
|
||||||
self.db.execute(sql)
|
self.db.execute(sql)
|
||||||
return self.db[new_name]
|
return self.db[new_name]
|
||||||
|
|
@ -2034,23 +2051,27 @@ class Table(Queryable):
|
||||||
if "rowid" not in new_cols:
|
if "rowid" not in new_cols:
|
||||||
new_cols.insert(0, "rowid")
|
new_cols.insert(0, "rowid")
|
||||||
old_cols.insert(0, "rowid")
|
old_cols.insert(0, "rowid")
|
||||||
copy_sql = "INSERT INTO [{new_table}] ({new_cols})\n SELECT {old_cols} FROM [{old_table}];".format(
|
copy_sql = "INSERT INTO {} ({new_cols})\n SELECT {old_cols} FROM {};".format(
|
||||||
new_table=new_table_name,
|
quote_identifier(new_table_name),
|
||||||
old_table=self.name,
|
quote_identifier(self.name),
|
||||||
old_cols=", ".join("[{}]".format(col) for col in old_cols),
|
old_cols=", ".join(quote_identifier(col) for col in old_cols),
|
||||||
new_cols=", ".join("[{}]".format(col) for col in new_cols),
|
new_cols=", ".join(quote_identifier(col) for col in new_cols),
|
||||||
)
|
)
|
||||||
sqls.append(copy_sql)
|
sqls.append(copy_sql)
|
||||||
# Drop (or keep) the old table
|
# Drop (or keep) the old table
|
||||||
if keep_table:
|
if keep_table:
|
||||||
sqls.append(
|
sqls.append(
|
||||||
"ALTER TABLE [{}] RENAME TO [{}];".format(self.name, keep_table)
|
"ALTER TABLE {} RENAME TO {};".format(
|
||||||
|
quote_identifier(self.name), quote_identifier(keep_table)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sqls.append("DROP TABLE [{}];".format(self.name))
|
sqls.append("DROP TABLE {};".format(quote_identifier(self.name)))
|
||||||
# Rename the new one
|
# Rename the new one
|
||||||
sqls.append(
|
sqls.append(
|
||||||
"ALTER TABLE [{}] RENAME TO [{}];".format(new_table_name, self.name)
|
"ALTER TABLE {} RENAME TO {};".format(
|
||||||
|
quote_identifier(new_table_name), quote_identifier(self.name)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
# Re-add existing indexes
|
# Re-add existing indexes
|
||||||
for index in self.indexes:
|
for index in self.indexes:
|
||||||
|
|
@ -2066,7 +2087,7 @@ class Table(Queryable):
|
||||||
"transformation and manually recreate the new index after running this transformation."
|
"transformation and manually recreate the new index after running this transformation."
|
||||||
)
|
)
|
||||||
if keep_table:
|
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:
|
for col in index.columns:
|
||||||
if col in rename.keys() or col in drop:
|
if col in rename.keys() or col in drop:
|
||||||
raise TransformError(
|
raise TransformError(
|
||||||
|
|
@ -2137,11 +2158,11 @@ class Table(Queryable):
|
||||||
lookup_columns = [(rename.get(col) or col) for col in columns]
|
lookup_columns = [(rename.get(col) or col) for col in columns]
|
||||||
lookup_table.create_index(lookup_columns, unique=True, if_not_exists=True)
|
lookup_table.create_index(lookup_columns, unique=True, if_not_exists=True)
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"INSERT OR IGNORE INTO [{lookup_table}] ({lookup_columns}) SELECT DISTINCT {table_cols} FROM [{table}]".format(
|
"INSERT OR IGNORE INTO {} ({lookup_columns}) SELECT DISTINCT {table_cols} FROM {}".format(
|
||||||
lookup_table=table,
|
quote_identifier(table),
|
||||||
lookup_columns=", ".join("[{}]".format(c) for c in lookup_columns),
|
quote_identifier(self.name),
|
||||||
table_cols=", ".join("[{}]".format(c) for c in columns),
|
lookup_columns=", ".join(quote_identifier(c) for c in lookup_columns),
|
||||||
table=self.name,
|
table_cols=", ".join(quote_identifier(c) for c in columns),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2150,16 +2171,16 @@ class Table(Queryable):
|
||||||
|
|
||||||
# And populate it
|
# And populate it
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"UPDATE [{table}] SET [{magic_lookup_column}] = (SELECT id FROM [{lookup_table}] WHERE {where})".format(
|
"UPDATE {} SET {} = (SELECT id FROM {} WHERE {where})".format(
|
||||||
table=self.name,
|
quote_identifier(self.name),
|
||||||
magic_lookup_column=magic_lookup_column,
|
quote_identifier(magic_lookup_column),
|
||||||
lookup_table=table,
|
quote_identifier(table),
|
||||||
where=" AND ".join(
|
where=" AND ".join(
|
||||||
"[{table}].[{column}] IS [{lookup_table}].[{lookup_column}]".format(
|
"{}.{} IS {}.{}".format(
|
||||||
table=self.name,
|
quote_identifier(self.name),
|
||||||
lookup_table=table,
|
quote_identifier(column),
|
||||||
column=column,
|
quote_identifier(table),
|
||||||
lookup_column=rename.get(column) or column,
|
quote_identifier(rename.get(column) or column),
|
||||||
)
|
)
|
||||||
for column in columns
|
for column in columns
|
||||||
),
|
),
|
||||||
|
|
@ -2216,10 +2237,9 @@ class Table(Queryable):
|
||||||
columns_sql = []
|
columns_sql = []
|
||||||
for column in columns:
|
for column in columns:
|
||||||
if isinstance(column, DescIndex):
|
if isinstance(column, DescIndex):
|
||||||
fmt = "[{}] desc"
|
columns_sql.append("{} desc".format(quote_identifier(column)))
|
||||||
else:
|
else:
|
||||||
fmt = "[{}]"
|
columns_sql.append(quote_identifier(column))
|
||||||
columns_sql.append(fmt.format(column))
|
|
||||||
|
|
||||||
suffix = None
|
suffix = None
|
||||||
created_index_name = None
|
created_index_name = None
|
||||||
|
|
@ -2230,14 +2250,14 @@ class Table(Queryable):
|
||||||
sql = (
|
sql = (
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
CREATE {unique}INDEX {if_not_exists}[{index_name}]
|
CREATE {unique}INDEX {if_not_exists}{index_name}
|
||||||
ON [{table_name}] ({columns});
|
ON {table_name} ({columns});
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.format(
|
||||||
index_name=created_index_name,
|
index_name=quote_identifier(created_index_name),
|
||||||
table_name=self.name,
|
table_name=quote_identifier(self.name),
|
||||||
columns=", ".join(columns_sql),
|
columns=", ".join(columns_sql),
|
||||||
unique="UNIQUE " if unique else "",
|
unique="UNIQUE " if unique else "",
|
||||||
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
|
if_not_exists="IF NOT EXISTS " if if_not_exists else "",
|
||||||
|
|
@ -2307,9 +2327,9 @@ class Table(Queryable):
|
||||||
not_null_sql = "NOT NULL DEFAULT {}".format(
|
not_null_sql = "NOT NULL DEFAULT {}".format(
|
||||||
self.db.quote_default_value(not_null_default)
|
self.db.quote_default_value(not_null_default)
|
||||||
)
|
)
|
||||||
sql = "ALTER TABLE [{table}] ADD COLUMN [{col_name}] {col_type}{not_null_default};".format(
|
sql = "ALTER TABLE {} ADD COLUMN {} {col_type}{not_null_default};".format(
|
||||||
table=self.name,
|
quote_identifier(self.name),
|
||||||
col_name=col_name,
|
quote_identifier(col_name),
|
||||||
col_type=fk_col_type or COLUMN_TYPE_MAPPING[col_type],
|
col_type=fk_col_type or COLUMN_TYPE_MAPPING[col_type],
|
||||||
not_null_default=(" " + not_null_sql) if not_null_sql else "",
|
not_null_default=(" " + not_null_sql) if not_null_sql else "",
|
||||||
)
|
)
|
||||||
|
|
@ -2325,7 +2345,7 @@ class Table(Queryable):
|
||||||
:param ignore: Set to ``True`` to ignore the error if the table does not exist
|
:param ignore: Set to ``True`` to ignore the error if the table does not exist
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.db.execute("DROP TABLE [{}]".format(self.name))
|
self.db.execute("DROP TABLE {}".format(quote_identifier(self.name)))
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
if not ignore:
|
if not ignore:
|
||||||
raise
|
raise
|
||||||
|
|
@ -2431,29 +2451,29 @@ class Table(Queryable):
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
{create_counts_table}
|
{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
|
BEGIN
|
||||||
INSERT OR REPLACE INTO [{counts_table}]
|
INSERT OR REPLACE INTO {counts_table}
|
||||||
VALUES (
|
VALUES (
|
||||||
{table_quoted},
|
{table_quoted},
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT count FROM [{counts_table}] WHERE [table] = {table_quoted}),
|
(SELECT count FROM {counts_table} WHERE "table" = {table_quoted}),
|
||||||
0
|
0
|
||||||
) + 1
|
) + 1
|
||||||
);
|
);
|
||||||
END;
|
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
|
BEGIN
|
||||||
INSERT OR REPLACE INTO [{counts_table}]
|
INSERT OR REPLACE INTO {counts_table}
|
||||||
VALUES (
|
VALUES (
|
||||||
{table_quoted},
|
{table_quoted},
|
||||||
COALESCE(
|
COALESCE(
|
||||||
(SELECT count FROM [{counts_table}] WHERE [table] = {table_quoted}),
|
(SELECT count FROM {counts_table} WHERE "table" = {table_quoted}),
|
||||||
0
|
0
|
||||||
) - 1
|
) - 1
|
||||||
);
|
);
|
||||||
END;
|
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()
|
.strip()
|
||||||
|
|
@ -2461,9 +2481,15 @@ class Table(Queryable):
|
||||||
create_counts_table=_COUNTS_TABLE_CREATE_SQL.format(
|
create_counts_table=_COUNTS_TABLE_CREATE_SQL.format(
|
||||||
self.db._counts_table_name
|
self.db._counts_table_name
|
||||||
),
|
),
|
||||||
counts_table=self.db._counts_table_name,
|
counts_table=quote_identifier(self.db._counts_table_name),
|
||||||
table=self.name,
|
table=quote_identifier(self.name),
|
||||||
table_quoted=self.db.quote(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:
|
with self.db.conn:
|
||||||
|
|
@ -2503,16 +2529,17 @@ class Table(Queryable):
|
||||||
create_fts_sql = (
|
create_fts_sql = (
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
CREATE VIRTUAL TABLE [{table}_fts] USING {fts_version} (
|
CREATE VIRTUAL TABLE {table_fts} USING {fts_version} (
|
||||||
{columns},{tokenize}
|
{columns},{tokenize}
|
||||||
content=[{table}]
|
content={table}
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.format(
|
||||||
table=self.name,
|
table=quote_identifier(self.name),
|
||||||
columns=", ".join("[{}]".format(c) for c in columns),
|
table_fts=quote_identifier(self.name + "_fts"),
|
||||||
|
columns=", ".join(quote_identifier(c) for c in columns),
|
||||||
fts_version=fts_version,
|
fts_version=fts_version,
|
||||||
tokenize="\n tokenize='{}',".format(tokenize) if tokenize else "",
|
tokenize="\n tokenize='{}',".format(tokenize) if tokenize else "",
|
||||||
)
|
)
|
||||||
|
|
@ -2539,27 +2566,34 @@ class Table(Queryable):
|
||||||
self.populate_fts(columns)
|
self.populate_fts(columns)
|
||||||
|
|
||||||
if create_triggers:
|
if create_triggers:
|
||||||
old_cols = ", ".join("old.[{}]".format(c) for c in columns)
|
old_cols = ", ".join("old.{}".format(quote_identifier(c)) for c in columns)
|
||||||
new_cols = ", ".join("new.[{}]".format(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 = (
|
triggers = (
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
CREATE TRIGGER [{table}_ai] AFTER INSERT ON [{table}] BEGIN
|
CREATE TRIGGER {table_ai} AFTER INSERT ON {table} BEGIN
|
||||||
INSERT INTO [{table}_fts] (rowid, {columns}) VALUES (new.rowid, {new_cols});
|
INSERT INTO {table_fts} (rowid, {columns}) VALUES (new.rowid, {new_cols});
|
||||||
END;
|
END;
|
||||||
CREATE TRIGGER [{table}_ad] AFTER DELETE ON [{table}] BEGIN
|
CREATE TRIGGER {table_ad} AFTER DELETE ON {table} BEGIN
|
||||||
INSERT INTO [{table}_fts] ([{table}_fts], rowid, {columns}) VALUES('delete', old.rowid, {old_cols});
|
INSERT INTO {table_fts} ({table_fts}, rowid, {columns}) VALUES('delete', old.rowid, {old_cols});
|
||||||
END;
|
END;
|
||||||
CREATE TRIGGER [{table}_au] AFTER UPDATE ON [{table}] BEGIN
|
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} ({table_fts}, rowid, {columns}) VALUES('delete', old.rowid, {old_cols});
|
||||||
INSERT INTO [{table}_fts] (rowid, {columns}) VALUES (new.rowid, {new_cols});
|
INSERT INTO {table_fts} (rowid, {columns}) VALUES (new.rowid, {new_cols});
|
||||||
END;
|
END;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.format(
|
||||||
table=self.name,
|
table=table,
|
||||||
columns=", ".join("[{}]".format(c) for c in columns),
|
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,
|
old_cols=old_cols,
|
||||||
new_cols=new_cols,
|
new_cols=new_cols,
|
||||||
)
|
)
|
||||||
|
|
@ -2574,16 +2608,19 @@ class Table(Queryable):
|
||||||
|
|
||||||
:param columns: Columns to populate the data for
|
:param columns: Columns to populate the data for
|
||||||
"""
|
"""
|
||||||
|
columns_quoted = ", ".join(quote_identifier(c) for c in columns)
|
||||||
sql = (
|
sql = (
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
INSERT INTO [{table}_fts] (rowid, {columns})
|
INSERT INTO {table_fts} (rowid, {columns})
|
||||||
SELECT rowid, {columns} FROM [{table}];
|
SELECT rowid, {columns} FROM {table};
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(
|
.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)
|
self.db.executescript(sql)
|
||||||
|
|
@ -2600,18 +2637,20 @@ class Table(Queryable):
|
||||||
"""
|
"""
|
||||||
SELECT name FROM sqlite_master
|
SELECT name FROM sqlite_master
|
||||||
WHERE type = 'trigger'
|
WHERE type = 'trigger'
|
||||||
AND sql LIKE '% INSERT INTO [{}]%'
|
AND (sql LIKE '% INSERT INTO [{}]%' OR sql LIKE '% INSERT INTO "{}"%')
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
.strip()
|
.strip()
|
||||||
.format(fts_table)
|
.format(fts_table, fts_table)
|
||||||
)
|
)
|
||||||
trigger_names = []
|
trigger_names = []
|
||||||
for row in self.db.execute(sql).fetchall():
|
for row in self.db.execute(sql).fetchall():
|
||||||
trigger_names.append(row[0])
|
trigger_names.append(row[0])
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
for trigger_name in trigger_names:
|
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
|
return self
|
||||||
|
|
||||||
def rebuild_fts(self):
|
def rebuild_fts(self):
|
||||||
|
|
@ -2621,8 +2660,8 @@ class Table(Queryable):
|
||||||
# Assume this is itself an FTS table
|
# Assume this is itself an FTS table
|
||||||
fts_table = self.name
|
fts_table = self.name
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"INSERT INTO [{table}]([{table}]) VALUES('rebuild');".format(
|
"INSERT INTO {table}({table}) VALUES('rebuild');".format(
|
||||||
table=fts_table
|
table=quote_identifier(fts_table)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
@ -2644,7 +2683,7 @@ class Table(Queryable):
|
||||||
"""
|
"""
|
||||||
).strip()
|
).strip()
|
||||||
args = {
|
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),
|
"like2": '%VIRTUAL TABLE%USING FTS%content="{}"%'.format(self.name),
|
||||||
"table": self.name,
|
"table": self.name,
|
||||||
}
|
}
|
||||||
|
|
@ -2660,9 +2699,9 @@ class Table(Queryable):
|
||||||
if fts_table is not None:
|
if fts_table is not None:
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO [{table}] ([{table}]) VALUES ("optimize");
|
INSERT INTO {table} ({table}) VALUES ("optimize");
|
||||||
""".strip().format(
|
""".strip().format(
|
||||||
table=fts_table
|
table=quote_identifier(fts_table)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
@ -2688,17 +2727,19 @@ class Table(Queryable):
|
||||||
"""
|
"""
|
||||||
# Pick names for table and rank column that don't clash
|
# Pick names for table and rank column that don't clash
|
||||||
original = "original_" if self.name == "original" else "original"
|
original = "original_" if self.name == "original" else "original"
|
||||||
|
original_quoted = quote_identifier(original)
|
||||||
columns_sql = "*"
|
columns_sql = "*"
|
||||||
columns_with_prefix_sql = "[{}].*".format(original)
|
columns_with_prefix_sql = "{}.*".format(original_quoted)
|
||||||
if columns:
|
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(
|
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()
|
fts_table = self.detect_fts()
|
||||||
assert fts_table, "Full-text search is not configured for table '{}'".format(
|
assert fts_table, "Full-text search is not configured for table '{}'".format(
|
||||||
self.name
|
self.name
|
||||||
)
|
)
|
||||||
|
fts_table_quoted = quote_identifier(fts_table)
|
||||||
virtual_table_using = self.db[fts_table].virtual_table_using
|
virtual_table_using = self.db[fts_table].virtual_table_using
|
||||||
sql = textwrap.dedent(
|
sql = textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
|
|
@ -2706,26 +2747,26 @@ class Table(Queryable):
|
||||||
select
|
select
|
||||||
rowid,
|
rowid,
|
||||||
{columns}
|
{columns}
|
||||||
from [{dbtable}]{where_clause}
|
from {dbtable}{where_clause}
|
||||||
)
|
)
|
||||||
select
|
select
|
||||||
{columns_with_prefix}
|
{columns_with_prefix}
|
||||||
from
|
from
|
||||||
[{original}]
|
{original}
|
||||||
join [{fts_table}] on [{original}].rowid = [{fts_table}].rowid
|
join {fts_table} on {original}.rowid = {fts_table}.rowid
|
||||||
where
|
where
|
||||||
[{fts_table}] match :query
|
{fts_table} match :query
|
||||||
order by
|
order by
|
||||||
{order_by}
|
{order_by}
|
||||||
{limit_offset}
|
{limit_offset}
|
||||||
"""
|
"""
|
||||||
).strip()
|
).strip()
|
||||||
if virtual_table_using == "FTS5":
|
if virtual_table_using == "FTS5":
|
||||||
rank_implementation = "[{}].rank".format(fts_table)
|
rank_implementation = "{}.rank".format(fts_table_quoted)
|
||||||
else:
|
else:
|
||||||
self.db.register_fts4_bm25()
|
self.db.register_fts4_bm25()
|
||||||
rank_implementation = "rank_bm25(matchinfo([{}], 'pcnalx'))".format(
|
rank_implementation = "rank_bm25(matchinfo({}, 'pcnalx'))".format(
|
||||||
fts_table
|
fts_table_quoted
|
||||||
)
|
)
|
||||||
if include_rank:
|
if include_rank:
|
||||||
columns_with_prefix_sql += ",\n " + rank_implementation + " rank"
|
columns_with_prefix_sql += ",\n " + rank_implementation + " rank"
|
||||||
|
|
@ -2735,12 +2776,12 @@ class Table(Queryable):
|
||||||
if offset is not None:
|
if offset is not None:
|
||||||
limit_offset += " offset {}".format(offset)
|
limit_offset += " offset {}".format(offset)
|
||||||
return sql.format(
|
return sql.format(
|
||||||
dbtable=self.name,
|
dbtable=quote_identifier(self.name),
|
||||||
where_clause="\n where {}".format(where) if where else "",
|
where_clause="\n where {}".format(where) if where else "",
|
||||||
original=original,
|
original=original_quoted,
|
||||||
columns=columns_sql,
|
columns=columns_sql,
|
||||||
columns_with_prefix=columns_with_prefix_sql,
|
columns_with_prefix=columns_with_prefix_sql,
|
||||||
fts_table=fts_table,
|
fts_table=fts_table_quoted,
|
||||||
order_by=order_by or rank_implementation,
|
order_by=order_by or rank_implementation,
|
||||||
limit_offset=limit_offset.strip(),
|
limit_offset=limit_offset.strip(),
|
||||||
).strip()
|
).strip()
|
||||||
|
|
@ -2808,9 +2849,9 @@ class Table(Queryable):
|
||||||
if not isinstance(pk_values, (list, tuple)):
|
if not isinstance(pk_values, (list, tuple)):
|
||||||
pk_values = [pk_values]
|
pk_values = [pk_values]
|
||||||
self.get(pk_values)
|
self.get(pk_values)
|
||||||
wheres = ["[{}] = ?".format(pk_name) for pk_name in self.pks]
|
wheres = ["{} = ?".format(quote_identifier(pk_name)) for pk_name in self.pks]
|
||||||
sql = "delete from [{table}] where {wheres}".format(
|
sql = "delete from {} where {wheres}".format(
|
||||||
table=self.name, wheres=" and ".join(wheres)
|
quote_identifier(self.name), wheres=" and ".join(wheres)
|
||||||
)
|
)
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
self.db.execute(sql, pk_values)
|
self.db.execute(sql, pk_values)
|
||||||
|
|
@ -2834,7 +2875,7 @@ class Table(Queryable):
|
||||||
"""
|
"""
|
||||||
if not self.exists():
|
if not self.exists():
|
||||||
return self
|
return self
|
||||||
sql = "delete from [{}]".format(self.name)
|
sql = "delete from {}".format(quote_identifier(self.name))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
sql += " where " + where
|
sql += " where " + where
|
||||||
self.db.execute(sql, where_args or [])
|
self.db.execute(sql, where_args or [])
|
||||||
|
|
@ -2873,14 +2914,17 @@ class Table(Queryable):
|
||||||
sets = []
|
sets = []
|
||||||
wheres = []
|
wheres = []
|
||||||
pks = self.pks
|
pks = self.pks
|
||||||
validate_column_names(updates.keys())
|
|
||||||
for key, value in updates.items():
|
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))
|
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)
|
args.extend(pk_values)
|
||||||
sql = "update [{table}] set {sets} where {wheres}".format(
|
sql = "update {} set {sets} where {wheres}".format(
|
||||||
table=self.name, sets=", ".join(sets), wheres=" and ".join(wheres)
|
quote_identifier(self.name),
|
||||||
|
sets=", ".join(sets),
|
||||||
|
wheres=" and ".join(wheres),
|
||||||
)
|
)
|
||||||
with self.db.conn:
|
with self.db.conn:
|
||||||
try:
|
try:
|
||||||
|
|
@ -2957,14 +3001,14 @@ class Table(Queryable):
|
||||||
if fn_name == "<lambda>":
|
if fn_name == "<lambda>":
|
||||||
fn_name = f"lambda_{abs(hash(fn))}"
|
fn_name = f"lambda_{abs(hash(fn))}"
|
||||||
self.db.register_function(convert_value, name=fn_name)
|
self.db.register_function(convert_value, name=fn_name)
|
||||||
sql = "update [{table}] set {sets}{where};".format(
|
sql = "update {} set {sets}{where};".format(
|
||||||
table=self.name,
|
quote_identifier(self.name),
|
||||||
sets=", ".join(
|
sets=", ".join(
|
||||||
[
|
[
|
||||||
"[{output_column}] = {fn_name}([{column}])".format(
|
"{} = {}({})".format(
|
||||||
output_column=output or column,
|
quote_identifier(output or column),
|
||||||
column=column,
|
fn_name,
|
||||||
fn_name=fn_name,
|
quote_identifier(column),
|
||||||
)
|
)
|
||||||
for column in columns
|
for column in columns
|
||||||
]
|
]
|
||||||
|
|
@ -2992,7 +3036,7 @@ class Table(Queryable):
|
||||||
) as bar:
|
) as bar:
|
||||||
for row in self.rows_where(
|
for row in self.rows_where(
|
||||||
select=", ".join(
|
select=", ".join(
|
||||||
"[{}]".format(column_name) for column_name in (pks + [column])
|
quote_identifier(column_name) for column_name in (pks + [column])
|
||||||
),
|
),
|
||||||
where=where,
|
where=where,
|
||||||
where_args=where_args,
|
where_args=where_args,
|
||||||
|
|
@ -3097,7 +3141,7 @@ class Table(Queryable):
|
||||||
record_values.append(value)
|
record_values.append(value)
|
||||||
values.append(record_values)
|
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)
|
placeholder_expr = ", ".join(conversions.get(c, "?") for c in all_columns)
|
||||||
row_placeholders_sql = ", ".join(f"({placeholder_expr})" for _ in values)
|
row_placeholders_sql = ", ".join(f"({placeholder_expr})" for _ in values)
|
||||||
flat_params = list(itertools.chain.from_iterable(values))
|
flat_params = list(itertools.chain.from_iterable(values))
|
||||||
|
|
@ -3105,7 +3149,7 @@ class Table(Queryable):
|
||||||
# replace=True mean INSERT OR REPLACE INTO
|
# replace=True mean INSERT OR REPLACE INTO
|
||||||
if replace:
|
if replace:
|
||||||
sql = (
|
sql = (
|
||||||
f"INSERT OR REPLACE INTO [{self.name}] "
|
f"INSERT OR REPLACE INTO {quote_identifier(self.name)} "
|
||||||
f"({columns_sql}) VALUES {row_placeholders_sql}"
|
f"({columns_sql}) VALUES {row_placeholders_sql}"
|
||||||
)
|
)
|
||||||
return [(sql, flat_params)]
|
return [(sql, flat_params)]
|
||||||
|
|
@ -3116,7 +3160,7 @@ class Table(Queryable):
|
||||||
if ignore:
|
if ignore:
|
||||||
or_ignore = " OR IGNORE"
|
or_ignore = " OR IGNORE"
|
||||||
sql = (
|
sql = (
|
||||||
f"INSERT{or_ignore} INTO [{self.name}] "
|
f"INSERT{or_ignore} INTO {quote_identifier(self.name)} "
|
||||||
f"({columns_sql}) VALUES {row_placeholders_sql}"
|
f"({columns_sql}) VALUES {row_placeholders_sql}"
|
||||||
)
|
)
|
||||||
return [(sql, flat_params)]
|
return [(sql, flat_params)]
|
||||||
|
|
@ -3124,26 +3168,27 @@ class Table(Queryable):
|
||||||
# Everything from here on is for upsert=True
|
# Everything from here on is for upsert=True
|
||||||
pk_cols = [pk] if isinstance(pk, str) else list(pk)
|
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]
|
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 self.db.supports_on_conflict and not self.db.use_old_upsert:
|
||||||
if non_pk_cols:
|
if non_pk_cols:
|
||||||
# DO UPDATE
|
# DO UPDATE
|
||||||
assignments = []
|
assignments = []
|
||||||
for c in non_pk_cols:
|
for c in non_pk_cols:
|
||||||
|
c_quoted = quote_identifier(c)
|
||||||
if c in conversions:
|
if c in conversions:
|
||||||
assignments.append(
|
assignments.append(
|
||||||
f"[{c}] = {conversions[c].replace('?', f'excluded.[{c}]')}"
|
f"{c_quoted} = {conversions[c].replace('?', f'excluded.{c_quoted}')}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assignments.append(f"[{c}] = excluded.[{c}]")
|
assignments.append(f"{c_quoted} = excluded.{c_quoted}")
|
||||||
do_clause = "DO UPDATE SET " + ", ".join(assignments)
|
do_clause = "DO UPDATE SET " + ", ".join(assignments)
|
||||||
else:
|
else:
|
||||||
# All columns are in the PK – nothing to update.
|
# All columns are in the PK – nothing to update.
|
||||||
do_clause = "DO NOTHING"
|
do_clause = "DO NOTHING"
|
||||||
|
|
||||||
sql = (
|
sql = (
|
||||||
f"INSERT INTO [{self.name}] ({columns_sql}) "
|
f"INSERT INTO {quote_identifier(self.name)} ({columns_sql}) "
|
||||||
f"VALUES {row_placeholders_sql} "
|
f"VALUES {row_placeholders_sql} "
|
||||||
f"ON CONFLICT({conflict_sql}) {do_clause}"
|
f"ON CONFLICT({conflict_sql}) {do_clause}"
|
||||||
)
|
)
|
||||||
|
|
@ -3164,24 +3209,30 @@ class Table(Queryable):
|
||||||
# them since it ignores the resulting integrity errors
|
# them since it ignores the resulting integrity errors
|
||||||
if not_null:
|
if not_null:
|
||||||
placeholders.extend(not_null)
|
placeholders.extend(not_null)
|
||||||
sql = "INSERT OR IGNORE INTO [{table}]({cols}) VALUES({placeholders});".format(
|
sql = (
|
||||||
table=self.name,
|
"INSERT OR IGNORE INTO {table}({cols}) VALUES({placeholders});".format(
|
||||||
cols=", ".join(["[{}]".format(p) for p in placeholders]),
|
table=quote_identifier(self.name),
|
||||||
placeholders=", ".join(["?" for p in placeholders]),
|
cols=", ".join([quote_identifier(p) for p in placeholders]),
|
||||||
|
placeholders=", ".join(["?" for p in placeholders]),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
queries_and_params.append(
|
queries_and_params.append(
|
||||||
(sql, [record[col] for col in pks] + ["" for _ in (not_null or [])])
|
(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]
|
set_cols = [col for col in all_columns if col not in pks]
|
||||||
if set_cols:
|
if set_cols:
|
||||||
sql2 = "UPDATE [{table}] SET {pairs} WHERE {wheres}".format(
|
sql2 = "UPDATE {} SET {pairs} WHERE {wheres}".format(
|
||||||
table=self.name,
|
quote_identifier(self.name),
|
||||||
pairs=", ".join(
|
pairs=", ".join(
|
||||||
"[{}] = {}".format(col, conversions.get(col, "?"))
|
"{} = {}".format(
|
||||||
|
quote_identifier(col), conversions.get(col, "?")
|
||||||
|
)
|
||||||
for col in set_cols
|
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(
|
queries_and_params.append(
|
||||||
(
|
(
|
||||||
|
|
@ -3452,9 +3503,6 @@ class Table(Queryable):
|
||||||
else:
|
else:
|
||||||
# Dict mode: traditional behavior
|
# Dict mode: traditional behavior
|
||||||
records_iter = itertools.chain([first_record], records_iter)
|
records_iter = itertools.chain([first_record], records_iter)
|
||||||
records_iter = fix_square_braces(
|
|
||||||
cast(Iterable[Dict[str, Any]], records_iter)
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
first_record = next(records_iter)
|
first_record = next(records_iter)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
|
|
@ -3469,7 +3517,7 @@ class Table(Queryable):
|
||||||
self.last_rowid = None
|
self.last_rowid = None
|
||||||
self.last_pk = None
|
self.last_pk = None
|
||||||
if truncate and self.exists():
|
if truncate and self.exists():
|
||||||
self.db.execute("DELETE FROM [{}];".format(self.name))
|
self.db.execute("DELETE FROM {};".format(quote_identifier(self.name)))
|
||||||
result = None
|
result = None
|
||||||
for chunk in chunks(itertools.chain([first_record], records_iter), batch_size):
|
for chunk in chunks(itertools.chain([first_record], records_iter), batch_size):
|
||||||
chunk = list(chunk)
|
chunk = list(chunk)
|
||||||
|
|
@ -3724,7 +3772,9 @@ class Table(Queryable):
|
||||||
unique_column_sets = [set(i.columns) for i in self.indexes]
|
unique_column_sets = [set(i.columns) for i in self.indexes]
|
||||||
if set(lookup_values.keys()) not in unique_column_sets:
|
if set(lookup_values.keys()) not in unique_column_sets:
|
||||||
self.create_index(lookup_values.keys(), unique=True)
|
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(
|
rows = list(
|
||||||
self.rows_where(
|
self.rows_where(
|
||||||
" and ".join(wheres), [value for _, value in lookup_values.items()]
|
" and ".join(wheres), [value for _, value in lookup_values.items()]
|
||||||
|
|
@ -3887,20 +3937,24 @@ class Table(Queryable):
|
||||||
value = value[:value_truncate] + "..."
|
value = value[:value_truncate] + "..."
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
table_quoted = quote_identifier(table)
|
||||||
|
column_quoted = quote_identifier(column)
|
||||||
num_null = db.execute(
|
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]
|
).fetchone()[0]
|
||||||
num_blank = db.execute(
|
num_blank = db.execute(
|
||||||
"select count(*) from [{}] where [{}] = ''".format(table, column)
|
"select count(*) from {} where {} = ''".format(table_quoted, column_quoted)
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
num_distinct = db.execute(
|
num_distinct = db.execute(
|
||||||
"select count(distinct [{}]) from [{}]".format(column, table)
|
"select count(distinct {}) from {}".format(column_quoted, table_quoted)
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
most_common_results = None
|
most_common_results = None
|
||||||
least_common_results = None
|
least_common_results = None
|
||||||
if num_distinct == 1:
|
if num_distinct == 1:
|
||||||
value = db.execute(
|
value = db.execute(
|
||||||
"select [{}] from [{}] limit 1".format(column, table)
|
"select {} from {} limit 1".format(column_quoted, table_quoted)
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
most_common_results = [(truncate(value), total_rows)]
|
most_common_results = [(truncate(value), total_rows)]
|
||||||
elif num_distinct != total_rows:
|
elif num_distinct != total_rows:
|
||||||
|
|
@ -3912,8 +3966,12 @@ class Table(Queryable):
|
||||||
most_common_results = [
|
most_common_results = [
|
||||||
(truncate(r[0]), r[1])
|
(truncate(r[0]), r[1])
|
||||||
for r in db.execute(
|
for r in db.execute(
|
||||||
"select [{}], count(*) from [{}] group by [{}] order by count(*) desc, [{}] limit {}".format(
|
"select {}, count(*) from {} group by {} order by count(*) desc, {} limit {}".format(
|
||||||
column, table, column, column, common_limit
|
column_quoted,
|
||||||
|
table_quoted,
|
||||||
|
column_quoted,
|
||||||
|
column_quoted,
|
||||||
|
common_limit,
|
||||||
)
|
)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
]
|
]
|
||||||
|
|
@ -3926,8 +3984,12 @@ class Table(Queryable):
|
||||||
least_common_results = [
|
least_common_results = [
|
||||||
(truncate(r[0]), r[1])
|
(truncate(r[0]), r[1])
|
||||||
for r in db.execute(
|
for r in db.execute(
|
||||||
"select [{}], count(*) from [{}] group by [{}] order by count(*), [{}] desc limit {}".format(
|
"select {}, count(*) from {} group by {} order by count(*), {} desc limit {}".format(
|
||||||
column, table, column, column, common_limit
|
column_quoted,
|
||||||
|
table_quoted,
|
||||||
|
column_quoted,
|
||||||
|
column_quoted,
|
||||||
|
common_limit,
|
||||||
)
|
)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
]
|
]
|
||||||
|
|
@ -4045,7 +4107,7 @@ class View(Queryable):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.db.execute("DROP VIEW [{}]".format(self.name))
|
self.db.execute("DROP VIEW {}".format(quote_identifier(self.name)))
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
if not ignore:
|
if not ignore:
|
||||||
raise
|
raise
|
||||||
|
|
@ -4082,25 +4144,6 @@ def resolve_extracts(
|
||||||
return 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):
|
def _decode_default_value(value):
|
||||||
if value.startswith("'") and value.endswith("'"):
|
if value.startswith("'") and value.endswith("'"):
|
||||||
# It's a string
|
# It's a string
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,8 @@ def test_views(db_path):
|
||||||
result = CliRunner().invoke(cli.cli, ["views", db_path, "--table", "--schema"])
|
result = CliRunner().invoke(cli.cli, ["views", db_path, "--table", "--schema"])
|
||||||
assert (
|
assert (
|
||||||
"view schema\n"
|
"view schema\n"
|
||||||
"------ --------------------------------------------\n"
|
"------ ----------------------------------------------\n"
|
||||||
"hello CREATE VIEW hello AS select sqlite_version()"
|
'hello CREATE VIEW "hello" AS select sqlite_version()'
|
||||||
) == result.output.strip()
|
) == result.output.strip()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -132,7 +132,7 @@ def test_tables_schema(db_path):
|
||||||
assert (
|
assert (
|
||||||
'[{"table": "Gosh", "schema": "CREATE TABLE Gosh (c1 text, c2 text, c3 text)"},\n'
|
'[{"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": "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()
|
) == result.output.strip()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -264,38 +264,38 @@ def test_create_index_desc(db_path):
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert (
|
assert (
|
||||||
db.execute("select sql from sqlite_master where type='index'").fetchone()[0]
|
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(
|
@pytest.mark.parametrize(
|
||||||
"col_name,col_type,expected_schema",
|
"col_name,col_type,expected_schema",
|
||||||
(
|
(
|
||||||
("text", "TEXT", "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)'),
|
||||||
("text", "STR", "CREATE TABLE [dogs] (\n [name] TEXT\n, [text] TEXT)"),
|
("text", "STR", 'CREATE TABLE "dogs" (\n "name" TEXT\n, "text" TEXT)'),
|
||||||
(
|
(
|
||||||
"integer",
|
"integer",
|
||||||
"INTEGER",
|
"INTEGER",
|
||||||
"CREATE TABLE [dogs] (\n [name] TEXT\n, [integer] INTEGER)",
|
'CREATE TABLE "dogs" (\n "name" TEXT\n, "integer" INTEGER)',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"integer",
|
"integer",
|
||||||
"int",
|
"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)"),
|
("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", "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)'),
|
||||||
("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)"),
|
("default", None, 'CREATE TABLE "dogs" (\n "name" TEXT\n, "default" TEXT)'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_add_column(db_path, col_name, col_type, expected_schema):
|
def test_add_column(db_path, col_name, col_type, expected_schema):
|
||||||
db = Database(db_path)
|
db = Database(db_path)
|
||||||
db.create_table("dogs", {"name": str})
|
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]
|
args = ["add-column", db_path, "dogs", col_name]
|
||||||
if col_type is not None:
|
if col_type is not None:
|
||||||
args.append(col_type)
|
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):
|
def test_add_column_not_null_default(db_path):
|
||||||
db = Database(db_path)
|
db = Database(db_path)
|
||||||
db.create_table("dogs", {"name": str})
|
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 = [
|
args = [
|
||||||
"add-column",
|
"add-column",
|
||||||
db_path,
|
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 CliRunner().invoke(cli.cli, args).exit_code == 0
|
||||||
assert db["dogs"].schema == (
|
assert db["dogs"].schema == (
|
||||||
"CREATE TABLE [dogs] (\n"
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
", [nickname] TEXT NOT NULL DEFAULT 'dogs''dawg')"
|
", \"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 result.exit_code == 0, result.output
|
||||||
assert db["books"].schema == (
|
assert db["books"].schema == (
|
||||||
'CREATE TABLE "books" (\n'
|
'CREATE TABLE "books" (\n'
|
||||||
" [title] TEXT,\n"
|
' "title" TEXT,\n'
|
||||||
" [author_id] INTEGER REFERENCES [authors]([id])\n"
|
' "author_id" INTEGER REFERENCES "authors"("id")\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
# Try it again with a custom --fk-col
|
# 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 result.exit_code == 0, result.output
|
||||||
assert db["books"].schema == (
|
assert db["books"].schema == (
|
||||||
'CREATE TABLE "books" (\n'
|
'CREATE TABLE "books" (\n'
|
||||||
" [title] TEXT,\n"
|
' "title" TEXT,\n'
|
||||||
" [author_id] INTEGER REFERENCES [authors]([id]),\n"
|
' "author_id" INTEGER REFERENCES "authors"("id"),\n'
|
||||||
" [author_name_ref] TEXT REFERENCES [authors]([name])\n"
|
' "author_name_ref" TEXT REFERENCES "authors"("name")\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
# Throw an error if the --fk table does not exist
|
# 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()
|
assert "http://example.com_fts" == db["http://example.com"].detect_fts()
|
||||||
# Check tokenize was set to porter
|
# Check tokenize was set to porter
|
||||||
assert (
|
assert (
|
||||||
"CREATE VIRTUAL TABLE [http://example.com_fts] USING FTS4 (\n"
|
'CREATE VIRTUAL TABLE "http://example.com_fts" USING FTS4 (\n'
|
||||||
" [c1],\n"
|
' "c1",\n'
|
||||||
" tokenize='porter',\n"
|
" tokenize='porter',\n"
|
||||||
" content=[http://example.com]"
|
' content="http://example.com"'
|
||||||
"\n)"
|
"\n)"
|
||||||
) == db["http://example.com_fts"].schema
|
) == db["http://example.com_fts"].schema
|
||||||
db["http://example.com"].drop()
|
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"]
|
cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"]
|
||||||
)
|
)
|
||||||
assert result2.exit_code == 1
|
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
|
# This should work
|
||||||
result3 = CliRunner().invoke(
|
result3 = CliRunner().invoke(
|
||||||
|
|
@ -1139,7 +1139,7 @@ def test_upsert_alter(db_path, tmpdir):
|
||||||
"age",
|
"age",
|
||||||
"integer",
|
"integer",
|
||||||
],
|
],
|
||||||
("CREATE TABLE [t] (\n [name] TEXT,\n [age] INTEGER\n)"),
|
('CREATE TABLE "t" (\n "name" TEXT,\n "age" INTEGER\n)'),
|
||||||
),
|
),
|
||||||
# All types:
|
# All types:
|
||||||
(
|
(
|
||||||
|
|
@ -1158,31 +1158,31 @@ def test_upsert_alter(db_path, tmpdir):
|
||||||
"id",
|
"id",
|
||||||
],
|
],
|
||||||
(
|
(
|
||||||
"CREATE TABLE [t] (\n"
|
'CREATE TABLE "t" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [age] INTEGER,\n"
|
' "age" INTEGER,\n'
|
||||||
" [weight] FLOAT,\n"
|
' "weight" FLOAT,\n'
|
||||||
" [thumbnail] BLOB\n"
|
' "thumbnail" BLOB\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
# Not null:
|
# Not null:
|
||||||
(
|
(
|
||||||
["name", "text", "--not-null", "name"],
|
["name", "text", "--not-null", "name"],
|
||||||
("CREATE TABLE [t] (\n" " [name] TEXT NOT NULL\n" ")"),
|
('CREATE TABLE "t" (\n' ' "name" TEXT NOT NULL\n' ")"),
|
||||||
),
|
),
|
||||||
# Default:
|
# Default:
|
||||||
(
|
(
|
||||||
["age", "integer", "--default", "age", "3"],
|
["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
|
# Compound primary key
|
||||||
(
|
(
|
||||||
["category", "text", "name", "text", "--pk", "category", "--pk", "name"],
|
["category", "text", "name", "text", "--pk", "category", "--pk", "name"],
|
||||||
(
|
(
|
||||||
"CREATE TABLE [t] (\n [category] TEXT,\n [name] TEXT,\n"
|
'CREATE TABLE "t" (\n "category" TEXT,\n "name" TEXT,\n'
|
||||||
" PRIMARY KEY ([category], [name])\n)"
|
' PRIMARY KEY ("category", "name")\n)'
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -1233,16 +1233,16 @@ def test_create_table_foreign_key():
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
db = Database("books.db")
|
db = Database("books.db")
|
||||||
assert (
|
assert (
|
||||||
"CREATE TABLE [authors] (\n"
|
'CREATE TABLE "authors" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
) == db["authors"].schema
|
) == db["authors"].schema
|
||||||
assert (
|
assert (
|
||||||
"CREATE TABLE [books] (\n"
|
'CREATE TABLE "books" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [title] TEXT,\n"
|
' "title" TEXT,\n'
|
||||||
" [author_id] INTEGER REFERENCES [authors]([id])\n"
|
' "author_id" INTEGER REFERENCES "authors"("id")\n'
|
||||||
")"
|
")"
|
||||||
) == db["books"].schema
|
) == db["books"].schema
|
||||||
|
|
||||||
|
|
@ -1271,7 +1271,7 @@ def test_create_table_ignore():
|
||||||
cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--ignore"]
|
cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--ignore"]
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
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():
|
def test_create_table_replace():
|
||||||
|
|
@ -1283,7 +1283,7 @@ def test_create_table_replace():
|
||||||
cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--replace"]
|
cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--replace"]
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
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():
|
def test_create_view():
|
||||||
|
|
@ -1294,7 +1294,9 @@ def test_create_view():
|
||||||
cli.cli, ["create-view", "test.db", "version", "select sqlite_version()"]
|
cli.cli, ["create-view", "test.db", "version", "select sqlite_version()"]
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
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():
|
def test_create_view_error_if_view_exists():
|
||||||
|
|
@ -1329,7 +1331,8 @@ def test_create_view_ignore():
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert (
|
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 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():
|
def test_drop_table():
|
||||||
|
|
@ -1537,9 +1542,9 @@ def test_add_foreign_keys(db_path):
|
||||||
[],
|
[],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [age] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1547,9 +1552,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--type", "age", "text"],
|
["--type", "age", "text"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [age] TEXT NOT NULL DEFAULT '1',\n"
|
" \"age\" TEXT NOT NULL DEFAULT '1',\n"
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1557,8 +1562,8 @@ def test_add_foreign_keys(db_path):
|
||||||
["--drop", "age"],
|
["--drop", "age"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1566,9 +1571,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--rename", "age", "age2", "--rename", "id", "pk"],
|
["--rename", "age", "age2", "--rename", "id", "pk"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [pk] INTEGER PRIMARY KEY,\n"
|
' "pk" INTEGER PRIMARY KEY,\n'
|
||||||
" [age2] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age2\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1576,9 +1581,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--not-null", "name"],
|
["--not-null", "name"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [age] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [name] TEXT NOT NULL\n"
|
' "name" TEXT NOT NULL\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1586,9 +1591,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--not-null-false", "age"],
|
["--not-null-false", "age"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [age] INTEGER DEFAULT '1',\n"
|
" \"age\" INTEGER DEFAULT '1',\n"
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1596,9 +1601,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--pk", "name"],
|
["--pk", "name"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [age] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [name] TEXT PRIMARY KEY\n"
|
' "name" TEXT PRIMARY KEY\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1606,9 +1611,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--pk-none"],
|
["--pk-none"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [age] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1616,9 +1621,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--default", "name", "Turnip"],
|
["--default", "name", "Turnip"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [age] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [name] TEXT DEFAULT 'Turnip'\n"
|
" \"name\" TEXT DEFAULT 'Turnip'\n"
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1626,9 +1631,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["--default-none", "age"],
|
["--default-none", "age"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [age] INTEGER NOT NULL,\n"
|
' "age" INTEGER NOT NULL,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1636,9 +1641,9 @@ def test_add_foreign_keys(db_path):
|
||||||
["-o", "name", "--column-order", "age", "-o", "id"],
|
["-o", "name", "--column-order", "age", "-o", "id"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [age] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [id] INTEGER PRIMARY KEY\n"
|
' "id" INTEGER PRIMARY KEY\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1667,11 +1672,11 @@ def test_transform(db_path, args, expected_schema):
|
||||||
["--drop-foreign-key", "country"],
|
["--drop-foreign-key", "country"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "places" (\n'
|
'CREATE TABLE "places" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [country] INTEGER,\n"
|
' "country" INTEGER,\n'
|
||||||
" [city] INTEGER REFERENCES [city]([id]),\n"
|
' "city" INTEGER REFERENCES "city"("id"),\n'
|
||||||
" [continent] INTEGER\n"
|
' "continent" INTEGER\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1679,11 +1684,11 @@ def test_transform(db_path, args, expected_schema):
|
||||||
["--drop-foreign-key", "country", "--drop-foreign-key", "city"],
|
["--drop-foreign-key", "country", "--drop-foreign-key", "city"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "places" (\n'
|
'CREATE TABLE "places" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [country] INTEGER,\n"
|
' "country" INTEGER,\n'
|
||||||
" [city] INTEGER,\n"
|
' "city" INTEGER,\n'
|
||||||
" [continent] INTEGER\n"
|
' "continent" INTEGER\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1691,11 +1696,11 @@ def test_transform(db_path, args, expected_schema):
|
||||||
["--add-foreign-key", "continent", "continent", "id"],
|
["--add-foreign-key", "continent", "continent", "id"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "places" (\n'
|
'CREATE TABLE "places" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [country] INTEGER REFERENCES [country]([id]),\n"
|
' "country" INTEGER REFERENCES "country"("id"),\n'
|
||||||
" [city] INTEGER REFERENCES [city]([id]),\n"
|
' "city" INTEGER REFERENCES "city"("id"),\n'
|
||||||
" [continent] INTEGER REFERENCES [continent]([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 = (
|
_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'
|
'CREATE TABLE "trees" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [address] TEXT,\n"
|
' "address" TEXT,\n'
|
||||||
" [species_id] INTEGER REFERENCES [species]([id])\n"
|
' "species_id" INTEGER REFERENCES "species"("id")\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
_common_other_schema,
|
_common_other_schema,
|
||||||
|
|
@ -1756,20 +1761,20 @@ _common_other_schema = (
|
||||||
["--table", "custom_table"],
|
["--table", "custom_table"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "trees" (\n'
|
'CREATE TABLE "trees" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [address] TEXT,\n"
|
' "address" TEXT,\n'
|
||||||
" [custom_table_id] INTEGER REFERENCES [custom_table]([id])\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"],
|
["--fk-column", "custom_fk"],
|
||||||
(
|
(
|
||||||
'CREATE TABLE "trees" (\n'
|
'CREATE TABLE "trees" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [address] TEXT,\n"
|
' "address" TEXT,\n'
|
||||||
" [custom_fk] INTEGER REFERENCES [species]([id])\n"
|
' "custom_fk" INTEGER REFERENCES "species"("id")\n'
|
||||||
")"
|
")"
|
||||||
),
|
),
|
||||||
_common_other_schema,
|
_common_other_schema,
|
||||||
|
|
@ -1777,11 +1782,11 @@ _common_other_schema = (
|
||||||
(
|
(
|
||||||
["--rename", "name", "name2"],
|
["--rename", "name", "name2"],
|
||||||
'CREATE TABLE "trees" (\n'
|
'CREATE TABLE "trees" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [address] TEXT,\n"
|
' "address" TEXT,\n'
|
||||||
" [species_id] INTEGER REFERENCES [species]([id])\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"
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
");\n"
|
");\n"
|
||||||
"CREATE TABLE [chickens] (\n"
|
'CREATE TABLE "chickens" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [breed] TEXT\n"
|
' "breed" TEXT\n'
|
||||||
");\n"
|
");\n"
|
||||||
"CREATE INDEX [idx_chickens_breed]\n"
|
'CREATE INDEX "idx_chickens_breed"\n'
|
||||||
" ON [chickens] ([breed]);\n"
|
' ON "chickens" ("breed");\n'
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
["dogs"],
|
["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"],
|
["chickens", "dogs"],
|
||||||
(
|
(
|
||||||
"CREATE TABLE [chickens] (\n"
|
'CREATE TABLE "chickens" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [breed] TEXT\n"
|
' "breed" TEXT\n'
|
||||||
")\n"
|
")\n"
|
||||||
"CREATE TABLE [dogs] (\n"
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
")\n"
|
")\n"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -2127,10 +2132,10 @@ def test_import_no_headers(tmpdir, args, tsv):
|
||||||
db = Database(db_path)
|
db = Database(db_path)
|
||||||
schema = db["creatures"].schema
|
schema = db["creatures"].schema
|
||||||
assert schema == (
|
assert schema == (
|
||||||
"CREATE TABLE [creatures] (\n"
|
'CREATE TABLE "creatures" (\n'
|
||||||
" [untitled_1] TEXT,\n"
|
' "untitled_1" TEXT,\n'
|
||||||
" [untitled_2] TEXT,\n"
|
' "untitled_2" TEXT,\n'
|
||||||
" [untitled_3] TEXT\n"
|
' "untitled_3" TEXT\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
rows = list(db["creatures"].rows)
|
rows = list(db["creatures"].rows)
|
||||||
|
|
@ -2182,8 +2187,8 @@ def test_csv_insert_bom(tmpdir):
|
||||||
db = Database(db_path)
|
db = Database(db_path)
|
||||||
tables = db.execute("select name, sql from sqlite_master").fetchall()
|
tables = db.execute("select name, sql from sqlite_master").fetchall()
|
||||||
assert tables == [
|
assert tables == [
|
||||||
("broken", "CREATE TABLE [broken] (\n [\ufeffname] 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)"),
|
("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.exit_code == 1
|
||||||
assert result.output == (
|
assert result.output == (
|
||||||
"Error: Python int too large to convert to SQLite INTEGER\n\n"
|
"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"
|
"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},
|
{"id": 4, "is_str": None, "is_float": None, "is_int": None, "is_bytes": None},
|
||||||
]
|
]
|
||||||
assert db["rows"].schema == (
|
assert db["rows"].schema == (
|
||||||
"CREATE TABLE [rows] (\n"
|
'CREATE TABLE "rows" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY\n"
|
' "id" INTEGER PRIMARY KEY\n'
|
||||||
", [is_str] TEXT, [is_float] FLOAT, [is_int] INTEGER, [is_bytes] BLOB)"
|
', "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 dogs == list(db.query("select * from dogs order by breed, id"))
|
||||||
assert {"breed", "id"} == set(db["dogs"].pks)
|
assert {"breed", "id"} == set(db["dogs"].pks)
|
||||||
assert (
|
assert (
|
||||||
"CREATE TABLE [dogs] (\n"
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [breed] TEXT,\n"
|
' "breed" TEXT,\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [age] INTEGER,\n"
|
' "age" INTEGER,\n'
|
||||||
" PRIMARY KEY ([id], [breed])\n"
|
' PRIMARY KEY ("id", "breed")\n'
|
||||||
")"
|
")"
|
||||||
) == db["dogs"].schema
|
) == db["dogs"].schema
|
||||||
|
|
||||||
|
|
@ -154,11 +154,11 @@ def test_insert_not_null_default(db_path, tmpdir):
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
db = Database(db_path)
|
db = Database(db_path)
|
||||||
assert (
|
assert (
|
||||||
"CREATE TABLE [dogs] (\n"
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT NOT NULL,\n"
|
' "name" TEXT NOT NULL,\n'
|
||||||
" [age] INTEGER NOT NULL DEFAULT '1',\n"
|
" \"age\" INTEGER NOT NULL DEFAULT '1',\n"
|
||||||
" [score] INTEGER DEFAULT '5'\n)"
|
" \"score\" INTEGER DEFAULT '5'\n)"
|
||||||
) == db["dogs"].schema
|
) == db["dogs"].schema
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -466,7 +466,7 @@ def test_insert_convert_text(db_path):
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
db = Database(db_path)
|
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"}]
|
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
|
assert result.exit_code == 0, result.output
|
||||||
db = Database(db_path)
|
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"}]
|
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
|
assert result.exit_code == 0, result.output
|
||||||
db = Database(db_path)
|
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"}]
|
assert rows == [{"line": "THIS IS TEXT"}, {"line": "WILL BE UPPER NOW"}]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,13 +167,13 @@ def test_memory_dump(extra_args):
|
||||||
expected = (
|
expected = (
|
||||||
"BEGIN TRANSACTION;\n"
|
"BEGIN TRANSACTION;\n"
|
||||||
'CREATE TABLE IF NOT EXISTS "stdin" (\n'
|
'CREATE TABLE IF NOT EXISTS "stdin" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
");\n"
|
");\n"
|
||||||
"INSERT INTO \"stdin\" VALUES(1,'Cleo');\n"
|
"INSERT INTO \"stdin\" VALUES(1,'Cleo');\n"
|
||||||
"INSERT INTO \"stdin\" VALUES(2,'Bants');\n"
|
"INSERT INTO \"stdin\" VALUES(2,'Bants');\n"
|
||||||
"CREATE VIEW t1 AS select * from [stdin];\n"
|
'CREATE VIEW "t1" AS select * from "stdin";\n'
|
||||||
"CREATE VIEW t AS select * from [stdin];\n"
|
'CREATE VIEW "t" AS select * from "stdin";\n'
|
||||||
"COMMIT;"
|
"COMMIT;"
|
||||||
)
|
)
|
||||||
# Using sqlite-dump it won't have IF NOT EXISTS
|
# 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.exit_code == 0
|
||||||
assert result.output.strip() == (
|
assert result.output.strip() == (
|
||||||
'CREATE TABLE "stdin" (\n'
|
'CREATE TABLE "stdin" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
");\n"
|
");\n"
|
||||||
"CREATE VIEW t1 AS select * from [stdin];\n"
|
'CREATE VIEW "t1" AS select * from "stdin";\n'
|
||||||
"CREATE VIEW t AS select * from [stdin];"
|
'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.exit_code == 0
|
||||||
assert result.output == (
|
assert result.output == (
|
||||||
'CREATE TABLE "data" (\n'
|
'CREATE TABLE "data" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
");\n"
|
");\n"
|
||||||
"CREATE VIEW t1 AS select * from [data];\n"
|
'CREATE VIEW "t1" AS select * from "data";\n'
|
||||||
"CREATE VIEW t AS select * from [data];\n"
|
'CREATE VIEW "t" AS select * from "data";\n'
|
||||||
'CREATE TABLE "data_2" (\n'
|
'CREATE TABLE "data_2" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT\n"
|
' "name" TEXT\n'
|
||||||
");\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": "datetime_col", "type": "TEXT"},
|
||||||
] == [{"name": col.name, "type": col.type} for col in table.columns]
|
] == [{"name": col.name, "type": col.type} for col in table.columns]
|
||||||
assert (
|
assert (
|
||||||
"CREATE TABLE [test_table] (\n"
|
'CREATE TABLE "test_table" (\n'
|
||||||
" [text_col] TEXT,\n"
|
' "text_col" TEXT,\n'
|
||||||
" [float_col] FLOAT,\n"
|
' "float_col" FLOAT,\n'
|
||||||
" [int_col] INTEGER,\n"
|
' "int_col" INTEGER,\n'
|
||||||
" [bool_col] INTEGER,\n"
|
' "bool_col" INTEGER,\n'
|
||||||
" [bytes_col] BLOB,\n"
|
' "bytes_col" BLOB,\n'
|
||||||
" [datetime_col] TEXT\n"
|
' "datetime_col" TEXT\n'
|
||||||
")"
|
")"
|
||||||
) == table.schema
|
) == 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")
|
"test_table", {"id1": str, "id2": str, "value": int}, pk=("id1", "id2")
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
"CREATE TABLE [test_table] (\n"
|
'CREATE TABLE "test_table" (\n'
|
||||||
" [id1] TEXT,\n"
|
' "id1" TEXT,\n'
|
||||||
" [id2] TEXT,\n"
|
' "id2" TEXT,\n'
|
||||||
" [value] INTEGER,\n"
|
' "value" INTEGER,\n'
|
||||||
" PRIMARY KEY ([id1], [id2])\n"
|
' PRIMARY KEY ("id1", "id2")\n'
|
||||||
")"
|
")"
|
||||||
) == table.schema
|
) == table.schema
|
||||||
assert ["id1", "id2"] == table.pks
|
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):
|
def test_create_table_with_single_primary_key(fresh_db, pk):
|
||||||
fresh_db["foo"].insert({"id": 1}, pk=pk)
|
fresh_db["foo"].insert({"id": 1}, pk=pk)
|
||||||
assert (
|
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):
|
def test_create_table_with_special_column_characters(fresh_db):
|
||||||
with pytest.raises(AssertionError):
|
# With double-quote escaping, columns with special characters are now valid
|
||||||
fresh_db.create_table("players", {"name[foo]": str})
|
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):
|
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
|
{"name": col.name, "type": col.type} for col in table.columns
|
||||||
]
|
]
|
||||||
assert (
|
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
|
) == 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
|
{"name": col.name, "type": col.type} for col in table.columns
|
||||||
]
|
]
|
||||||
assert (
|
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
|
) == table.schema
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -145,7 +149,7 @@ def test_create_table_with_not_null(fresh_db):
|
||||||
[{"name": "memoryview", "type": "BLOB"}],
|
[{"name": "memoryview", "type": "BLOB"}],
|
||||||
),
|
),
|
||||||
({"uuid": uuid.uuid4()}, [{"name": "uuid", "type": "TEXT"}]),
|
({"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)},
|
{"timedelta": datetime.timedelta(hours=1)},
|
||||||
[{"name": "timedelta", "type": "TEXT"}],
|
[{"name": "timedelta", "type": "TEXT"}],
|
||||||
|
|
@ -307,9 +311,9 @@ def test_self_referential_foreign_key(fresh_db):
|
||||||
foreign_keys=(("ref", "test_table", "id"),),
|
foreign_keys=(("ref", "test_table", "id"),),
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
"CREATE TABLE [test_table] (\n"
|
'CREATE TABLE "test_table" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [ref] INTEGER REFERENCES [test_table]([id])\n"
|
' "ref" INTEGER REFERENCES "test_table"("id")\n'
|
||||||
")"
|
")"
|
||||||
) == table.schema
|
) == table.schema
|
||||||
|
|
||||||
|
|
@ -340,58 +344,58 @@ def test_create_error_if_invalid_self_referential_foreign_keys(fresh_db):
|
||||||
"nickname",
|
"nickname",
|
||||||
str,
|
str,
|
||||||
None,
|
None,
|
||||||
"CREATE TABLE [dogs] (\n [name] TEXT\n, [nickname] TEXT)",
|
'CREATE TABLE "dogs" (\n "name" TEXT\n, "nickname" TEXT)',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"dob",
|
"dob",
|
||||||
datetime.date,
|
datetime.date,
|
||||||
None,
|
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",
|
"weight",
|
||||||
float,
|
float,
|
||||||
None,
|
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",
|
||||||
"INTEGER",
|
"INTEGER",
|
||||||
None,
|
None,
|
||||||
"CREATE TABLE [dogs] (\n [name] TEXT\n, [integer] INTEGER)",
|
'CREATE TABLE "dogs" (\n "name" TEXT\n, "integer" INTEGER)',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"float",
|
"float",
|
||||||
"FLOAT",
|
"FLOAT",
|
||||||
None,
|
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",
|
"default_str",
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
"CREATE TABLE [dogs] (\n [name] TEXT\n, [default_str] TEXT)",
|
'CREATE TABLE "dogs" (\n "name" TEXT\n, "default_str" TEXT)',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"nickname",
|
"nickname",
|
||||||
str,
|
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",
|
"nickname",
|
||||||
str,
|
str,
|
||||||
"dawg's dawg",
|
"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):
|
def test_add_column(fresh_db, col_name, col_type, not_null_default, expected_schema):
|
||||||
fresh_db.create_table("dogs", {"name": str})
|
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)
|
fresh_db["dogs"].add_column(col_name, col_type, not_null_default=not_null_default)
|
||||||
assert fresh_db["dogs"].schema == expected_schema
|
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")
|
fresh_db["dogs"].add_column("breed_id", fk="breeds")
|
||||||
assert fresh_db["dogs"].schema == (
|
assert fresh_db["dogs"].schema == (
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [breed_id] INTEGER REFERENCES [breeds]([rowid])\n"
|
' "breed_id" INTEGER REFERENCES "breeds"("rowid")\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
# And again with an explicit primary key column
|
# 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")
|
fresh_db["dogs"].add_column("subbreed_id", fk="subbreeds")
|
||||||
assert fresh_db["dogs"].schema == (
|
assert fresh_db["dogs"].schema == (
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [breed_id] INTEGER REFERENCES [breeds]([rowid]),\n"
|
' "breed_id" INTEGER REFERENCES "breeds"("rowid"),\n'
|
||||||
" [subbreed_id] TEXT REFERENCES [subbreeds]([primkey])\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")
|
fresh_db["dogs"].add_foreign_key("breed_id")
|
||||||
assert fresh_db["dogs"].schema == (
|
assert fresh_db["dogs"].schema == (
|
||||||
'CREATE TABLE "dogs" (\n'
|
'CREATE TABLE "dogs" (\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [breed_id] INTEGER REFERENCES [breeds]([id])\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}])
|
table.add_missing_columns([{"Name": ".", "age": 4}])
|
||||||
assert (
|
assert (
|
||||||
table.schema
|
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'"
|
"select sql from sqlite_master where name='idx_dogs_age_name'"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
assert sql == (
|
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": int},
|
||||||
"[id] INTEGER",
|
'"id" INTEGER',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"col": dict},
|
{"col": dict},
|
||||||
"[col] TEXT",
|
'"col" TEXT',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"col": tuple},
|
{"col": tuple},
|
||||||
"[col] TEXT",
|
'"col" TEXT',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"col": list},
|
{"col": list},
|
||||||
"[col] TEXT",
|
'"col" TEXT',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -1191,12 +1195,12 @@ def test_create(fresh_db):
|
||||||
defaults={"integer": 0},
|
defaults={"integer": 0},
|
||||||
)
|
)
|
||||||
assert fresh_db["t"].schema == (
|
assert fresh_db["t"].schema == (
|
||||||
"CREATE TABLE [t] (\n"
|
'CREATE TABLE "t" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [float] FLOAT NOT NULL,\n"
|
' "float" FLOAT NOT NULL,\n'
|
||||||
" [text] TEXT,\n"
|
' "text" TEXT,\n'
|
||||||
" [integer] INTEGER NOT NULL DEFAULT 0,\n"
|
' "integer" INTEGER NOT NULL DEFAULT 0,\n'
|
||||||
" [bytes] BLOB\n"
|
' "bytes" BLOB\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1232,7 +1236,7 @@ def test_create_replace(fresh_db):
|
||||||
fresh_db["t"].create({"id": int})
|
fresh_db["t"].create({"id": int})
|
||||||
# This should not
|
# This should not
|
||||||
fresh_db["t"].create({"name": str}, replace=True)
|
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(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -1242,58 +1246,58 @@ def test_create_replace(fresh_db):
|
||||||
(
|
(
|
||||||
{"id": int, "name": str},
|
{"id": int, "name": str},
|
||||||
{"pk": "id"},
|
{"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,
|
False,
|
||||||
),
|
),
|
||||||
# Drop name column, remove primary key
|
# 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
|
# Add a new column
|
||||||
(
|
(
|
||||||
{"id": int, "name": str, "age": int},
|
{"id": int, "name": str, "age": int},
|
||||||
{"pk": "id"},
|
{"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,
|
True,
|
||||||
),
|
),
|
||||||
# Change a column type
|
# Change a column type
|
||||||
(
|
(
|
||||||
{"id": int, "name": bytes},
|
{"id": int, "name": bytes},
|
||||||
{"pk": "id"},
|
{"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,
|
True,
|
||||||
),
|
),
|
||||||
# Change the primary key
|
# Change the primary key
|
||||||
(
|
(
|
||||||
{"id": int, "name": str},
|
{"id": int, "name": str},
|
||||||
{"pk": "name"},
|
{"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,
|
True,
|
||||||
),
|
),
|
||||||
# Change in column order
|
# Change in column order
|
||||||
(
|
(
|
||||||
{"id": int, "name": str},
|
{"id": int, "name": str},
|
||||||
{"pk": "id", "column_order": ["name"]},
|
{"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,
|
True,
|
||||||
),
|
),
|
||||||
# Same column order is ignored
|
# Same column order is ignored
|
||||||
(
|
(
|
||||||
{"id": int, "name": str},
|
{"id": int, "name": str},
|
||||||
{"pk": "id", "column_order": ["id", "name"]},
|
{"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,
|
False,
|
||||||
),
|
),
|
||||||
# Change not null
|
# Change not null
|
||||||
(
|
(
|
||||||
{"id": int, "name": str},
|
{"id": int, "name": str},
|
||||||
{"pk": "id", "not_null": {"name"}},
|
{"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,
|
True,
|
||||||
),
|
),
|
||||||
# Change default values
|
# Change default values
|
||||||
(
|
(
|
||||||
{"id": int, "name": str},
|
{"id": int, "name": str},
|
||||||
{"pk": "id", "defaults": {"id": 0, "name": "Bob"}},
|
{"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,
|
True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -1356,11 +1360,11 @@ def test_insert_upsert_strict(fresh_db, method_name, strict):
|
||||||
def test_create_table_strict(fresh_db, strict):
|
def test_create_table_strict(fresh_db, strict):
|
||||||
table = fresh_db.create_table("t", {"id": int, "f": float}, strict=strict)
|
table = fresh_db.create_table("t", {"id": int, "f": float}, strict=strict)
|
||||||
assert table.strict == strict or not fresh_db.supports_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:
|
if strict and not fresh_db.supports_strict:
|
||||||
return
|
return
|
||||||
if strict:
|
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
|
assert table.schema == expected_schema
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ import pytest
|
||||||
def test_duplicate(fresh_db):
|
def test_duplicate(fresh_db):
|
||||||
# Create table using native Sqlite statement:
|
# Create table using native Sqlite statement:
|
||||||
fresh_db.execute(
|
fresh_db.execute(
|
||||||
"""CREATE TABLE [table1] (
|
"""CREATE TABLE "table1" (
|
||||||
[text_col] TEXT,
|
"text_col" TEXT,
|
||||||
[real_col] REAL,
|
"real_col" REAL,
|
||||||
[int_col] INTEGER,
|
"int_col" INTEGER,
|
||||||
[bool_col] INTEGER,
|
"bool_col" INTEGER,
|
||||||
[datetime_col] TEXT)"""
|
"datetime_col" TEXT)"""
|
||||||
)
|
)
|
||||||
# Insert one row of mock data:
|
# Insert one row of mock data:
|
||||||
dt = datetime.datetime.now()
|
dt = datetime.datetime.now()
|
||||||
|
|
|
||||||
|
|
@ -15,25 +15,25 @@ def test_enable_counts_specific_table(fresh_db):
|
||||||
foo.enable_counts()
|
foo.enable_counts()
|
||||||
assert foo.triggers_dict == {
|
assert foo.triggers_dict == {
|
||||||
"foo_counts_insert": (
|
"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"
|
"BEGIN\n"
|
||||||
" INSERT OR REPLACE INTO [_counts]\n"
|
' INSERT OR REPLACE INTO "_counts"\n'
|
||||||
" VALUES (\n 'foo',\n"
|
" VALUES (\n 'foo',\n"
|
||||||
" COALESCE(\n"
|
" COALESCE(\n"
|
||||||
" (SELECT count FROM [_counts] WHERE [table] = 'foo'),\n"
|
' (SELECT count FROM "_counts" WHERE "table" = \'foo\'),\n'
|
||||||
" 0\n"
|
" 0\n"
|
||||||
" ) + 1\n"
|
" ) + 1\n"
|
||||||
" );\n"
|
" );\n"
|
||||||
"END"
|
"END"
|
||||||
),
|
),
|
||||||
"foo_counts_delete": (
|
"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"
|
"BEGIN\n"
|
||||||
" INSERT OR REPLACE INTO [_counts]\n"
|
' INSERT OR REPLACE INTO "_counts"\n'
|
||||||
" VALUES (\n"
|
" VALUES (\n"
|
||||||
" 'foo',\n"
|
" 'foo',\n"
|
||||||
" COALESCE(\n"
|
" COALESCE(\n"
|
||||||
" (SELECT count FROM [_counts] WHERE [table] = 'foo'),\n"
|
' (SELECT count FROM "_counts" WHERE "table" = \'foo\'),\n'
|
||||||
" 0\n"
|
" 0\n"
|
||||||
" ) - 1\n"
|
" ) - 1\n"
|
||||||
" );\n"
|
" );\n"
|
||||||
|
|
@ -132,7 +132,7 @@ def test_uses_counts_after_enable_counts(counts_db_path):
|
||||||
assert db.table("foo").count == 1
|
assert db.table("foo").count == 1
|
||||||
assert logged == [
|
assert logged == [
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("select count(*) from [foo]", []),
|
('select count(*) from "foo"', []),
|
||||||
]
|
]
|
||||||
logged.clear()
|
logged.clear()
|
||||||
assert not db.use_counts_table
|
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 db.table("foo").count == 1
|
||||||
assert logged == [
|
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,
|
None,
|
||||||
),
|
),
|
||||||
("select name from sqlite_master where type = 'table'", 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 quote(:value)", {"value": "baz"}),
|
||||||
("select sql from sqlite_master where name = ?", ("_counts",)),
|
("select sql from sqlite_master where name = ?", ("_counts",)),
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("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)
|
fresh_db["tree"].extract("species", table=table, fk_column=fk_column)
|
||||||
assert fresh_db["tree"].schema == (
|
assert fresh_db["tree"].schema == (
|
||||||
'CREATE TABLE "tree" (\n'
|
'CREATE TABLE "tree" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [{}] INTEGER REFERENCES [{}]([id]),\n".format(expected_fk, expected_table)
|
' "{}" INTEGER REFERENCES "{}"("id"),\n'.format(expected_fk, expected_table)
|
||||||
+ " [end] INTEGER\n"
|
+ ' "end" INTEGER\n'
|
||||||
+ ")"
|
+ ")"
|
||||||
)
|
)
|
||||||
assert fresh_db[expected_table].schema == (
|
assert fresh_db[expected_table].schema == (
|
||||||
"CREATE TABLE [{}] (\n".format(expected_table)
|
'CREATE TABLE "{}" (\n'.format(expected_table)
|
||||||
+ " [id] INTEGER PRIMARY KEY,\n"
|
+ ' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [species] TEXT\n"
|
' "species" TEXT\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
assert list(fresh_db[expected_table].rows) == [
|
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 == (
|
assert fresh_db["tree"].schema == (
|
||||||
'CREATE TABLE "tree" (\n'
|
'CREATE TABLE "tree" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [common_name_latin_name_id] INTEGER REFERENCES [common_name_latin_name]([id])\n"
|
' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
assert fresh_db["common_name_latin_name"].schema == (
|
assert fresh_db["common_name_latin_name"].schema == (
|
||||||
"CREATE TABLE [common_name_latin_name] (\n"
|
'CREATE TABLE "common_name_latin_name" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [latin_name] TEXT\n"
|
' "latin_name" TEXT\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
assert list(fresh_db["common_name_latin_name"].rows) == [
|
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"])
|
fresh_db["tree"].extract(["common_name", "latin_name"])
|
||||||
assert fresh_db["tree"].schema == (
|
assert fresh_db["tree"].schema == (
|
||||||
'CREATE TABLE "tree" (\n'
|
'CREATE TABLE "tree" (\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [common_name_latin_name_id] INTEGER REFERENCES [common_name_latin_name]([id])\n"
|
' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
|
|
@ -152,15 +152,15 @@ def test_reuse_lookup_table(fresh_db):
|
||||||
fresh_db["individuals"].extract("species", rename={"species": "name"})
|
fresh_db["individuals"].extract("species", rename={"species": "name"})
|
||||||
assert fresh_db["sightings"].schema == (
|
assert fresh_db["sightings"].schema == (
|
||||||
'CREATE TABLE "sightings" (\n'
|
'CREATE TABLE "sightings" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [species_id] INTEGER REFERENCES [species]([id])\n"
|
' "species_id" INTEGER REFERENCES "species"("id")\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
assert fresh_db["individuals"].schema == (
|
assert fresh_db["individuals"].schema == (
|
||||||
'CREATE TABLE "individuals" (\n'
|
'CREATE TABLE "individuals" (\n'
|
||||||
" [id] INTEGER PRIMARY KEY,\n"
|
' "id" INTEGER PRIMARY KEY,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [species_id] INTEGER REFERENCES [species]([id])\n"
|
' "species_id" INTEGER REFERENCES "species"("id")\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
assert list(fresh_db["species"].rows) == [
|
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
|
# Should now have two tables: Trees and Species
|
||||||
assert {expected_table, "Trees"} == set(fresh_db.table_names())
|
assert {expected_table, "Trees"} == set(fresh_db.table_names())
|
||||||
assert (
|
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
|
expected_table
|
||||||
)
|
)
|
||||||
== fresh_db[expected_table].schema
|
== fresh_db[expected_table].schema
|
||||||
)
|
)
|
||||||
assert (
|
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
|
expected_table
|
||||||
)
|
)
|
||||||
== fresh_db["Trees"].schema
|
== fresh_db["Trees"].schema
|
||||||
|
|
|
||||||
|
|
@ -435,40 +435,40 @@ def test_enable_fts_error_message_on_views():
|
||||||
{},
|
{},
|
||||||
"FTS5",
|
"FTS5",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*\n"
|
' "original".*\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" [books_fts].rank"
|
' "books_fts".rank'
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"columns": ["title"], "order_by": "rowid", "limit": 10},
|
{"columns": ["title"], "order_by": "rowid", "limit": 10},
|
||||||
"FTS5",
|
"FTS5",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" [title]\n"
|
' "title"\n'
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].[title]\n"
|
' "original"."title"\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" rowid\n"
|
" rowid\n"
|
||||||
"limit 10"
|
"limit 10"
|
||||||
|
|
@ -478,64 +478,64 @@ def test_enable_fts_error_message_on_views():
|
||||||
{"where": "author = :author"},
|
{"where": "author = :author"},
|
||||||
"FTS5",
|
"FTS5",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
" where author = :author\n"
|
" where author = :author\n"
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*\n"
|
' "original".*\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" [books_fts].rank"
|
' "books_fts".rank'
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"columns": ["title"]},
|
{"columns": ["title"]},
|
||||||
"FTS4",
|
"FTS4",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" [title]\n"
|
' "title"\n'
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].[title]\n"
|
' "original"."title"\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" rank_bm25(matchinfo([books_fts], 'pcnalx'))"
|
" rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"offset": 1, "limit": 1},
|
{"offset": 1, "limit": 1},
|
||||||
"FTS4",
|
"FTS4",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*\n"
|
' "original".*\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" rank_bm25(matchinfo([books_fts], 'pcnalx'))\n"
|
" rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))\n"
|
||||||
"limit 1 offset 1"
|
"limit 1 offset 1"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -543,21 +543,21 @@ def test_enable_fts_error_message_on_views():
|
||||||
{"limit": 2},
|
{"limit": 2},
|
||||||
"FTS4",
|
"FTS4",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*\n"
|
' "original".*\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" rank_bm25(matchinfo([books_fts], 'pcnalx'))\n"
|
" rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))\n"
|
||||||
"limit 2"
|
"limit 2"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -565,66 +565,66 @@ def test_enable_fts_error_message_on_views():
|
||||||
{"where": "author = :author"},
|
{"where": "author = :author"},
|
||||||
"FTS4",
|
"FTS4",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
" where author = :author\n"
|
" where author = :author\n"
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*\n"
|
' "original".*\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" rank_bm25(matchinfo([books_fts], 'pcnalx'))"
|
" rank_bm25(matchinfo(\"books_fts\", 'pcnalx'))"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"include_rank": True},
|
{"include_rank": True},
|
||||||
"FTS5",
|
"FTS5",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*,\n"
|
' "original".*,\n'
|
||||||
" [books_fts].rank rank\n"
|
' "books_fts".rank rank\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" [books_fts].rank"
|
' "books_fts".rank'
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{"include_rank": True},
|
{"include_rank": True},
|
||||||
"FTS4",
|
"FTS4",
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [books]\n"
|
' from "books"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*,\n"
|
' "original".*,\n'
|
||||||
" rank_bm25(matchinfo([books_fts], 'pcnalx')) rank\n"
|
" rank_bm25(matchinfo(\"books_fts\", 'pcnalx')) rank\n"
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [books_fts] on [original].rowid = [books_fts].rowid\n"
|
' join "books_fts" on "original".rowid = "books_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [books_fts] match :query\n"
|
' "books_fts" match :query\n'
|
||||||
"order by\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 = {
|
expected_triggers = {
|
||||||
"authors_ai": (
|
"authors_ai": (
|
||||||
"CREATE TRIGGER [authors_ai] AFTER INSERT ON [authors] BEGIN\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"
|
' INSERT INTO "authors_fts" (rowid, "name", "famous_works") VALUES (new.rowid, new."name", new."famous_works");\n'
|
||||||
"END"
|
"END"
|
||||||
),
|
),
|
||||||
"authors_ad": (
|
"authors_ad": (
|
||||||
"CREATE TRIGGER [authors_ad] AFTER DELETE ON [authors] BEGIN\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"
|
' INSERT INTO "authors_fts" ("authors_fts", rowid, "name", "famous_works") VALUES(\'delete\', old.rowid, old."name", old."famous_works");\n'
|
||||||
"END"
|
"END"
|
||||||
),
|
),
|
||||||
"authors_au": (
|
"authors_au": (
|
||||||
"CREATE TRIGGER [authors_au] AFTER UPDATE ON [authors] BEGIN\n"
|
'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" ("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"
|
' INSERT INTO "authors_fts" (rowid, "name", "famous_works") VALUES (new.rowid, new."name", new."famous_works");\nEND'
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
assert authors.triggers_dict == expected_triggers
|
assert authors.triggers_dict == expected_triggers
|
||||||
|
|
|
||||||
|
|
@ -114,18 +114,18 @@ def test_lookup_with_extra_insert_parameters(fresh_db):
|
||||||
columns={"make_this_integer": int},
|
columns={"make_this_integer": int},
|
||||||
)
|
)
|
||||||
assert species.schema == (
|
assert species.schema == (
|
||||||
"CREATE TABLE [species] (\n"
|
'CREATE TABLE "species" (\n'
|
||||||
" [renamed_id] INTEGER PRIMARY KEY,\n"
|
' "renamed_id" INTEGER PRIMARY KEY,\n'
|
||||||
" [this_at_front] INTEGER,\n"
|
' "this_at_front" INTEGER,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [type] TEXT,\n"
|
' "type" TEXT,\n'
|
||||||
" [first_seen] TEXT,\n"
|
' "first_seen" TEXT,\n'
|
||||||
" [make_not_null] INTEGER NOT NULL,\n"
|
' "make_not_null" INTEGER NOT NULL,\n'
|
||||||
" [fk_to_other] INTEGER REFERENCES [other_table]([id]),\n"
|
' "fk_to_other" INTEGER REFERENCES "other_table"("id"),\n'
|
||||||
" [default_is_dog] TEXT DEFAULT 'dog',\n"
|
" \"default_is_dog\" TEXT DEFAULT 'dog',\n"
|
||||||
" [extract_this] INTEGER REFERENCES [extract_this]([id]),\n"
|
' "extract_this" INTEGER REFERENCES "extract_this"("id"),\n'
|
||||||
" [convert_to_upper] TEXT,\n"
|
' "convert_to_upper" TEXT,\n'
|
||||||
" [make_this_integer] INTEGER\n"
|
' "make_this_integer" INTEGER\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
assert species.get(id) == {
|
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 = 'view'", None),
|
||||||
("select name from sqlite_master where type = 'table'", None),
|
("select name from sqlite_master where type = 'table'", None),
|
||||||
("select name from sqlite_master where type = 'view'", 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),
|
("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,
|
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,
|
None,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
@ -64,7 +64,7 @@ def test_with_tracer():
|
||||||
" )\n"
|
" )\n"
|
||||||
" )",
|
" )",
|
||||||
{
|
{
|
||||||
"like": "%VIRTUAL TABLE%USING FTS%content=[dogs]%",
|
"like": '%VIRTUAL TABLE%USING FTS%content="dogs"%',
|
||||||
"like2": '%VIRTUAL TABLE%USING FTS%content="dogs"%',
|
"like2": '%VIRTUAL TABLE%USING FTS%content="dogs"%',
|
||||||
"table": "dogs",
|
"table": "dogs",
|
||||||
},
|
},
|
||||||
|
|
@ -73,21 +73,21 @@ def test_with_tracer():
|
||||||
("select name from sqlite_master where type = 'view'", None),
|
("select name from sqlite_master where type = 'view'", None),
|
||||||
("select sql from sqlite_master where name = ?", ("dogs_fts",)),
|
("select sql from sqlite_master where name = ?", ("dogs_fts",)),
|
||||||
(
|
(
|
||||||
"with original as (\n"
|
'with "original" as (\n'
|
||||||
" select\n"
|
" select\n"
|
||||||
" rowid,\n"
|
" rowid,\n"
|
||||||
" *\n"
|
" *\n"
|
||||||
" from [dogs]\n"
|
' from "dogs"\n'
|
||||||
")\n"
|
")\n"
|
||||||
"select\n"
|
"select\n"
|
||||||
" [original].*\n"
|
' "original".*\n'
|
||||||
"from\n"
|
"from\n"
|
||||||
" [original]\n"
|
' "original"\n'
|
||||||
" join [dogs_fts] on [original].rowid = [dogs_fts].rowid\n"
|
' join "dogs_fts" on "original".rowid = "dogs_fts".rowid\n'
|
||||||
"where\n"
|
"where\n"
|
||||||
" [dogs_fts] match :query\n"
|
' "dogs_fts" match :query\n'
|
||||||
"order by\n"
|
"order by\n"
|
||||||
" [dogs_fts].rank",
|
' "dogs_fts".rank',
|
||||||
{"query": "Cleopaws"},
|
{"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);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Change column type
|
# Change column type
|
||||||
(
|
(
|
||||||
{"types": {"age": int}},
|
{"types": {"age": int}},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] INTEGER\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Rename a column
|
# Rename a column
|
||||||
(
|
(
|
||||||
{"rename": {"age": "dog_age"}},
|
{"rename": {"age": "dog_age"}},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [dog_age] TEXT\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Drop a column
|
# Drop a column
|
||||||
(
|
(
|
||||||
{"drop": ["age"]},
|
{"drop": ["age"]},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name")\n SELECT "rowid", "id", "name" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Convert type AND rename column
|
# Convert type AND rename column
|
||||||
(
|
(
|
||||||
{"types": {"age": int}, "rename": {"age": "dog_age"}},
|
{"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);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Change primary key
|
# Change primary key
|
||||||
(
|
(
|
||||||
{"pk": "age"},
|
{"pk": "age"},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT PRIMARY KEY\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Change primary key to a compound pk
|
# Change primary key to a compound pk
|
||||||
(
|
(
|
||||||
{"pk": ("age", "name")},
|
{"pk": ("age", "name")},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT,\n PRIMARY KEY ([age], [name])\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Remove primary key, creating a rowid table
|
# Remove primary key, creating a rowid table
|
||||||
(
|
(
|
||||||
{"pk": None},
|
{"pk": None},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Keeping the table
|
# Keeping the table
|
||||||
(
|
(
|
||||||
{"drop": ["age"], "keep_table": "kept_table"},
|
{"drop": ["age"], "keep_table": "kept_table"},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n);",
|
'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];",
|
'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" RENAME TO "kept_table";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'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);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Change column type
|
# Change column type
|
||||||
(
|
(
|
||||||
{"types": {"age": int}},
|
{"types": {"age": int}},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] INTEGER\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Rename a column
|
# Rename a column
|
||||||
(
|
(
|
||||||
{"rename": {"age": "dog_age"}},
|
{"rename": {"age": "dog_age"}},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [dog_age] TEXT\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "dog_age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];",
|
'ALTER TABLE "dogs_new_suffix" RENAME TO "dogs";',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# Make ID a primary key
|
# Make ID a primary key
|
||||||
(
|
(
|
||||||
{"pk": "id"},
|
{"pk": "id"},
|
||||||
[
|
[
|
||||||
"CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] TEXT\n);",
|
'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];",
|
'INSERT INTO "dogs_new_suffix" ("rowid", "id", "name", "age")\n SELECT "rowid", "id", "name", "age" FROM "dogs";',
|
||||||
"DROP TABLE [dogs];",
|
'DROP TABLE "dogs";',
|
||||||
"ALTER TABLE [dogs_new_suffix] RENAME TO [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"})
|
dogs.insert({"id": 1, "name": "Cleo", "age": "5"})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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")
|
dogs.transform(pk="id")
|
||||||
# Slight oddity: [dogs] becomes "dogs" during the rename:
|
# Slight oddity: [dogs] becomes "dogs" during the rename:
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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"})
|
dogs.transform(rename={"id": "pk"})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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"})
|
dogs.transform(not_null={"name"})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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})
|
dogs.transform(not_null={"name": True, "age": False})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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"})
|
dogs.transform(not_null=not_null, rename={"age": "dog_age"})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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})
|
dogs.transform(defaults={"age": 1})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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})
|
dogs.transform(rename={"age": "dog_age"}, defaults={"age": 1})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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})
|
dogs.transform(defaults={"age": None})
|
||||||
assert (
|
assert (
|
||||||
dogs.schema
|
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
|
# This should have rolled us back
|
||||||
assert (
|
assert (
|
||||||
fresh_db["authors"].schema
|
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]
|
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 == (
|
assert fresh_db["places"].schema == (
|
||||||
'CREATE TABLE "places" (\n'
|
'CREATE TABLE "places" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [country] INTEGER REFERENCES [country]([id]),\n"
|
' "country" INTEGER REFERENCES "country"("id"),\n'
|
||||||
" [continent] INTEGER REFERENCES [continent]([id]),\n"
|
' "continent" INTEGER REFERENCES "continent"("id"),\n'
|
||||||
" [city] INTEGER REFERENCES [city]([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)
|
fresh_db["places"].transform(foreign_keys=foreign_keys)
|
||||||
assert fresh_db["places"].schema == (
|
assert fresh_db["places"].schema == (
|
||||||
'CREATE TABLE "places" (\n'
|
'CREATE TABLE "places" (\n'
|
||||||
" [id] INTEGER,\n"
|
' "id" INTEGER,\n'
|
||||||
" [name] TEXT,\n"
|
' "name" TEXT,\n'
|
||||||
" [country] INTEGER REFERENCES [country]([id]),\n"
|
' "country" INTEGER REFERENCES "country"("id"),\n'
|
||||||
" [continent] INTEGER REFERENCES [continent]([id]),\n"
|
' "continent" INTEGER REFERENCES "continent"("id"),\n'
|
||||||
" [city] INTEGER\n"
|
' "city" INTEGER\n'
|
||||||
")"
|
")"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,11 +70,12 @@ def test_update_alter(fresh_db):
|
||||||
] == list(table.rows)
|
] == 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"]
|
table = fresh_db["table"]
|
||||||
rowid = table.insert({"foo": "bar"}).last_pk
|
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):
|
def test_update_with_no_values_sets_last_pk(fresh_db):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue