Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 845d9120 authored by PAPERMAN Charles's avatar PAPERMAN Charles
Browse files

Merge branch 'hotfix/readme' into 'master'

Hotfix/readme

See merge request !73
parents 33ad554b 2bb13e86
No related branches found
No related tags found
1 merge request!73Hotfix/readme
Pipeline #869411 passed
...@@ -34,23 +34,23 @@ from uuid import UUID, uuid4 as uuid_gen ...@@ -34,23 +34,23 @@ from uuid import UUID, uuid4 as uuid_gen
from querybuilder.schemas.helper import table from querybuilder.schemas.helper import table
@table @table
class Person: class person:
id: UUID id: UUID
name: str name: str
birth_year: int birth_year: int
~~~ ~~~
This code snippet creates a (non-mutable) table `Person`. From those object we can build This code snippet creates a (non-mutable) table `person`. From those object we can build
`queries`. Those are not tied to any database. `queries`. Those are not tied to any database.
~~~python ~~~python
q = Person.select([Person.c.birth_year, Person.c.name]) # Person.c is a shorthand for Person.columns q = person.select([person.c.birth_year, person.c.name]) # person.c is a shorthand for person.columns
~~~ ~~~
We can then get the string representation of the query `q` by using `str(q)` which returns We can then get the string representation of the query `q` by using `str(q)` which returns
~~~sql ~~~sql
SELECT Person.birth_year, Person.name FROM Person SELECT person.birth_year, person.name FROM person
~~~ ~~~
We can apply simple transformation to `q`. As queries (and most object) We can apply simple transformation to `q`. As queries (and most object)
...@@ -63,7 +63,7 @@ q2 = q.add_where(q.c.birth_year.gt(1800)).set_limit(10).set_columns(["name"]) ...@@ -63,7 +63,7 @@ q2 = q.add_where(q.c.birth_year.gt(1800)).set_limit(10).set_columns(["name"])
Then, formatting the query with `str(q2)` gives: Then, formatting the query with `str(q2)` gives:
~~~sql ~~~sql
SELECT Person.name FROM Person WHERE Person.birth_year > 1800 FETCH FIRST 10 ROWS ONLY SELECT person.name FROM person WHERE person.birth_year > 1800 FETCH FIRST 10 ROWS ONLY
~~~ ~~~
## Using an actual database backend ## Using an actual database backend
...@@ -83,10 +83,10 @@ from querybuilder.drivers.sqlite.connector import Connector ...@@ -83,10 +83,10 @@ from querybuilder.drivers.sqlite.connector import Connector
db = Connector(":memory:") # The ":memory:" here is SQLite-specific for in-memory non-persistent database. db = Connector(":memory:") # The ":memory:" here is SQLite-specific for in-memory non-persistent database.
~~~ ~~~
We can create the table `Person` using `Person.create()` which produces the following query: We can create the table `person` using `person.create()` which produces the following query:
~~~sql ~~~sql
CREATE TABLE Person (id UUID, name TEXT, birth_year INTEGER) CREATE TABLE person (id UUID, name TEXT, birth_year INTEGER)
~~~ ~~~
SQLite does not support `UUID`. SQLite does not support `UUID`.
...@@ -101,19 +101,19 @@ UserWarning: sqlite does not support UUID, using TEXT type instead ...@@ -101,19 +101,19 @@ UserWarning: sqlite does not support UUID, using TEXT type instead
Here is how the query is executed in the SQLite database `db`: Here is how the query is executed in the SQLite database `db`:
~~~python ~~~python
db.execute(Person.create()) db.execute(person.create())
~~~ ~~~
We can then create a query to populate the table: We can then create a query to populate the table:
~~~python ~~~python
q_insert = Person.insert_values(in_columns=(Person.c[["name", "birth_year"]]), values=[("Dijkstra", 1930), ("Euler", 1707), ("Steiner", 1796)]) q_insert = person.insert_values(in_columns=(person.c[["name", "birth_year"]]), values=[("Dijkstra", 1930), ("Euler", 1707), ("Steiner", 1796)])
db.execute(q_insert) db.execute(q_insert)
~~~ ~~~
Now, our table admit three tuples: Now, our table admit three tuples:
~~~python ~~~python
cursor = db.execute(Person.select(Person.c[1:], orderby=Person.c.name)) cursor = db.execute(person.select(person.c[1:], orderby=person.c.name))
results = list(cursor.fetchall()) results = list(cursor.fetchall())
print(results) print(results)
~~~ ~~~
...@@ -132,22 +132,22 @@ The values are safely embedded within `q_insert` (click me to see details). ...@@ -132,22 +132,22 @@ The values are safely embedded within `q_insert` (click me to see details).
The query `q_insert` above actually embedded the values within its formatting as shown by its pretty printing `q_insert.pretty_print()`: The query `q_insert` above actually embedded the values within its formatting as shown by its pretty printing `q_insert.pretty_print()`:
~~~sql ~~~sql
INSERT INTO Person (name, age) VALUES (?, 1930), (?, 1707), (?, 1796) INSERT INTO person (name, birth_year) VALUES (?, 1930), (?, 1707), (?, 1796)
-- ↖{0: 'Dijkstra', 1: 'Euler', 2: 'Steiner'} -- ↖{0: 'Dijkstra', 1: 'Euler', 2: 'Steiner'}
~~~ ~~~
But it is still safe against injection as the string values will be passed as placeholders to an underlying prepared query: But it is still safe against injection as the string values will be passed as placeholders to an underlying prepared query:
~~~python ~~~python
q_inject = Person.insert_values(in_columns=Person.c[1:], values= [("\";DROP TABLE Person;", 0000)]) q_inject = person.insert_values(in_columns=person.c[1:], values= [("\";DROP TABLE person;", 0000)])
db.execute(q_inject) db.execute(q_inject)
cursor = db.execute(Person.select(Person.c[1:], where=Person.c.birth_year.eq(0))) cursor = db.execute(person.select(person.c[1:], where=person.c.birth_year.eq(0)))
results = list(cursor.fetchall()) results = list(cursor.fetchall())
print(results) print(results)
~~~ ~~~
gives: gives:
~~~python ~~~python
[('"; DROP TABLE Person;"', 0)] [('";DROP TABLE person;', 0)]
~~~ ~~~
</details> </details>
...@@ -155,23 +155,23 @@ gives: ...@@ -155,23 +155,23 @@ gives:
We can also define updates easily, _e.g._, with the following query: We can also define updates easily, _e.g._, with the following query:
~~~python ~~~python
q_upd = Person.update(dict(id=uuid_gen()), where=Person.c.name.eq("Dijkstra")) q_upd = person.update(dict(id=uuid_gen()), where=person.c.name.eq("Dijkstra"))
db.execute(q_upd) db.execute(q_upd)
~~~ ~~~
which pretty prints as: which pretty prints as:
~~~sql ~~~sql
UPDATE Person SET id = ? WHERE Person.name = ? UPDATE person SET id = ? WHERE person.name = ?
-- ↖{0: UUID('33525c22-f938-4256-b673-e595ff6df828'), 1: 'Dijkstra'} -- ↖{0: UUID('33525c22-f938-4256-b673-e595ff6df828'), 1: 'Dijkstra'}
~~~ ~~~
Of course, deletions are also possible, _e.g._: Of course, deletions are also possible, _e.g._:
~~~python ~~~python
q_del = Person.delete(where=Person.c.id.isnull()) q_del = person.delete(where=person.c.id.isnull())
db.execute(q_del) db.execute(q_del)
~~~ ~~~
is formatted as: is formatted as:
~~~sql ~~~sql
DELETE FROM Person WHERE Person.id IS NULL DELETE FROM person WHERE person.id IS NULL
~~~ ~~~
If we throw in more complicated tables, If we throw in more complicated tables,
...@@ -268,41 +268,40 @@ q_delete = tag.delete(where=tag.c.name.like("value_%")) ...@@ -268,41 +268,40 @@ q_delete = tag.delete(where=tag.c.name.like("value_%"))
db.execute(q_delete) db.execute(q_delete)
~~~ ~~~
Finally, we can build a last table with references to connect tag with Person: Finally, we can build a last table with references to connect tag with person:
~~~python ~~~python
@table @table
class tag_person: class tag_person:
person_id: Person.c.id person_id: person.c.id
tag_id: tag.c.id tag_id: tag.c.id
db.execute(tag_person.create()) db.execute(tag_person.create())
~~~ ~~~
The query given by `tag_person.create()` is: The query given by `str(tag_person.create())` is:
~~~sql ~~~sql
CREATE TABLE tag_person( CREATE TABLE tag_person (person_id UUID REFERENCES person(id), tag_id INTEGER REFERENCES tag(id))
person_id TEXT REFERENCES Person(id),
tag_id INTEGER REFERENCES tag(id)
)
~~~ ~~~
Again, within SQLite, the `UUID` type will fallback to `TEXT`.
Remark that it automatically infers the types and add the constraints associated with Remark that it automatically infers the types and add the constraints associated with
the table definitions. the table definitions.
Let us label Dijkstra as a Mathematician: Let us label Dijkstra as a Mathematician:
~~~python ~~~python
condition = Person.c.name.eq("Dijkstra") & tag.c.name.eq("mathematician") condition = person.c.name.eq("Dijkstra") & tag.c.name.eq("mathematician")
q_prod = Person.product(tag).select(columns=(Person.c.id, tag.c.id), where=condition) q_prod = person.product(tag).select(columns=(person.c.id, tag.c.id), where=condition)
~~~ ~~~
The query `q_prod` is of the following shape: The query `q_prod` is of the following shape:
~~~sql ~~~sql
SELECT Person.id, tag.id SELECT person.id, tag.id
FROM Person, tag FROM person, tag
WHERE Person.name = ? AND tag.name = ? WHERE person.name = ? AND tag.name = ?
-- ↖{0: 'Dijkstra', 1: 'mathematician'} -- ↖{0: 'Dijkstra', 1: 'mathematician'}
~~~ ~~~
...@@ -316,12 +315,12 @@ Let us check by getting the list of mathematicians using the following complex q ...@@ -316,12 +315,12 @@ Let us check by getting the list of mathematicians using the following complex q
~~~python ~~~python
q_math = tag_person\ q_math = tag_person\
.inner_join(tag, on=tag.c.id.eq(tag_person.c.tag_id))\ .inner_join(tag, on=tag.c.id.eq(tag_person.c.tag_id))\
.inner_join(Person, on=Person.c.id.eq(tag_person.c.person_id))\ .inner_join(person, on=person.c.id.eq(tag_person.c.person_id))\
.select([Person.c.name], where=tag.c.name.eq("mathematician")) .select([person.c.name], where=tag.c.name.eq("mathematician"))
~~~ ~~~
which is formatted as which is formatted as
~~~sql ~~~sql
SELECT Person.name FROM (tag_person INNER JOIN tag ON tag.tag_id = tag_person.id) INNER JOIN Person ON Person.id = tag_person.person_id WHERE tag.name = ? SELECT person.name FROM (tag_person INNER JOIN tag ON tag.id = tag_person.tag_id) INNER JOIN person ON person.id = tag_person.person_id WHERE tag.name = ?
~~~ ~~~
and whose execution and whose execution
~~~python ~~~python
...@@ -339,20 +338,20 @@ will return only `"Dijkstra"` ...@@ -339,20 +338,20 @@ will return only `"Dijkstra"`
The previous queries work seamlessly on PostgreSQL by simply using the PostgreSQL connector: The previous queries work seamlessly on PostgreSQL by simply using the PostgreSQL connector:
~~~python ~~~python
from querybuilder.drivers.postgresql.connector import Connector from querybuilder.drivers.postgres.connector import Connector
db = Connector() db = Connector() # Postgresql has default connection information
~~~ ~~~
The only differences are The only differences with respect to using the SQLite driver are:
1. We need to specify that `Person.id` is the primary key of `Person` 1. We need to specify that `person.id` is the primary key of `person`
to allow us to use it as a foreign key in `tag_person`; to allow us to use it as a foreign key in `tag_person`;
2. We do not have warnings, since PostgreSQL has implementations 2. We do not have warnings, since PostgreSQL has implementations
of type `UUID`, and of `GENERATED ALWAYS AS IDENTITY`. of type `UUID`, and of `GENERATED ALWAYS AS IDENTITY`.
~~~python ~~~python
@table @table
class Person: class person:
id : ColumnSpec(UUID, primary_key=True) id : ColumnSpec(UUID, primary_key=True)
name : str name : str
birth_year : int birth_year : int
...@@ -363,8 +362,8 @@ class tag: ...@@ -363,8 +362,8 @@ class tag:
@table @table
class tag_person: class tag_person:
tag_id: tag.c.id tag_id: tag.c.id
person_id: Person.c.id person_id: person.c.id
db.execute(Person.create(temporary=True)) db.execute(person.create(temporary=True))
db.execute(tag.create(temporary=True)) db.execute(tag.create(temporary=True))
db.execute(tag_person.create(temporary=True)) db.execute(tag_person.create(temporary=True))
~~~ ~~~
...@@ -372,7 +371,7 @@ db.execute(tag_person.create(temporary=True)) ...@@ -372,7 +371,7 @@ db.execute(tag_person.create(temporary=True))
Since PostgreSQL has no non-persistent in-memory database, Since PostgreSQL has no non-persistent in-memory database,
we indicated `temporary=True` when generating the creation query of each table, we indicated `temporary=True` when generating the creation query of each table,
so that `CREATE TEMPORARY TABLE` queries are produced. so that `CREATE TEMPORARY TABLE` queries are produced.
For instance, `str(Person.create(temporary=True))` gives the query: For instance, `str(person.create(temporary=True))` gives the query:
~~~sql ~~~sql
CREATE TEMPORARY TABLE Person (id UUID PRIMARY KEY, name TEXT, birth_year INTEGER) CREATE TEMPORARY TABLE person (id UUID PRIMARY KEY, name TEXT, birth_year INTEGER)
~~~ ~~~
"""
>>> import querybuilder as qb
>>> qb.settings['pretty_formatter'] = qb.formatting.formatter.StandardFormatter()
>>> from uuid import UUID, uuid4 as uuid_gen
>>> from querybuilder.schemas.helper import table
>>> @table
... class person:
... id: UUID
... name: str
... birth_year: int
>>> q = person.select([person.c.birth_year, person.c.name]) # person.c is a shorthand for person.columns
>>> str(q)
'SELECT person.birth_year, person.name FROM person'
>>> q2 = q.add_where(q.c.birth_year.gt(1800)).set_limit(10).set_columns(["name"])
>>> str(q2)
'SELECT person.name FROM person WHERE person.birth_year > 1800 FETCH FIRST 10 ROWS ONLY'
>>> from querybuilder.drivers.sqlite.connector import Connector
>>> db = Connector(":memory:") # The ":memory:" here is SQLite-specific for in-memory non-persistent database.
>>> str(person.create())
'CREATE TABLE person (id UUID, name TEXT, birth_year INTEGER)'
>>> _ = db.execute(person.create())
>>> q_insert = person.insert_values(in_columns=(person.c[["name", "birth_year"]]), values=[("Dijkstra", 1930), ("Euler", 1707), ("Steiner", 1796)])
>>> q_insert.pretty_print()
INSERT INTO person (name, birth_year) VALUES (?, 1930), (?, 1707), (?, 1796)
-- {0: 'Dijkstra', 1: 'Euler', 2: 'Steiner'}
>>> _ = db.execute(q_insert)
>>> cursor = db.execute(person.select(person.c[1:], orderby=person.c.name))
>>> list(cursor.fetchall())
[('Dijkstra', 1930), ('Euler', 1707), ('Steiner', 1796)]
>>> q_inject = person.insert_values(in_columns=person.c[1:], values= [("\\";DROP TABLE person;", 0000)])
>>> _ = db.execute(q_inject)
>>> cursor = db.execute(person.select(person.c[1:], where=person.c.birth_year.eq(0)))
>>> list(cursor.fetchall())
[('";DROP TABLE person;', 0)]
>>> q_upd = person.update(dict(id=uuid_gen()), where=person.c.name.eq("Dijkstra"))
>>> q_upd.pretty_print() # doctest: +ELLIPSIS
UPDATE person SET id = ? WHERE person.name = ?
-- {0: UUID('...'), 1: 'Dijkstra'}
>>> _ = db.execute(q_upd)
>>> q_del = person.delete(where=person.c.id.isnull())
>>> str(q_del)
'DELETE FROM person WHERE person.id IS NULL'
>>> _ = db.execute(q_del)
>>> from querybuilder.schemas.helper import table, ColumnSpec
>>> @table
... class tag:
... id: ColumnSpec(int, primary_key=True, generated_as_identity=True)
... name: str
>>> str(tag.create())
'CREATE TABLE tag (id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, name TEXT)'
>>> db.stringify(tag.create())
'CREATE TABLE tag (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)'
>>> _ = db.execute(tag.create())
>>> q_tag = tag.insert_values(in_columns=(tag.c.name,), values=[("mathematician",)])
>>> q_tag.pretty_print()
INSERT INTO tag (name) VALUES (?) -- {0: 'mathematician'}
>>> _ = db.execute(q_tag)
>>> q_tag_bulk = tag.insert_many(in_columns=(tag.c.name,))
>>> q_tag_bulk.pretty_print()
INSERT INTO tag (name) VALUES (:name) -- {name: ø}
>>> _ = db.executemany(q_tag_bulk, [dict(name=f"value_{i}") for i in range(1000)])
>>> q_delete = tag.delete(where=tag.c.name.like("value_%"))
>>> _ = db.execute(q_delete)
>>> @table
... class tag_person:
... person_id: person.c.id
... tag_id: tag.c.id
>>> str(tag_person.create())
'CREATE TABLE tag_person (person_id UUID REFERENCES person(id), tag_id INTEGER REFERENCES tag(id))'
>>> db.stringify(tag_person.create())
'CREATE TABLE tag_person (person_id TEXT REFERENCES person(id), tag_id INTEGER REFERENCES tag(id))'
>>> _ = db.execute(tag_person.create())
>>> condition = person.c.name.eq("Dijkstra") & tag.c.name.eq("mathematician")
>>> str(condition)
'person.name = ? AND tag.name = ?'
>>> q_prod = person.product(tag).select(columns=(person.c.id, tag.c.id), where=condition)
>>> q_prod.pretty_print()
SELECT person.id, tag.id FROM person, tag WHERE person.name = ? AND tag.name = ?
-- {0: 'Dijkstra', 1: 'mathematician'}
>>> _ = db.execute(tag_person.insert(query=q_prod))
>>> q_math = tag_person.inner_join(tag, on=tag.c.id.eq(tag_person.c.tag_id)).inner_join(person, on=person.c.id.eq(tag_person.c.person_id)).select([person.c.name], where=tag.c.name.eq("mathematician"))
>>> str(q_math)
'SELECT person.name FROM (tag_person INNER JOIN tag ON tag.id = tag_person.tag_id) INNER JOIN person ON person.id = tag_person.person_id WHERE tag.name = ?'
>>> cursor = db.execute(q_math)
>>> list(cursor.fetchall())
[('Dijkstra',)]
>>> from querybuilder.drivers.postgres.connector import Connector
>>> db = Connector()
>>> @table
... class person:
... id : ColumnSpec(UUID, primary_key=True)
... name : str
... birth_year : int
>>> @table
... class tag:
... id: ColumnSpec(int, primary_key=True, generated_as_identity=True)
... name: str
>>> @table
... class tag_person:
... tag_id: tag.c.id
... person_id: person.c.id
>>> _ = db.execute(person.create(temporary=True))
>>> _ = db.execute(tag.create(temporary=True))
>>> _ = db.execute(tag_person.create(temporary=True))
>>> str(person.create(temporary=True))
'CREATE TEMPORARY TABLE person (id UUID PRIMARY KEY, name TEXT, birth_year INTEGER)'
"""
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment