From 155c6178cd18671c8046c27c22ed580a9e4b1fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 28 Dec 2021 11:48:03 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Document=20indexes=20and=20make=20t?= =?UTF-8?q?hem=20opt-in=20(#205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../img/tutorial/indexes/dictionary001.drawio | 97 +++++ docs/img/tutorial/indexes/dictionary001.svg | 57 +++ .../img/tutorial/indexes/dictionary002.drawio | 97 +++++ docs/img/tutorial/indexes/dictionary002.svg | 1 + .../img/tutorial/indexes/dictionary003.drawio | 97 +++++ docs/img/tutorial/indexes/dictionary003.svg | 1 + .../img/tutorial/indexes/dictionary004.drawio | 100 +++++ docs/img/tutorial/indexes/dictionary004.svg | 1 + .../img/tutorial/indexes/dictionary005.drawio | 97 +++++ docs/img/tutorial/indexes/dictionary005.svg | 1 + .../img/tutorial/indexes/dictionary006.drawio | 100 +++++ docs/img/tutorial/indexes/dictionary006.svg | 1 + .../img/tutorial/indexes/dictionary007.drawio | 100 +++++ docs/img/tutorial/indexes/dictionary007.svg | 1 + .../img/tutorial/indexes/dictionary008.drawio | 103 +++++ docs/img/tutorial/indexes/dictionary008.svg | 1 + docs/img/tutorial/indexes/techbook001.drawio | 92 ++++ docs/img/tutorial/indexes/techbook001.svg | 1 + .../connect/create-connected-tables.md | 1 + docs/tutorial/fastapi/multiple-models.md | 25 ++ docs/tutorial/indexes.md | 406 ++++++++++++++++++ docs/tutorial/one.md | 6 +- docs/tutorial/update.md | 2 +- docs/tutorial/where.md | 8 +- docs_src/advanced/decimal/tutorial001.py | 4 +- .../code_structure/tutorial001/models.py | 6 +- .../code_structure/tutorial002/hero_model.py | 4 +- .../code_structure/tutorial002/team_model.py | 2 +- .../connect/create_tables/tutorial001.py | 6 +- .../tutorial/connect/delete/tutorial001.py | 6 +- .../tutorial/connect/insert/tutorial001.py | 6 +- .../tutorial/connect/select/tutorial001.py | 6 +- .../tutorial/connect/select/tutorial002.py | 6 +- .../tutorial/connect/select/tutorial003.py | 6 +- .../tutorial/connect/select/tutorial004.py | 6 +- .../tutorial/connect/select/tutorial005.py | 6 +- .../tutorial/connect/update/tutorial001.py | 6 +- docs_src/tutorial/delete/tutorial001.py | 4 +- docs_src/tutorial/delete/tutorial002.py | 4 +- .../fastapi/app_testing/tutorial001/main.py | 4 +- .../tutorial/fastapi/delete/tutorial001.py | 4 +- .../fastapi/limit_and_offset/tutorial001.py | 4 +- .../fastapi/multiple_models/tutorial001.py | 4 +- .../fastapi/multiple_models/tutorial002.py | 4 +- .../tutorial/fastapi/read_one/tutorial001.py | 4 +- .../fastapi/relationships/tutorial001.py | 6 +- .../fastapi/response_model/tutorial001.py | 4 +- .../session_with_dependency/tutorial001.py | 4 +- .../fastapi/simple_hero_api/tutorial001.py | 4 +- .../tutorial/fastapi/teams/tutorial001.py | 6 +- .../tutorial/fastapi/update/tutorial001.py | 4 +- docs_src/tutorial/indexes/__init__.py | 0 docs_src/tutorial/indexes/tutorial001.py | 51 +++ docs_src/tutorial/indexes/tutorial002.py | 59 +++ docs_src/tutorial/many_to_many/tutorial001.py | 6 +- docs_src/tutorial/many_to_many/tutorial002.py | 6 +- docs_src/tutorial/many_to_many/tutorial003.py | 6 +- .../tutorial/offset_and_limit/tutorial001.py | 4 +- .../tutorial/offset_and_limit/tutorial002.py | 4 +- .../tutorial/offset_and_limit/tutorial003.py | 4 +- .../tutorial/offset_and_limit/tutorial004.py | 4 +- docs_src/tutorial/one/tutorial001.py | 4 +- docs_src/tutorial/one/tutorial002.py | 4 +- docs_src/tutorial/one/tutorial003.py | 4 +- docs_src/tutorial/one/tutorial004.py | 4 +- docs_src/tutorial/one/tutorial005.py | 4 +- docs_src/tutorial/one/tutorial006.py | 4 +- docs_src/tutorial/one/tutorial007.py | 4 +- docs_src/tutorial/one/tutorial008.py | 4 +- docs_src/tutorial/one/tutorial009.py | 4 +- .../back_populates/tutorial001.py | 6 +- .../back_populates/tutorial002.py | 6 +- .../back_populates/tutorial003.py | 10 +- .../tutorial001.py | 6 +- .../tutorial001.py | 6 +- .../read_relationships/tutorial001.py | 6 +- .../read_relationships/tutorial002.py | 6 +- docs_src/tutorial/update/tutorial001.py | 4 +- docs_src/tutorial/update/tutorial002.py | 4 +- docs_src/tutorial/update/tutorial003.py | 4 +- docs_src/tutorial/update/tutorial004.py | 4 +- mkdocs.yml | 5 +- sqlmodel/main.py | 2 +- .../test_multiple_models/test_tutorial001.py | 15 + .../test_multiple_models/test_tutorial002.py | 15 + tests/test_tutorial/test_indexes/__init__.py | 0 .../test_indexes/test_tutorial001.py | 35 ++ .../test_indexes/test_tutorial006.py | 36 ++ .../test_where/test_tutorial003.py | 7 +- .../test_where/test_tutorial004.py | 7 +- .../test_where/test_tutorial011.py | 7 +- 91 files changed, 1755 insertions(+), 142 deletions(-) create mode 100644 docs/img/tutorial/indexes/dictionary001.drawio create mode 100644 docs/img/tutorial/indexes/dictionary001.svg create mode 100644 docs/img/tutorial/indexes/dictionary002.drawio create mode 100644 docs/img/tutorial/indexes/dictionary002.svg create mode 100644 docs/img/tutorial/indexes/dictionary003.drawio create mode 100644 docs/img/tutorial/indexes/dictionary003.svg create mode 100644 docs/img/tutorial/indexes/dictionary004.drawio create mode 100644 docs/img/tutorial/indexes/dictionary004.svg create mode 100644 docs/img/tutorial/indexes/dictionary005.drawio create mode 100644 docs/img/tutorial/indexes/dictionary005.svg create mode 100644 docs/img/tutorial/indexes/dictionary006.drawio create mode 100644 docs/img/tutorial/indexes/dictionary006.svg create mode 100644 docs/img/tutorial/indexes/dictionary007.drawio create mode 100644 docs/img/tutorial/indexes/dictionary007.svg create mode 100644 docs/img/tutorial/indexes/dictionary008.drawio create mode 100644 docs/img/tutorial/indexes/dictionary008.svg create mode 100644 docs/img/tutorial/indexes/techbook001.drawio create mode 100644 docs/img/tutorial/indexes/techbook001.svg create mode 100644 docs/tutorial/indexes.md create mode 100644 docs_src/tutorial/indexes/__init__.py create mode 100644 docs_src/tutorial/indexes/tutorial001.py create mode 100644 docs_src/tutorial/indexes/tutorial002.py create mode 100644 tests/test_tutorial/test_indexes/__init__.py create mode 100644 tests/test_tutorial/test_indexes/test_tutorial001.py create mode 100644 tests/test_tutorial/test_indexes/test_tutorial006.py diff --git a/docs/img/tutorial/indexes/dictionary001.drawio b/docs/img/tutorial/indexes/dictionary001.drawio new file mode 100644 index 0000000..659f6b5 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary001.drawio @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary001.svg b/docs/img/tutorial/indexes/dictionary001.svg new file mode 100644 index 0000000..b543793 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary001.svg @@ -0,0 +1,57 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
M
M
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary002.drawio b/docs/img/tutorial/indexes/dictionary002.drawio new file mode 100644 index 0000000..cb1857b --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary002.drawio @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary002.svg b/docs/img/tutorial/indexes/dictionary002.svg new file mode 100644 index 0000000..677687d --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary002.svg @@ -0,0 +1 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
M
M
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary003.drawio b/docs/img/tutorial/indexes/dictionary003.drawio new file mode 100644 index 0000000..845eb06 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary003.drawio @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary003.svg b/docs/img/tutorial/indexes/dictionary003.svg new file mode 100644 index 0000000..d667a68 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary003.svg @@ -0,0 +1 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary004.drawio b/docs/img/tutorial/indexes/dictionary004.drawio new file mode 100644 index 0000000..14bbb1e --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary004.drawio @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary004.svg b/docs/img/tutorial/indexes/dictionary004.svg new file mode 100644 index 0000000..f881d6c --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary004.svg @@ -0,0 +1 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
F
F
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary005.drawio b/docs/img/tutorial/indexes/dictionary005.drawio new file mode 100644 index 0000000..9e339c1 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary005.drawio @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary005.svg b/docs/img/tutorial/indexes/dictionary005.svg new file mode 100644 index 0000000..9d37624 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary005.svg @@ -0,0 +1 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary006.drawio b/docs/img/tutorial/indexes/dictionary006.drawio new file mode 100644 index 0000000..3c669d3 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary006.drawio @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary006.svg b/docs/img/tutorial/indexes/dictionary006.svg new file mode 100644 index 0000000..30be80e --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary006.svg @@ -0,0 +1 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
C
C
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary007.drawio b/docs/img/tutorial/indexes/dictionary007.drawio new file mode 100644 index 0000000..89b32ca --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary007.drawio @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary007.svg b/docs/img/tutorial/indexes/dictionary007.svg new file mode 100644 index 0000000..79e7950 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary007.svg @@ -0,0 +1 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary008.drawio b/docs/img/tutorial/indexes/dictionary008.drawio new file mode 100644 index 0000000..ac08ad0 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary008.drawio @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/dictionary008.svg b/docs/img/tutorial/indexes/dictionary008.svg new file mode 100644 index 0000000..013a4d6 --- /dev/null +++ b/docs/img/tutorial/indexes/dictionary008.svg @@ -0,0 +1 @@ +
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
N
N
O
O
P
P
Q
Q
R
R
S
S
T
T
U
U
V
V
W
W
X
X
Y
Y
Z
Z
Dictionary
Dictionary
D
D
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/img/tutorial/indexes/techbook001.drawio b/docs/img/tutorial/indexes/techbook001.drawio new file mode 100644 index 0000000..de1c256 --- /dev/null +++ b/docs/img/tutorial/indexes/techbook001.drawio @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/img/tutorial/indexes/techbook001.svg b/docs/img/tutorial/indexes/techbook001.svg new file mode 100644 index 0000000..8b0c09d --- /dev/null +++ b/docs/img/tutorial/indexes/techbook001.svg @@ -0,0 +1 @@ +
Technical Book
Technical Book
Chapter 1
Chapter 1
Chapter 2
Chapter 2
Chapter 3
Chapter 3
Chapter 4
Chapter 4
Chapter 5
Chapter 5
Chapter 6
Chapter 6
Chapter 7
Chapter 7
Book Index
Book Index
Database
Database
Python
Python
Files
Files
Editors
Editors
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/docs/tutorial/connect/create-connected-tables.md b/docs/tutorial/connect/create-connected-tables.md index 90301ee..452c904 100644 --- a/docs/tutorial/connect/create-connected-tables.md +++ b/docs/tutorial/connect/create-connected-tables.md @@ -78,6 +78,7 @@ The `Team` model will be in a table automatically named `"team"`, and it will ha * `id`, the primary key, automatically generated by the database * `name`, the name of the team + * We also tell **SQLModel** to create an index for this column * `headquarters`, the headquarters of the team And finally we mark it as a table in the config. diff --git a/docs/tutorial/fastapi/multiple-models.md b/docs/tutorial/fastapi/multiple-models.md index 5cac6dd..d313874 100644 --- a/docs/tutorial/fastapi/multiple-models.md +++ b/docs/tutorial/fastapi/multiple-models.md @@ -305,6 +305,31 @@ And of course, all these fields will be in the columns for the resulting `hero` And those inherited fields will also be in the **autocompletion** and **inline errors** in editors, etc. +### Columns and Inheritance with Multiple Models + +Notice that the parent model `HeroBase` is not a **table model**, but still, we can declare `name` and `age` using `Field(index=True)`. + +```Python hl_lines="4 6 9" +# Code above omitted 👆 + +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py[ln:7-14]!} + +# Code below omitted 👇 +``` + +
+👀 Full file preview + +```Python +{!./docs_src/tutorial/fastapi/multiple_models/tutorial002.py!} +``` + +
+ +This won't affect this parent **data model** `HeroBase`. + +But once the child model `Hero` (the actual **table model**) inherits those fields, it will use those field configurations to create the indexes when creating the tables in the database. + ### The `HeroCreate` **Data Model** Now let's see the `HeroCreate` model that will be used to define the data that we want to receive in the API when creating a new hero. diff --git a/docs/tutorial/indexes.md b/docs/tutorial/indexes.md new file mode 100644 index 0000000..6513d7d --- /dev/null +++ b/docs/tutorial/indexes.md @@ -0,0 +1,406 @@ +# Indexes - Optimize Queries + +We just saw how to get some data `WHERE` a **condition** is true. For example, where the hero **name is "Deadpond"**. + +If we just create the tables and the data as we have been doing, when we `SELECT` some data using `WHERE`, the database would have to **scan** through **each one of the records** to find the ones that **match**. This is not a problem with 3 heroes as in these examples. + +But imagine that your database has **thousands** or **millions** of **records**, if every time you want to find the heroes with the name "Deadpond" it has to scan through **all** of the records to find all the possible matches, then that becomes problematic, as it would be too slow. + +I'll show you how to handle it with a database **index**. + +The change in the code is **extremely small**, but it's useful to understand what's happening behind the scenes, so I'll show you **how it all works** and what it means. + +--- + +If you already executed the previous examples and have a database with data, **remove the database file** before running each example, that way you won't have duplicate data and you will be able to get the same results. + +## No Time to Explain + +Are you already a **SQL expert** and don't have time for all my explanations? + +Fine, in that case, you can **sneak peek** the final code to create indexes here. + +
+👀 Full file preview + +```Python hl_lines="8 10" +{!./docs_src/tutorial/indexes/tutorial002.py!} +``` + +
+ +..but if you are not an expert, **continue reading**, this will probably be useful. 🤓 + +## What is an Index + +In general, an **index** is just something we can have to help us **find things faster**. It normally works by having things in **order**. Let's think about some real-life examples before even thinking about databases and code. + +### An Index and a Dictionary + +Imagine a **dictionary**, a book with definitions of words. 📔 ...not a Python `dict`. 😅 + +Let's say that you want to **find a word**, for example the word "**database**". You take the dictionary, and open it somewhere, for example in the middle. Maybe you see some definitions of words that start with `m`, like `manual`, so you conclude that you are in the letter `m` in the dictionary. + + + +You know that in the alphabet, the letter `d` for `database` comes **before** the letter `m` for `manual`. + + + +So, you know you have to search in the dictionary **before** the point you currently are. You still don't know where the word `database` is, because you don't know exactly where the letter `d` is in the dictionary, but you know that **it is not after** that point, you can now **discard the right half** of the dictionary in your search. + + + +Next, you **open the dictionary again**, but only taking into account the **half of the dictionary** that can contain the word you want, the **left part of the dictionary**. You open it in the middle of that left part and now you arrive maybe at the letter `f`. + + + +You know that `d` from `database` comes before `f`. So it has to be **before** that. But now you know that `database` **is not after** that point, and you can discard the dictionary from that point onward. + + + +Now you have a **small section of dictionary** to search (only a **quarter** of dictionary can have your word). You take that **quarter** of the pages at the start of the dictionary that can contain your word, and open it in the middle of that section. Maybe you arrive at the letter `c`. + + + +You know the word `database` has to be **after** that and **not before** that point, so you can discard the left part of that block of pages. + + + +You repeat this process **a few more times**, and you finally arrive at the letter `d`, you continue with the same process in that section for the letter `d` and you finally **find the word** `database`. 🎉 + + + +You had to open the dictionary a few times, maybe **5 or 10**. That's actually **very little work** compared to what it could have been. + +!!! note "Technical Details" + Do you like **fancy words**? Cool! Programmers tend to like fancy words. 😅 + + That algorithm I showed you above is called **Binary Search**. + + It's called like that because you **search** something by splitting the dictionary (or any ordered list of things) in **two** ("binary" means "two") parts. And you do that process multiple times until you find what you want. + +### An Index and a Novel + +Let's now imagine you are reading a **novel book**. And someone told you that at some point, they mention a **database**, and you want to find that chapter. + +How do you find the word "*database*" there? You might have to read **the entire book** to find where the word "*database*" is located in the book. So, instead of opening the book 5 or 10 times, you would have to open each of the **500 pages** and read them one by one until you find the word. You might enjoy the book, though. 😅 + +But if we are only interested in **quickly finding information** (as when working with SQL databases), then reading each of the 500 pages is **too inefficient** when there could be an option to open the book in 5 or 10 places and find what you're looking for. + +### A Technical Book with an Index + +Now let's imagine you are reading a technical book. For example, with several topics about programming. And there's a couple of sections where it talks about a **database**. + +This book might have a **book index**: a section in the book that has some **names of topics covered** and the **page numbers** in the book where you can read about them. And the topic names are **sorted** in alphabetic order, pretty much like a dictionary (a book with words, as in the previous example). + +In this case, you can open that book in the end (or in the beginning) to find the **book index** section, it would have only a few pages. And then, you can do the same process as with the **dictionary** example above. + +Open the index, and after **5 or 10 steps**, quickly find the topic "**database**" with the page numbers where that is covered, for example "page 253 in Chapter 5". Now you used the dictionary technique to find the **topic**, and that topic gave you a **page number**. + +Now you know that you need to find "**page 253**". But by looking at the closed book you still don't know where that page is, so you have to **find that page**. To find it, you can do the same process again, but this time, instead of searching for a **topic** in the **index**, you are searching for a **page number** in the **entire book**. And after **5 or 10 more steps**, you find the page 253 in Chapter 5. + + + +After this, even though this book is not a dictionary and has some particular content, you were able to **find the section** in the book that talks about a "**database**" in a **few steps** (say 10 or 20, instead of reading all the 500 pages). + +The main point is that the index is **sorted**, so we can use the same process we used for the **dictionary** to find the topic. And then that gives us a page number, and the **page numbers are also sorted**! 😅 + +When we have a list of sorted things we can apply the same technique, and that's the whole trick here, we use the same technique first for the **topics** in the index and then for the **page numbers** to find the actual chapter. + +Such efficiency! 😎 + +## What are Database Indexes + +**Database indexes** are very similar to **book indexes**. + +Database indexes store some info, some keys, in a way that makes it **easy and fast to find** (for example sorted), and then for each key they **point to some data somewhere else** in the database. + +Let's see a more clear example. Let's say you have this table in a database: + + + + + + + + + + + + + + +
idnamesecret_nameage
1DeadpondDive Wilsonnull
2Spider-BoyPedro Parqueadornull
3Rusty-ManTommy Sharp48
+ +And let's imagine you have **many more rows**, many more heroes. Probably **thousands**. + +If you tell the SQL database to get you a hero by a specific name, for example `Spider-Boy` (by using the `name` in the `WHERE` part of the SQL query), the database will have to **scan** all the heroes, checking **one by one** to find all the ones with a name of `Spider-Boy`. + +In this case, there's only one, but there's nothing limiting the database from having **more records with the same name**. And because of that, the database would **continue searching** and checking each one of the records, which would be very slow. + +But now let's say that the database has an index for the column `name`. The index could look something like this, we could imagine that the index is like an additional special table that the database manages automatically: + + + + + + + + + + + + + + +
nameid
Deadpond1
Rusty-Man3
Spider-Boy2
+ +It would have each `name` field from the `hero` table **in order**. It would not be sorted by `id`, but by `name` (in alphabetical order, as the `name` is a string). So, first it would have `Deadpond`, then `Rusty-Man`, and last `Spider-Boy`. It would also include the `id` of each hero. Remember that this could have **thousands** of heroes. + +Then the database would be able to use more or less the same ideas in the examples above with the **dictionary** and the **book index**. + +It could start somewhere (for example, in the middle of the index). It could arrive at some hero there in the middle, like `Rusty-Man`. And because the **index** has the `name` fields in order, the database would know that it can **discard all the previous index rows** and **only search** in the following index rows. + + + + + + + + + + + + + + +
nameid
Deadpond1
Rusty-Man3
Spider-Boy2
+ +And that way, as with the example with the dictionary above, **instead of reading thousands of heroes**, the database would be able to do a few steps, say **5 or 10 steps**, and arrive at the row of the index that has `Spider-Boy`, even if the table (and index) has thousands of rows: + + + + + + + + + + + + + + +
nameid
Deadpond1
Rusty-Man3
✨ Spider-Boy ✨2
+ +Then by looking at **this index row**, it would know that the `id` for `Spider-Boy` in the `hero` table is `2`. + +So then it could **search that `id`** in the `hero` table using more or less the **same technique**. + +That way, in the end, instead of reading thousands of records, the database only had to do **a few steps** to find the hero we wanted. + +## Updating the Index + +As you can imagine, for all this to work, the index would need to be **up to date** with the data in the database. + +If you had to update it **manually** in code, it would be very cumbersome and **error-prone**, as it would be easy to end up in a state where the index is not up to date and points to incorrect data. 😱 + +Here's the good news: when you create an index in a **SQL Database**, the database takes care of **updating** it **automatically** whenever it's necessary. 😎🎉 + +If you **add new records** to the `hero` table, the database will **automatically** update the index. It will do the **same process** of **finding** the right place to put the new index data (those **5 or 10 steps** described above), and then it will save the new index information there. The same would happen when you **update** or **delete** data. + +Defining and creating an index is very **easy** with SQL databases. And then **using it** is even easier... it's transparent. The database will figure out which index to use automatically, the SQL queries don't even change. + +So, in SQL databases **indexes are great**! And are super **easy to use**. Why not just have indexes for everything? .....Because indexes also have a "**cost**" in computation and storage (disk space). + +## Index Cost + +There's a **cost** associated with **indexes**. 💰 + +When you don't have an index and add a **new row** to the table `hero`, the database has to perform **1 operation** to add the new hero row at the end of the table. + +But if you have an **index** for the **hero names**, now the database has to perform the same **1 operation** to add that row **plus** some extra **5 or 10 operations** in the index, to find the right spot for the name, to then add that **index record** there. + +And if you have an index for the `name`, one for the `age`, and one for the `secret_name`, now the database has to perform the same **1 operation** to add that row **plus** some extra **5 or 10 operations** in the index **times 3**, for each of the indexes. This means that now adding one row takes something like **31 operations**. + +This also means that you are **exchanging** the time it takes to **read** data for the time it takes to **write** data plus some extra **space** in the database. + +If you have queries that get data out of the database comparing each one of those fields (for example using `WHERE`), then it makes total sense to have indexes for each one of them. Because **31 operations** while creating or updating data (plus the space of the index) is much, much better than the possible **500 or 1000 operations** to read all the rows to be able to compare them using each field. + +But if you **never** have queries that find records by the `secret_name` (you never use `secret_name` in the `WHERE` part) it probably doesn't make sense to have an index for the `secret_name` field/column, as that will increase the computational and space **cost** of writing and updating the database. + +## Create an Index with SQL + +Phew, that was a lot of theory and explanations. 😅 + +The most important thing about indexes is **understanding** them, how, and when to use them. + +Let's now see the **SQL** syntax to create an **index**. It is very simple: + +```SQL hl_lines="3" +CREATE INDEX ix_hero_name +ON hero (name) +``` + +This means, more or less: + +> Hey SQL database 👋, please `CREATE` an `INDEX` for me. +> +> I want the name of the index to be `ix_hero_name`. +> +> This index should be `ON` the table `hero`, it refers to that table. +> +> The column I want you to use for it is `name`. + +## Declare Indexes with SQLModel + +And now let's see how to define indexes in **SQLModel**. + +The change in code is underwhelming, it's very simple. 😆 + +Here's the `Hero` model we had before: + +```Python hl_lines="8" +{!./docs_src/tutorial/where/tutorial001.py[ln:1-10]!} + +# Code below omitted 👇 +``` + +
+👀 Full file preview + +```Python +{!./docs_src/tutorial/where/tutorial001.py!} +``` + +
+ +Let's now update it to tell **SQLModel** to create an index for the `name` field when creating the table: + +```Python hl_lines="8" +{!./docs_src/tutorial/indexes/tutorial001.py[ln:1-10]!} + +# Code below omitted 👇 +``` + +
+👀 Full file preview + +```Python +{!./docs_src/tutorial/indexes/tutorial001.py!} +``` + +
+ +We use the same `Field()` again as we did before, and set `index=True`. That's it! 🚀 + +Notice that we didn't set an argument of `default=None` or anything similar. This means that **SQLModel** (thanks to Pydantic) will keep it as a **required** field. + +!!! info + SQLModel (actually SQLAlchemy) will **automatically generate the index name** for you. + + In this case the generated name would be `ix_hero_name`. + +## Query Data + +Now, to query the data using the field `name` and the new index we don't have to do anything special or different in the code, it's just **the same code**. + +The SQL database will figure it out **automatically**. ✨ + +This is great because it means that indexes are very **simple to use**. But it might also feel counterintuitive at first, as you are **not doing anything** explicitly in the code to make it obvious that the index is useful, it all happens in the database behind the scenes. + +```Python hl_lines="5" +# Code above omitted 👆 + +{!./docs_src/tutorial/indexes/tutorial001.py[ln:36-41]!} + +# Code below omitted 👇 +``` + +
+👀 Full file preview + +```Python +{!./docs_src/tutorial/indexes/tutorial001.py!} +``` + +
+ +This is exactly the same code as we had before, but now the database will **use the index** underneath. + +## Run the Program + +If you run the program now, you will see an output like this: + +
+ +```console +$ python app.py + +// Some boilerplate output omitted 😉 + +// Create the table +CREATE TABLE hero ( + id INTEGER, + name VARCHAR NOT NULL, + secret_name VARCHAR NOT NULL, + age INTEGER, + PRIMARY KEY (id) +) + +// Create the index 🤓🎉 +CREATE INDEX ix_hero_name ON hero (name) + +// The SELECT with WHERE looks the same +INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age +FROM hero +WHERE hero.name = ? +INFO Engine [no key 0.00014s] ('Deadpond',) + +// The resulting hero +secret_name='Dive Wilson' age=None id=1 name='Deadpond' +``` + +
+ +## More Indexes + +We are going to query the `hero` table doing comparisons on the `age` field too, so we should **define an index** for that one as well: + +```Python hl_lines="10" +{!./docs_src/tutorial/indexes/tutorial002.py[ln:1-10]!} + +# Code below omitted 👇 +``` + +
+👀 Full file preview + +```Python +{!./docs_src/tutorial/indexes/tutorial002.py!} +``` + +
+ +In this case, we want the default value of `age` to continue being `None`, so we set `default=None` when using `Field()`. + +Now when we use **SQLModel** to create the database and tables, it will also create the **indexes** for these two columns in the `hero` table. + +So, when we query the database for the `hero` table and use those **two columns** to define what data we get, the database will be able to **use those indexes** to improve the **reading performance**. 🚀 + +## Primary Key and Indexes + +You probably noticed that we didn't set `index=True` for the `id` field. + +Because the `id` is already the **primary key**, the database will automatically create an internal **index** for it. + +The database always creates an internal index for **primary keys** automatically, as those are the primary way to organize, store, and retrieve data. 🤓 + +But if you want to be **frequently querying** the SQL database for any **other field** (e.g. using any other field in the `WHERE` section), you will probably want to have at least an **index** for that. + +## Recap + +**Indexes** are very important to improve **reading performance** and speed when querying the database. 🏎 + +Creating and using them is very **simple** and easy. The most important part is to understand **how** they work, **when** to create them, and for **which columns**. diff --git a/docs/tutorial/one.md b/docs/tutorial/one.md index 901199b..3b60653 100644 --- a/docs/tutorial/one.md +++ b/docs/tutorial/one.md @@ -18,7 +18,7 @@ We'll continue with the same examples we have been using in the previous chapter 👀 Full file preview ```Python -{!./docs_src/tutorial/where/tutorial006.py!} +{!./docs_src/tutorial/indexes/tutorial002.py!} ``` @@ -32,7 +32,7 @@ We have been iterating over the rows in a `result` object like: ```Python hl_lines="7-8" # Code above omitted 👆 -{!./docs_src/tutorial/where/tutorial006.py[ln:44-49]!} +{!./docs_src/tutorial/indexes/tutorial002.py[ln:44-49]!} # Code below omitted 👇 ``` @@ -41,7 +41,7 @@ We have been iterating over the rows in a `result` object like: 👀 Full file preview ```Python -{!./docs_src/tutorial/where/tutorial006.py!} +{!./docs_src/tutorial/indexes/tutorial002.py!} ``` diff --git a/docs/tutorial/update.md b/docs/tutorial/update.md index 420616d..b3099f5 100644 --- a/docs/tutorial/update.md +++ b/docs/tutorial/update.md @@ -10,7 +10,7 @@ As before, we'll continue from where we left off with the previous code. 👀 Full file preview ```Python -{!./docs_src/tutorial/where/tutorial006.py!} +{!./docs_src/tutorial/indexes/tutorial002.py!} ``` diff --git a/docs/tutorial/where.md b/docs/tutorial/where.md index fd80712..45e909c 100644 --- a/docs/tutorial/where.md +++ b/docs/tutorial/where.md @@ -233,7 +233,7 @@ The object returned by `select(Hero)` is a special type of object with some meth One of those methods is `.where()` used to (unsurprisingly) add a `WHERE` to the SQL statement in that **select** object. -There are other methods that will will explore later. 💡 +There are other methods that we will explore later. 💡 Most of these methods return the same object again after modifying it. @@ -698,7 +698,7 @@ age=35 id=5 name='Black Lion' secret_name='Trevor Challa' Here's a good moment to see that being able to use these pure Python expressions instead of keyword arguments can help a lot. ✨ -We can use the same standard Python comparison operators like `. +We can use the same standard Python comparison operators like `<`, `<=`, `>`, `>=`, `==`, etc. ## Multiple `.where()` @@ -933,3 +933,7 @@ And with that the editor knows this code is actually fine, because this is a spe ## Recap You can use `.where()` with powerful expressions using **SQLModel** columns (the special class attributes) to filter the rows that you want. 🚀 + +Up to now, the database would have been **looking through each one of the records** (rows) to find the ones that match what you want. If you have thousands or millions of records, this could be very **slow**. 😱 + +In the next section I'll tell you how to add **indexes** to the database, this is what will make the queries **very efficient**. 😎 diff --git a/docs_src/advanced/decimal/tutorial001.py b/docs_src/advanced/decimal/tutorial001.py index 1b16770..b803119 100644 --- a/docs_src/advanced/decimal/tutorial001.py +++ b/docs_src/advanced/decimal/tutorial001.py @@ -6,9 +6,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) money: condecimal(max_digits=5, decimal_places=3) = Field(default=0) diff --git a/docs_src/tutorial/code_structure/tutorial001/models.py b/docs_src/tutorial/code_structure/tutorial001/models.py index 9bd1fa9..8e2647b 100644 --- a/docs_src/tutorial/code_structure/tutorial001/models.py +++ b/docs_src/tutorial/code_structure/tutorial001/models.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, SQLModel class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") @@ -13,9 +13,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/code_structure/tutorial002/hero_model.py b/docs_src/tutorial/code_structure/tutorial002/hero_model.py index 84fc7f2..06dd9c6 100644 --- a/docs_src/tutorial/code_structure/tutorial002/hero_model.py +++ b/docs_src/tutorial/code_structure/tutorial002/hero_model.py @@ -8,9 +8,9 @@ if TYPE_CHECKING: class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional["Team"] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/code_structure/tutorial002/team_model.py b/docs_src/tutorial/code_structure/tutorial002/team_model.py index 54974a0..c8a008b 100644 --- a/docs_src/tutorial/code_structure/tutorial002/team_model.py +++ b/docs_src/tutorial/code_structure/tutorial002/team_model.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") diff --git a/docs_src/tutorial/connect/create_tables/tutorial001.py b/docs_src/tutorial/connect/create_tables/tutorial001.py index 86dcc9a..ef24ec7 100644 --- a/docs_src/tutorial/connect/create_tables/tutorial001.py +++ b/docs_src/tutorial/connect/create_tables/tutorial001.py @@ -5,15 +5,15 @@ from sqlmodel import Field, SQLModel, create_engine class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/delete/tutorial001.py b/docs_src/tutorial/connect/delete/tutorial001.py index 57bbd0e..eeb376a 100644 --- a/docs_src/tutorial/connect/delete/tutorial001.py +++ b/docs_src/tutorial/connect/delete/tutorial001.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/insert/tutorial001.py b/docs_src/tutorial/connect/insert/tutorial001.py index d64d37f..dc3661d 100644 --- a/docs_src/tutorial/connect/insert/tutorial001.py +++ b/docs_src/tutorial/connect/insert/tutorial001.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/select/tutorial001.py b/docs_src/tutorial/connect/select/tutorial001.py index 18c4f40..d4cdf41 100644 --- a/docs_src/tutorial/connect/select/tutorial001.py +++ b/docs_src/tutorial/connect/select/tutorial001.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/select/tutorial002.py b/docs_src/tutorial/connect/select/tutorial002.py index f7df277..59edbf7 100644 --- a/docs_src/tutorial/connect/select/tutorial002.py +++ b/docs_src/tutorial/connect/select/tutorial002.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/select/tutorial003.py b/docs_src/tutorial/connect/select/tutorial003.py index 110cace..fb5b8aa 100644 --- a/docs_src/tutorial/connect/select/tutorial003.py +++ b/docs_src/tutorial/connect/select/tutorial003.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/select/tutorial004.py b/docs_src/tutorial/connect/select/tutorial004.py index 87e739a..d1d260b 100644 --- a/docs_src/tutorial/connect/select/tutorial004.py +++ b/docs_src/tutorial/connect/select/tutorial004.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/select/tutorial005.py b/docs_src/tutorial/connect/select/tutorial005.py index 0e696d0..a61ef8a 100644 --- a/docs_src/tutorial/connect/select/tutorial005.py +++ b/docs_src/tutorial/connect/select/tutorial005.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/connect/update/tutorial001.py b/docs_src/tutorial/connect/update/tutorial001.py index 3c9726f..0080340 100644 --- a/docs_src/tutorial/connect/update/tutorial001.py +++ b/docs_src/tutorial/connect/update/tutorial001.py @@ -5,15 +5,15 @@ from sqlmodel import Field, Session, SQLModel, create_engine class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/delete/tutorial001.py b/docs_src/tutorial/delete/tutorial001.py index 0f7c056..7c911df 100644 --- a/docs_src/tutorial/delete/tutorial001.py +++ b/docs_src/tutorial/delete/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/delete/tutorial002.py b/docs_src/tutorial/delete/tutorial002.py index 1f26711..202d63b 100644 --- a/docs_src/tutorial/delete/tutorial002.py +++ b/docs_src/tutorial/delete/tutorial002.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py index 02f5ab8..88b8fbb 100644 --- a/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py +++ b/docs_src/tutorial/fastapi/app_testing/tutorial001/main.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class Hero(HeroBase, table=True): diff --git a/docs_src/tutorial/fastapi/delete/tutorial001.py b/docs_src/tutorial/fastapi/delete/tutorial001.py index 19ab2bb..3c15efb 100644 --- a/docs_src/tutorial/fastapi/delete/tutorial001.py +++ b/docs_src/tutorial/fastapi/delete/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class Hero(HeroBase, table=True): diff --git a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py index 9bdf604..aef2133 100644 --- a/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py +++ b/docs_src/tutorial/fastapi/limit_and_offset/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class Hero(HeroBase, table=True): diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial001.py b/docs_src/tutorial/fastapi/multiple_models/tutorial001.py index c46448b..df20123 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial001.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial001.py @@ -6,9 +6,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class HeroCreate(SQLModel): diff --git a/docs_src/tutorial/fastapi/multiple_models/tutorial002.py b/docs_src/tutorial/fastapi/multiple_models/tutorial002.py index 537e896..392c2c5 100644 --- a/docs_src/tutorial/fastapi/multiple_models/tutorial002.py +++ b/docs_src/tutorial/fastapi/multiple_models/tutorial002.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class Hero(HeroBase, table=True): diff --git a/docs_src/tutorial/fastapi/read_one/tutorial001.py b/docs_src/tutorial/fastapi/read_one/tutorial001.py index bc83db5..4d66e47 100644 --- a/docs_src/tutorial/fastapi/read_one/tutorial001.py +++ b/docs_src/tutorial/fastapi/read_one/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class Hero(HeroBase, table=True): diff --git a/docs_src/tutorial/fastapi/relationships/tutorial001.py b/docs_src/tutorial/fastapi/relationships/tutorial001.py index da21e46..97220b9 100644 --- a/docs_src/tutorial/fastapi/relationships/tutorial001.py +++ b/docs_src/tutorial/fastapi/relationships/tutorial001.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, sele class TeamBase(SQLModel): - name: str + name: str = Field(index=True) headquarters: str @@ -30,9 +30,9 @@ class TeamUpdate(SQLModel): class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/fastapi/response_model/tutorial001.py b/docs_src/tutorial/fastapi/response_model/tutorial001.py index 5f7a190..57d8738 100644 --- a/docs_src/tutorial/fastapi/response_model/tutorial001.py +++ b/docs_src/tutorial/fastapi/response_model/tutorial001.py @@ -6,9 +6,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py index 02f5ab8..88b8fbb 100644 --- a/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py +++ b/docs_src/tutorial/fastapi/session_with_dependency/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class Hero(HeroBase, table=True): diff --git a/docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py b/docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py index 715836c..41eaa62 100644 --- a/docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py +++ b/docs_src/tutorial/fastapi/simple_hero_api/tutorial001.py @@ -6,9 +6,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/fastapi/teams/tutorial001.py b/docs_src/tutorial/fastapi/teams/tutorial001.py index dc3e093..e8f88b8 100644 --- a/docs_src/tutorial/fastapi/teams/tutorial001.py +++ b/docs_src/tutorial/fastapi/teams/tutorial001.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, sele class TeamBase(SQLModel): - name: str + name: str = Field(index=True) headquarters: str @@ -30,9 +30,9 @@ class TeamUpdate(SQLModel): class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") diff --git a/docs_src/tutorial/fastapi/update/tutorial001.py b/docs_src/tutorial/fastapi/update/tutorial001.py index 9309d62..3555487 100644 --- a/docs_src/tutorial/fastapi/update/tutorial001.py +++ b/docs_src/tutorial/fastapi/update/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class HeroBase(SQLModel): - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) class Hero(HeroBase, table=True): diff --git a/docs_src/tutorial/indexes/__init__.py b/docs_src/tutorial/indexes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/tutorial/indexes/tutorial001.py b/docs_src/tutorial/indexes/tutorial001.py new file mode 100644 index 0000000..539220c --- /dev/null +++ b/docs_src/tutorial/indexes/tutorial001.py @@ -0,0 +1,51 @@ +from typing import Optional + +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str = Field(index=True) + secret_name: str + age: Optional[int] = Field(default=None, index=True) + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +engine = create_engine(sqlite_url, echo=True) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def create_heroes(): + hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") + hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador") + hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48) + + with Session(engine) as session: + session.add(hero_1) + session.add(hero_2) + session.add(hero_3) + + session.commit() + + +def select_heroes(): + with Session(engine) as session: + statement = select(Hero).where(Hero.name == "Deadpond") + results = session.exec(statement) + for hero in results: + print(hero) + + +def main(): + create_db_and_tables() + create_heroes() + select_heroes() + + +if __name__ == "__main__": + main() diff --git a/docs_src/tutorial/indexes/tutorial002.py b/docs_src/tutorial/indexes/tutorial002.py new file mode 100644 index 0000000..ebc8d64 --- /dev/null +++ b/docs_src/tutorial/indexes/tutorial002.py @@ -0,0 +1,59 @@ +from typing import Optional + +from sqlmodel import Field, Session, SQLModel, create_engine, select + + +class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str = Field(index=True) + secret_name: str + age: Optional[int] = Field(default=None, index=True) + + +sqlite_file_name = "database.db" +sqlite_url = f"sqlite:///{sqlite_file_name}" + +engine = create_engine(sqlite_url, echo=True) + + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + + +def create_heroes(): + hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson") + hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador") + hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48) + hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32) + hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35) + hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36) + hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93) + + with Session(engine) as session: + session.add(hero_1) + session.add(hero_2) + session.add(hero_3) + session.add(hero_4) + session.add(hero_5) + session.add(hero_6) + session.add(hero_7) + + session.commit() + + +def select_heroes(): + with Session(engine) as session: + statement = select(Hero).where(Hero.age <= 35) + results = session.exec(statement) + for hero in results: + print(hero) + + +def main(): + create_db_and_tables() + create_heroes() + select_heroes() + + +if __name__ == "__main__": + main() diff --git a/docs_src/tutorial/many_to_many/tutorial001.py b/docs_src/tutorial/many_to_many/tutorial001.py index aee77af..bb4e9d0 100644 --- a/docs_src/tutorial/many_to_many/tutorial001.py +++ b/docs_src/tutorial/many_to_many/tutorial001.py @@ -14,7 +14,7 @@ class HeroTeamLink(SQLModel, table=True): class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink) @@ -22,9 +22,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) teams: List[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink) diff --git a/docs_src/tutorial/many_to_many/tutorial002.py b/docs_src/tutorial/many_to_many/tutorial002.py index 123fa5a..dc4aa0b 100644 --- a/docs_src/tutorial/many_to_many/tutorial002.py +++ b/docs_src/tutorial/many_to_many/tutorial002.py @@ -14,7 +14,7 @@ class HeroTeamLink(SQLModel, table=True): class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink) @@ -22,9 +22,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) teams: List[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink) diff --git a/docs_src/tutorial/many_to_many/tutorial003.py b/docs_src/tutorial/many_to_many/tutorial003.py index c2f3d56..1e03c4a 100644 --- a/docs_src/tutorial/many_to_many/tutorial003.py +++ b/docs_src/tutorial/many_to_many/tutorial003.py @@ -18,7 +18,7 @@ class HeroTeamLink(SQLModel, table=True): class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str hero_links: List[HeroTeamLink] = Relationship(back_populates="team") @@ -26,9 +26,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_links: List[HeroTeamLink] = Relationship(back_populates="hero") diff --git a/docs_src/tutorial/offset_and_limit/tutorial001.py b/docs_src/tutorial/offset_and_limit/tutorial001.py index 5413c17..1b033ac 100644 --- a/docs_src/tutorial/offset_and_limit/tutorial001.py +++ b/docs_src/tutorial/offset_and_limit/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/offset_and_limit/tutorial002.py b/docs_src/tutorial/offset_and_limit/tutorial002.py index 9d85a13..65a6236 100644 --- a/docs_src/tutorial/offset_and_limit/tutorial002.py +++ b/docs_src/tutorial/offset_and_limit/tutorial002.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/offset_and_limit/tutorial003.py b/docs_src/tutorial/offset_and_limit/tutorial003.py index 5d2c3bf..36cae9c 100644 --- a/docs_src/tutorial/offset_and_limit/tutorial003.py +++ b/docs_src/tutorial/offset_and_limit/tutorial003.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/offset_and_limit/tutorial004.py b/docs_src/tutorial/offset_and_limit/tutorial004.py index bfa882d..a95715c 100644 --- a/docs_src/tutorial/offset_and_limit/tutorial004.py +++ b/docs_src/tutorial/offset_and_limit/tutorial004.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial001.py b/docs_src/tutorial/one/tutorial001.py index 9fa5f0b..072f4a3 100644 --- a/docs_src/tutorial/one/tutorial001.py +++ b/docs_src/tutorial/one/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial002.py b/docs_src/tutorial/one/tutorial002.py index a1d86e0..c244906 100644 --- a/docs_src/tutorial/one/tutorial002.py +++ b/docs_src/tutorial/one/tutorial002.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial003.py b/docs_src/tutorial/one/tutorial003.py index fe8c05c..f8fb0bc 100644 --- a/docs_src/tutorial/one/tutorial003.py +++ b/docs_src/tutorial/one/tutorial003.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial004.py b/docs_src/tutorial/one/tutorial004.py index 32bc9b9..8688fc4 100644 --- a/docs_src/tutorial/one/tutorial004.py +++ b/docs_src/tutorial/one/tutorial004.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial005.py b/docs_src/tutorial/one/tutorial005.py index 2382136..f624d4c 100644 --- a/docs_src/tutorial/one/tutorial005.py +++ b/docs_src/tutorial/one/tutorial005.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial006.py b/docs_src/tutorial/one/tutorial006.py index cf408c4..afc4854 100644 --- a/docs_src/tutorial/one/tutorial006.py +++ b/docs_src/tutorial/one/tutorial006.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial007.py b/docs_src/tutorial/one/tutorial007.py index 8a36d97..15df5ba 100644 --- a/docs_src/tutorial/one/tutorial007.py +++ b/docs_src/tutorial/one/tutorial007.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial008.py b/docs_src/tutorial/one/tutorial008.py index 1b0d6ef..9aa077c 100644 --- a/docs_src/tutorial/one/tutorial008.py +++ b/docs_src/tutorial/one/tutorial008.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/one/tutorial009.py b/docs_src/tutorial/one/tutorial009.py index 70deed2..f4ee23b 100644 --- a/docs_src/tutorial/one/tutorial009.py +++ b/docs_src/tutorial/one/tutorial009.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py b/docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py index d9851b4..fc4eb97 100644 --- a/docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py +++ b/docs_src/tutorial/relationship_attributes/back_populates/tutorial001.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, sele class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship() @@ -13,9 +13,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship() diff --git a/docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py b/docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py index b33fabe..a25df4e 100644 --- a/docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py +++ b/docs_src/tutorial/relationship_attributes/back_populates/tutorial002.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, sele class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") @@ -13,9 +13,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/relationship_attributes/back_populates/tutorial003.py b/docs_src/tutorial/relationship_attributes/back_populates/tutorial003.py index cbd1581..c137f58 100644 --- a/docs_src/tutorial/relationship_attributes/back_populates/tutorial003.py +++ b/docs_src/tutorial/relationship_attributes/back_populates/tutorial003.py @@ -5,14 +5,14 @@ from sqlmodel import Field, Relationship, SQLModel, create_engine class Weapon(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) hero: "Hero" = Relationship(back_populates="weapon") class Power(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) hero_id: int = Field(foreign_key="hero.id") hero: "Hero" = Relationship(back_populates="powers") @@ -20,7 +20,7 @@ class Power(SQLModel, table=True): class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") @@ -28,9 +28,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py b/docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py index 2bf2041..ec9c909 100644 --- a/docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py +++ b/docs_src/tutorial/relationship_attributes/create_and_update_relationships/tutorial001.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") @@ -13,9 +13,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py b/docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py index 98c1919..71cb3f6 100644 --- a/docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py +++ b/docs_src/tutorial/relationship_attributes/define_relationship_attributes/tutorial001.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") @@ -13,9 +13,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py b/docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py index e5c23a7..5f718ca 100644 --- a/docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py +++ b/docs_src/tutorial/relationship_attributes/read_relationships/tutorial001.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, sele class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") @@ -13,9 +13,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py b/docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py index efae8e5..fdb436e 100644 --- a/docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py +++ b/docs_src/tutorial/relationship_attributes/read_relationships/tutorial002.py @@ -5,7 +5,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, sele class Team(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) headquarters: str heroes: List["Hero"] = Relationship(back_populates="team") @@ -13,9 +13,9 @@ class Team(SQLModel, table=True): class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship(back_populates="heroes") diff --git a/docs_src/tutorial/update/tutorial001.py b/docs_src/tutorial/update/tutorial001.py index 96c7208..37868ac 100644 --- a/docs_src/tutorial/update/tutorial001.py +++ b/docs_src/tutorial/update/tutorial001.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/update/tutorial002.py b/docs_src/tutorial/update/tutorial002.py index 04185f8..4880f73 100644 --- a/docs_src/tutorial/update/tutorial002.py +++ b/docs_src/tutorial/update/tutorial002.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/update/tutorial003.py b/docs_src/tutorial/update/tutorial003.py index c319915..fd2ed75 100644 --- a/docs_src/tutorial/update/tutorial003.py +++ b/docs_src/tutorial/update/tutorial003.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/docs_src/tutorial/update/tutorial004.py b/docs_src/tutorial/update/tutorial004.py index e61a04f..868c66c 100644 --- a/docs_src/tutorial/update/tutorial004.py +++ b/docs_src/tutorial/update/tutorial004.py @@ -5,9 +5,9 @@ from sqlmodel import Field, Session, SQLModel, create_engine, select class Hero(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) - name: str + name: str = Field(index=True) secret_name: str - age: Optional[int] = None + age: Optional[int] = Field(default=None, index=True) sqlite_file_name = "database.db" diff --git a/mkdocs.yml b/mkdocs.yml index 41b44b6..13744db 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,7 @@ theme: features: - search.suggest - search.highlight + - content.tabs.link icon: repo: fontawesome/brands/github-alt logo: img/icon-white.svg @@ -43,6 +44,7 @@ nav: - tutorial/automatic-id-none-refresh.md - tutorial/select.md - tutorial/where.md + - tutorial/indexes.md - tutorial/one.md - tutorial/limit-and-offset.md - tutorial/update.md @@ -103,7 +105,8 @@ markdown_extensions: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_div_format '' -- pymdownx.tabbed +- pymdownx.tabbed: + alternate_style: true - mdx_include extra: diff --git a/sqlmodel/main.py b/sqlmodel/main.py index 08eaf59..12f30ba 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -426,7 +426,7 @@ def get_column_from_field(field: ModelField) -> Column: # type: ignore nullable = not field.required index = getattr(field.field_info, "index", Undefined) if index is Undefined: - index = True + index = False if hasattr(field.field_info, "nullable"): field_nullable = getattr(field.field_info, "nullable") if field_nullable != Undefined: diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py index ac85eca..cf00856 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial001.py @@ -1,4 +1,6 @@ from fastapi.testclient import TestClient +from sqlalchemy import inspect +from sqlalchemy.engine.reflection import Inspector from sqlmodel import create_engine from sqlmodel.pool import StaticPool @@ -166,3 +168,16 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text assert data == openapi_schema + + # Test inherited indexes + insp: Inspector = inspect(mod.engine) + indexes = insp.get_indexes(str(mod.Hero.__tablename__)) + expected_indexes = [ + {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, + {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, + ] + for index in expected_indexes: + assert index in indexes, "This expected index should be in the indexes in DB" + # Now that this index was checked, remove it from the list of indexes + indexes.pop(indexes.index(index)) + assert len(indexes) == 0, "The database should only have the expected indexes" diff --git a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py index 421a1ca..57393a7 100644 --- a/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py +++ b/tests/test_tutorial/test_fastapi/test_multiple_models/test_tutorial002.py @@ -1,4 +1,6 @@ from fastapi.testclient import TestClient +from sqlalchemy import inspect +from sqlalchemy.engine.reflection import Inspector from sqlmodel import create_engine from sqlmodel.pool import StaticPool @@ -166,3 +168,16 @@ def test_tutorial(clear_sqlmodel): assert response.status_code == 200, response.text assert data == openapi_schema + + # Test inherited indexes + insp: Inspector = inspect(mod.engine) + indexes = insp.get_indexes(str(mod.Hero.__tablename__)) + expected_indexes = [ + {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, + {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, + ] + for index in expected_indexes: + assert index in indexes, "This expected index should be in the indexes in DB" + # Now that this index was checked, remove it from the list of indexes + indexes.pop(indexes.index(index)) + assert len(indexes) == 0, "The database should only have the expected indexes" diff --git a/tests/test_tutorial/test_indexes/__init__.py b/tests/test_tutorial/test_indexes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_tutorial/test_indexes/test_tutorial001.py b/tests/test_tutorial/test_indexes/test_tutorial001.py new file mode 100644 index 0000000..5962077 --- /dev/null +++ b/tests/test_tutorial/test_indexes/test_tutorial001.py @@ -0,0 +1,35 @@ +from unittest.mock import patch + +from sqlalchemy import inspect +from sqlalchemy.engine.reflection import Inspector +from sqlmodel import create_engine + +from ...conftest import get_testing_print_function + + +def test_tutorial(clear_sqlmodel): + from docs_src.tutorial.indexes import tutorial001 as mod + + mod.sqlite_url = "sqlite://" + mod.engine = create_engine(mod.sqlite_url) + calls = [] + + new_print = get_testing_print_function(calls) + + with patch("builtins.print", new=new_print): + mod.main() + assert calls == [ + [{"secret_name": "Dive Wilson", "age": None, "id": 1, "name": "Deadpond"}] + ] + + insp: Inspector = inspect(mod.engine) + indexes = insp.get_indexes(str(mod.Hero.__tablename__)) + expected_indexes = [ + {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, + {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, + ] + for index in expected_indexes: + assert index in indexes, "This expected index should be in the indexes in DB" + # Now that this index was checked, remove it from the list of indexes + indexes.pop(indexes.index(index)) + assert len(indexes) == 0, "The database should only have the expected indexes" diff --git a/tests/test_tutorial/test_indexes/test_tutorial006.py b/tests/test_tutorial/test_indexes/test_tutorial006.py new file mode 100644 index 0000000..e26f8b2 --- /dev/null +++ b/tests/test_tutorial/test_indexes/test_tutorial006.py @@ -0,0 +1,36 @@ +from unittest.mock import patch + +from sqlalchemy import inspect +from sqlalchemy.engine.reflection import Inspector +from sqlmodel import create_engine + +from ...conftest import get_testing_print_function + + +def test_tutorial(clear_sqlmodel): + from docs_src.tutorial.indexes import tutorial002 as mod + + mod.sqlite_url = "sqlite://" + mod.engine = create_engine(mod.sqlite_url) + calls = [] + + new_print = get_testing_print_function(calls) + + with patch("builtins.print", new=new_print): + mod.main() + assert calls == [ + [{"name": "Tarantula", "secret_name": "Natalia Roman-on", "age": 32, "id": 4}], + [{"name": "Black Lion", "secret_name": "Trevor Challa", "age": 35, "id": 5}], + ] + + insp: Inspector = inspect(mod.engine) + indexes = insp.get_indexes(str(mod.Hero.__tablename__)) + expected_indexes = [ + {"name": "ix_hero_name", "column_names": ["name"], "unique": 0}, + {"name": "ix_hero_age", "column_names": ["age"], "unique": 0}, + ] + for index in expected_indexes: + assert index in indexes, "This expected index should be in the indexes in DB" + # Now that this index was checked, remove it from the list of indexes + indexes.pop(indexes.index(index)) + assert len(indexes) == 0, "The database should only have the expected indexes" diff --git a/tests/test_tutorial/test_where/test_tutorial003.py b/tests/test_tutorial/test_where/test_tutorial003.py index a01955e..4794d84 100644 --- a/tests/test_tutorial/test_where/test_tutorial003.py +++ b/tests/test_tutorial/test_where/test_tutorial003.py @@ -17,7 +17,7 @@ def test_tutorial(clear_sqlmodel): with patch("builtins.print", new=new_print): mod.main() - assert calls == [ + expected_calls = [ [{"id": 6, "name": "Dr. Weird", "secret_name": "Steve Weird", "age": 36}], [{"id": 3, "name": "Rusty-Man", "secret_name": "Tommy Sharp", "age": 48}], [ @@ -29,3 +29,8 @@ def test_tutorial(clear_sqlmodel): } ], ] + for call in expected_calls: + assert call in calls, "This expected item should be in the list" + # Now that this item was checked, remove it from the list + calls.pop(calls.index(call)) + assert len(calls) == 0, "The list should only have the expected items" diff --git a/tests/test_tutorial/test_where/test_tutorial004.py b/tests/test_tutorial/test_where/test_tutorial004.py index 9f4f80c..682babd 100644 --- a/tests/test_tutorial/test_where/test_tutorial004.py +++ b/tests/test_tutorial/test_where/test_tutorial004.py @@ -16,7 +16,7 @@ def test_tutorial(clear_sqlmodel): with patch("builtins.print", new=new_print): mod.main() - assert calls == [ + expected_calls = [ [{"id": 5, "name": "Black Lion", "secret_name": "Trevor Challa", "age": 35}], [{"id": 6, "name": "Dr. Weird", "secret_name": "Steve Weird", "age": 36}], [{"id": 3, "name": "Rusty-Man", "secret_name": "Tommy Sharp", "age": 48}], @@ -29,3 +29,8 @@ def test_tutorial(clear_sqlmodel): } ], ] + for call in expected_calls: + assert call in calls, "This expected item should be in the list" + # Now that this item was checked, remove it from the list + calls.pop(calls.index(call)) + assert len(calls) == 0, "The list should only have the expected items" diff --git a/tests/test_tutorial/test_where/test_tutorial011.py b/tests/test_tutorial/test_where/test_tutorial011.py index 743ecd5..8006cd0 100644 --- a/tests/test_tutorial/test_where/test_tutorial011.py +++ b/tests/test_tutorial/test_where/test_tutorial011.py @@ -16,7 +16,7 @@ def test_tutorial(clear_sqlmodel): with patch("builtins.print", new=new_print): mod.main() - assert calls == [ + expected_calls = [ [{"id": 5, "name": "Black Lion", "secret_name": "Trevor Challa", "age": 35}], [{"id": 6, "name": "Dr. Weird", "secret_name": "Steve Weird", "age": 36}], [{"id": 3, "name": "Rusty-Man", "secret_name": "Tommy Sharp", "age": 48}], @@ -29,3 +29,8 @@ def test_tutorial(clear_sqlmodel): } ], ] + for call in expected_calls: + assert call in calls, "This expected item should be in the list" + # Now that this item was checked, remove it from the list + calls.pop(calls.index(call)) + assert len(calls) == 0, "The list should only have the expected items"