django-sql-compiler 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_sql_compiler-0.2.0/CHANGELOG.md +44 -0
- django_sql_compiler-0.2.0/LICENSE +21 -0
- django_sql_compiler-0.2.0/PKG-INFO +141 -0
- {django-sql-compiler-0.1.0 → django_sql_compiler-0.2.0}/README.md +20 -9
- django_sql_compiler-0.2.0/pyproject.toml +72 -0
- django_sql_compiler-0.2.0/sql_compiler/compilers/__init__.py +24 -0
- {django-sql-compiler-0.1.0 → django_sql_compiler-0.2.0}/sql_compiler/compilers/base.py +3 -0
- django_sql_compiler-0.2.0/sql_compiler/compilers/mysql.py +40 -0
- django_sql_compiler-0.2.0/sql_compiler/compilers/postgresql.py +43 -0
- django_sql_compiler-0.2.0/sql_compiler/queryset.py +40 -0
- {django-sql-compiler-0.1.0 → django_sql_compiler-0.2.0}/sql_compiler/utils.py +5 -7
- django-sql-compiler-0.1.0/PKG-INFO +0 -114
- django-sql-compiler-0.1.0/django_sql_compiler.egg-info/PKG-INFO +0 -114
- django-sql-compiler-0.1.0/django_sql_compiler.egg-info/SOURCES.txt +0 -16
- django-sql-compiler-0.1.0/django_sql_compiler.egg-info/dependency_links.txt +0 -1
- django-sql-compiler-0.1.0/django_sql_compiler.egg-info/not-zip-safe +0 -1
- django-sql-compiler-0.1.0/django_sql_compiler.egg-info/requires.txt +0 -1
- django-sql-compiler-0.1.0/django_sql_compiler.egg-info/top_level.txt +0 -1
- django-sql-compiler-0.1.0/setup.cfg +0 -4
- django-sql-compiler-0.1.0/setup.py +0 -29
- django-sql-compiler-0.1.0/sql_compiler/compilers/__init__.py +0 -13
- django-sql-compiler-0.1.0/sql_compiler/compilers/mysql.py +0 -9
- django-sql-compiler-0.1.0/sql_compiler/compilers/postgresql.py +0 -16
- django-sql-compiler-0.1.0/sql_compiler/queryset.py +0 -30
- {django-sql-compiler-0.1.0 → django_sql_compiler-0.2.0}/sql_compiler/__init__.py +0 -0
- {django-sql-compiler-0.1.0 → django_sql_compiler-0.2.0}/sql_compiler/manager.py +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [0.2.0] - 2026-10-25
|
|
6
|
+
|
|
7
|
+
The first release since 0.1.1 (June 2021). It ships correctness fixes and first-time MySQL support.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- MySQL support for `django.db.backends.mysql` (via mysqlclient), contributed in [PR 2](https://github.com/JaredStufftGD/django-sql-compiler/pull/2). It is covered by the live-database test suite and CI. In 0.1.1 the MySQL compiler was a stub and was not registered.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Non-ASCII parameters are decoded with the connection's encoding instead of byte by byte, so values like `café` are no longer garbled. 0.1.1 corrupted them.
|
|
16
|
+
- SQL is compiled for the queryset's own database alias (`.using()` or a database router) instead of the default alias.
|
|
17
|
+
- The unsupported-backend error names the ENGINE instead of saying "None backend is not supported".
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Decoding is strict. Compiler output that can't be decoded with the connection's codec raises `UnicodeDecodeError` (or `LookupError` for an unknown client encoding) instead of returning garbled text. A Postgres connection on `SQL_ASCII`, or another unusual client encoding, with non-ASCII data now raises where 0.1.1 returned text. A compiler that returns non-bytes raises `TypeError`.
|
|
22
|
+
- Semi-public API:
|
|
23
|
+
- `sql_compiler.utils.bytes_to_string` now requires an `encoding` argument.
|
|
24
|
+
- `BaseSQLCompiler` has a new `python_encoding(connection)` method that custom compiler subclasses must implement (the base raises `NotImplementedError`).
|
|
25
|
+
- `MySQLCompiler` is exported from `sql_compiler.compilers`.
|
|
26
|
+
- Install requirements: Python >=3.10 and Django >=4.2. The package declares no database driver (psycopg2 or mysqlclient comes from your Django setup). Effects on existing environments:
|
|
27
|
+
- With an older Django installed but not pinned, upgrading django-sql-compiler can upgrade Django (accepted, because Django <4.2 is end-of-life).
|
|
28
|
+
- With Django <4.2 pinned, or on Python <3.10, pip stays on 0.1.1.
|
|
29
|
+
- An install from git master already reports 0.2.3, so pip won't treat 0.2.0 as an upgrade. Install `django-sql-compiler==0.2.0` explicitly.
|
|
30
|
+
|
|
31
|
+
### Packaging
|
|
32
|
+
|
|
33
|
+
- The first wheel release (0.1.1 was sdist-only).
|
|
34
|
+
- Built with flit_core instead of setuptools.
|
|
35
|
+
- The license is declared with PEP 639 `License-Expression: MIT`, and LICENSE ships in both artifacts.
|
|
36
|
+
- Metadata-Version 2.5.
|
|
37
|
+
- CHANGELOG.md ships in the sdist.
|
|
38
|
+
|
|
39
|
+
### Known issues
|
|
40
|
+
|
|
41
|
+
- psycopg 3 is not supported yet. With psycopg 3 installed, Django uses it and `executable_query` raises (planned for 1.0). 0.1.1 had this issue too.
|
|
42
|
+
- `.none()` querysets raise `EmptyResultSet` (planned for 1.0). 0.1.1 had this issue too.
|
|
43
|
+
|
|
44
|
+
[0.2.0]: https://github.com/JaredStufftGD/django-sql-compiler/releases/tag/v0.2.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Grok Data
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: django-sql-compiler
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A light-weight module to generate usable SQL from a Django QuerySet.
|
|
5
|
+
Author-email: Grok Data / Jared Stufft <jared@grokdata.tech>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Web Environment
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.2
|
|
14
|
+
Classifier: Framework :: Django :: 6.0
|
|
15
|
+
Classifier: Framework :: Django :: 6.1
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: Django>=4.2
|
|
27
|
+
Project-URL: Changelog, https://github.com/JaredStufftGD/django-sql-compiler/blob/master/CHANGELOG.md
|
|
28
|
+
Project-URL: Homepage, https://github.com/JaredStufftGD/django-sql-compiler
|
|
29
|
+
Project-URL: Issues, https://github.com/JaredStufftGD/django-sql-compiler/issues
|
|
30
|
+
Project-URL: Source, https://github.com/JaredStufftGD/django-sql-compiler
|
|
31
|
+
Import-Name: sql_compiler
|
|
32
|
+
|
|
33
|
+
# Django SQL Compiler
|
|
34
|
+
|
|
35
|
+
[](https://github.com/JaredStufftGD/django-sql-compiler/actions/workflows/ci.yml)
|
|
36
|
+
|
|
37
|
+
A light-weight module to generate usable SQL from a Django QuerySet.
|
|
38
|
+
|
|
39
|
+
## Backend Support
|
|
40
|
+
`django-sql-compiler` supports connections made via these Django `ENGINE` values:
|
|
41
|
+
|
|
42
|
+
- `django.db.backends.postgresql`
|
|
43
|
+
- `django.db.backends.postgresql_psycopg2`: a legacy alias for the PostgreSQL backend that Django removed in Django 3.0. The package still accepts the ENGINE string and maps it to the same compiler.
|
|
44
|
+
- `django_tenants.postgresql_backend` (django-tenants)
|
|
45
|
+
- `django.db.backends.mysql` (via mysqlclient)
|
|
46
|
+
|
|
47
|
+
PostgreSQL connections currently require psycopg2; psycopg 3 isn't supported yet. If psycopg 3 is installed, Django uses it and `executable_query` raises.
|
|
48
|
+
|
|
49
|
+
## About Django SQL Compiler
|
|
50
|
+
The Django ORM is very useful for abstracting away SQL queries from the focus of the developer. This is very useful
|
|
51
|
+
for preventing SQL injection attacks and generating queries programmatically using applied logic in your Django app.
|
|
52
|
+
|
|
53
|
+
However, very complex queries (such as those used in reporting, analytics, or data science projects) can be difficult
|
|
54
|
+
or impossible to create with the Django ORM alone. The ORM provides the `.raw` query method and exposes the raw database
|
|
55
|
+
`connection` objects which can be used to execute arbitrary SQL against the database. In doing so, we lose the benefit
|
|
56
|
+
of the ORM with respect to dynamically adding components to the query (such as filters in a `WHERE` clause) in a way that
|
|
57
|
+
prevents injection attacks.
|
|
58
|
+
|
|
59
|
+
The base Django `QuerySet` object has a `Query` object available at the `.query` property. Casting this `Query` object as a `str`
|
|
60
|
+
prints out what looks like a valid SQL query. However, this version of the query is not properly escaped or quoted, meaning
|
|
61
|
+
it's not **actually** valid SQL unless there are no dynamic components (such as filters from user input) in the `QuerySet`.
|
|
62
|
+
|
|
63
|
+
`django-sql-compiler` aims to provide a way to generate clean, usable SQL from a given `QuerySet`, which can be used in
|
|
64
|
+
tandem with a raw SQL query to give SQL users more flexibility in querying their Django-connected database while still
|
|
65
|
+
retaining the Django ORM for security and dynamic query generation purposes.
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
Install the package:
|
|
69
|
+
```
|
|
70
|
+
pip install django-sql-compiler
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
No database driver is installed with the package and there are no extras: it uses the database connection your Django project already has, so the driver (for example psycopg2 or mysqlclient) comes from your existing Django setup.
|
|
74
|
+
|
|
75
|
+
Add `sql_compiler` to your `INSTALLED_APPS`:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
INSTALLED_APPS = [
|
|
79
|
+
...,
|
|
80
|
+
'sql_compiler'
|
|
81
|
+
]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Add the `SQLCompilerManager` to your models:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from django.db import models
|
|
88
|
+
from sql_compiler import SQLCompilerManager
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MyModel(models.Model):
|
|
92
|
+
|
|
93
|
+
objects = SQLCompilerManager()
|
|
94
|
+
field_one = models.IntegerField()
|
|
95
|
+
field_two = models.CharField(max_length=200)
|
|
96
|
+
field_three = models.DateTimeField()
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For a given `QuerySet` on the model, you can access the executable query be accessing the `.executable_query` property of the `QuerySet`.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
query_set = MyModel.objects.filter(field_one__lte=10)
|
|
104
|
+
executable_query = query_set.executable_query
|
|
105
|
+
print(executable_query)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This query can then be used in another query as a Common Table Expression or Subquery:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Basic usage
|
|
112
|
+
wrapper_query = """select * from ({}) a""".format(executable_query)
|
|
113
|
+
new_queryset = MyModel.objects.raw(wrapper_query)
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
While this is a very simple example, you can use this in more complicated queries:
|
|
118
|
+
```python
|
|
119
|
+
|
|
120
|
+
# inside a view
|
|
121
|
+
filtered_query_set = MyModel.objects.filter(
|
|
122
|
+
field_one__lte=request.data.get('field_one_filter', 0),
|
|
123
|
+
field_two__in=request.data.get('field_two_filter', [])
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
more_complex_query = """
|
|
128
|
+
select
|
|
129
|
+
RANK() over (partition by a.field_one order by a.field_three desc) as rnk
|
|
130
|
+
,a.field_one
|
|
131
|
+
,a.field_two
|
|
132
|
+
from
|
|
133
|
+
({}) a
|
|
134
|
+
""".format(filtered_query_set.executable_query)
|
|
135
|
+
|
|
136
|
+
more_complex_results = MyModel.objects.raw(more_complex_query)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Now, users who are more familiar with SQL rather than the Django ORM can use the ORM for security and conveniently
|
|
140
|
+
generating SQL queries and use SQL for the rest of their transformations.
|
|
141
|
+
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
# Django SQL Compiler
|
|
2
2
|
|
|
3
|
+
[](https://github.com/JaredStufftGD/django-sql-compiler/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
A light-weight module to generate usable SQL from a Django QuerySet.
|
|
4
6
|
|
|
5
7
|
## Backend Support
|
|
6
|
-
|
|
8
|
+
`django-sql-compiler` supports connections made via these Django `ENGINE` values:
|
|
9
|
+
|
|
10
|
+
- `django.db.backends.postgresql`
|
|
11
|
+
- `django.db.backends.postgresql_psycopg2`: a legacy alias for the PostgreSQL backend that Django removed in Django 3.0. The package still accepts the ENGINE string and maps it to the same compiler.
|
|
12
|
+
- `django_tenants.postgresql_backend` (django-tenants)
|
|
13
|
+
- `django.db.backends.mysql` (via mysqlclient)
|
|
14
|
+
|
|
15
|
+
PostgreSQL connections currently require psycopg2; psycopg 3 isn't supported yet. If psycopg 3 is installed, Django uses it and `executable_query` raises.
|
|
7
16
|
|
|
8
17
|
## About Django SQL Compiler
|
|
9
18
|
The Django ORM is very useful for abstracting away SQL queries from the focus of the developer. This is very useful
|
|
10
19
|
for preventing SQL injection attacks and generating queries programmatically using applied logic in your Django app.
|
|
11
20
|
|
|
12
21
|
However, very complex queries (such as those used in reporting, analytics, or data science projects) can be difficult
|
|
13
|
-
or impossible to create with the Django ORM alone. The ORM provides the `.raw` query method and exposes the raw database
|
|
22
|
+
or impossible to create with the Django ORM alone. The ORM provides the `.raw` query method and exposes the raw database
|
|
14
23
|
`connection` objects which can be used to execute arbitrary SQL against the database. In doing so, we lose the benefit
|
|
15
|
-
of the ORM with respect to dynamically adding components to the query (such as filters in a `WHERE` clause) in a way that
|
|
24
|
+
of the ORM with respect to dynamically adding components to the query (such as filters in a `WHERE` clause) in a way that
|
|
16
25
|
prevents injection attacks.
|
|
17
26
|
|
|
18
|
-
The base Django `QuerySet` object has a `Query` object available at the `.query` property. Casting this `Query` object as a `str`
|
|
19
|
-
prints out what looks like a valid SQL query. However, this version of the query is not properly escaped or quoted, meaning
|
|
27
|
+
The base Django `QuerySet` object has a `Query` object available at the `.query` property. Casting this `Query` object as a `str`
|
|
28
|
+
prints out what looks like a valid SQL query. However, this version of the query is not properly escaped or quoted, meaning
|
|
20
29
|
it's not **actually** valid SQL unless there are no dynamic components (such as filters from user input) in the `QuerySet`.
|
|
21
30
|
|
|
22
|
-
`django-sql-compiler` aims to provide a way to generate clean, usable SQL from a given `QuerySet`, which can be used in
|
|
23
|
-
tandem with a raw SQL query to give SQL users more flexibility in querying their Django-connected database while still
|
|
31
|
+
`django-sql-compiler` aims to provide a way to generate clean, usable SQL from a given `QuerySet`, which can be used in
|
|
32
|
+
tandem with a raw SQL query to give SQL users more flexibility in querying their Django-connected database while still
|
|
24
33
|
retaining the Django ORM for security and dynamic query generation purposes.
|
|
25
34
|
|
|
26
35
|
## Usage
|
|
@@ -29,6 +38,8 @@ Install the package:
|
|
|
29
38
|
pip install django-sql-compiler
|
|
30
39
|
```
|
|
31
40
|
|
|
41
|
+
No database driver is installed with the package and there are no extras: it uses the database connection your Django project already has, so the driver (for example psycopg2 or mysqlclient) comes from your existing Django setup.
|
|
42
|
+
|
|
32
43
|
Add `sql_compiler` to your `INSTALLED_APPS`:
|
|
33
44
|
|
|
34
45
|
```python
|
|
@@ -79,7 +90,7 @@ filtered_query_set = MyModel.objects.filter(
|
|
|
79
90
|
field_one__lte=request.data.get('field_one_filter', 0),
|
|
80
91
|
field_two__in=request.data.get('field_two_filter', [])
|
|
81
92
|
)
|
|
82
|
-
|
|
93
|
+
|
|
83
94
|
|
|
84
95
|
more_complex_query = """
|
|
85
96
|
select
|
|
@@ -94,4 +105,4 @@ more_complex_results = MyModel.objects.raw(more_complex_query)
|
|
|
94
105
|
```
|
|
95
106
|
|
|
96
107
|
Now, users who are more familiar with SQL rather than the Django ORM can use the ORM for security and conveniently
|
|
97
|
-
generating SQL queries and use SQL for the rest of their transformations.
|
|
108
|
+
generating SQL queries and use SQL for the rest of their transformations.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "django-sql-compiler"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "A light-weight module to generate usable SQL from a Django QuerySet."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Grok Data / Jared Stufft", email = "jared@grokdata.tech" },
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"Django>=4.2",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Web Environment",
|
|
18
|
+
"Framework :: Django",
|
|
19
|
+
"Framework :: Django :: 4.2",
|
|
20
|
+
"Framework :: Django :: 5.2",
|
|
21
|
+
"Framework :: Django :: 6.0",
|
|
22
|
+
"Framework :: Django :: 6.1",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/JaredStufftGD/django-sql-compiler"
|
|
36
|
+
Source = "https://github.com/JaredStufftGD/django-sql-compiler"
|
|
37
|
+
Changelog = "https://github.com/JaredStufftGD/django-sql-compiler/blob/master/CHANGELOG.md"
|
|
38
|
+
Issues = "https://github.com/JaredStufftGD/django-sql-compiler/issues"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=8",
|
|
43
|
+
]
|
|
44
|
+
pg2 = [
|
|
45
|
+
"psycopg2-binary>=2.9.9",
|
|
46
|
+
]
|
|
47
|
+
pg3 = [
|
|
48
|
+
"psycopg[binary]>=3.2",
|
|
49
|
+
]
|
|
50
|
+
mysql = [
|
|
51
|
+
"mysqlclient>=2.2.1",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.uv]
|
|
55
|
+
conflicts = [
|
|
56
|
+
[{ group = "pg2" }, { group = "pg3" }],
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[build-system]
|
|
60
|
+
requires = ["flit_core>=4,<5"]
|
|
61
|
+
build-backend = "flit_core.buildapi"
|
|
62
|
+
|
|
63
|
+
[tool.flit.module]
|
|
64
|
+
name = "sql_compiler"
|
|
65
|
+
|
|
66
|
+
[tool.flit.sdist]
|
|
67
|
+
include = ["CHANGELOG.md"]
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
testpaths = ["tests"]
|
|
71
|
+
xfail_strict = true
|
|
72
|
+
addopts = "-ra"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .postgresql import PostgresCompiler, PostgresTenantsCompiler, Psycopg2Compiler
|
|
2
|
+
from .mysql import MySQLCompiler
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'MySQLCompiler',
|
|
6
|
+
'PostgresCompiler',
|
|
7
|
+
'PostgresTenantsCompiler',
|
|
8
|
+
'Psycopg2Compiler',
|
|
9
|
+
'compiler_map',
|
|
10
|
+
'SUPPORTED_BACKENDS'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
compilers = [
|
|
14
|
+
MySQLCompiler,
|
|
15
|
+
PostgresCompiler,
|
|
16
|
+
PostgresTenantsCompiler,
|
|
17
|
+
Psycopg2Compiler
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
compiler_map = {
|
|
21
|
+
compiler.backend_name: compiler for compiler in compilers
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
SUPPORTED_BACKENDS = compiler_map.keys()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
|
|
3
|
+
from django.db.utils import ProgrammingError
|
|
4
|
+
|
|
5
|
+
from .base import BaseSQLCompiler
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MySQLCompiler(BaseSQLCompiler):
|
|
9
|
+
|
|
10
|
+
backend_name = 'django.db.backends.mysql'
|
|
11
|
+
|
|
12
|
+
def compile_sql(self, connection, query, params):
|
|
13
|
+
with connection.cursor() as cursor:
|
|
14
|
+
compiled_sql = self.mogrify(cursor, query, params)
|
|
15
|
+
return compiled_sql
|
|
16
|
+
|
|
17
|
+
def python_encoding(self, connection):
|
|
18
|
+
return codecs.lookup(connection.connection.encoding).name
|
|
19
|
+
|
|
20
|
+
def mogrify(self, cursor, query, args=None):
|
|
21
|
+
db = cursor.connection
|
|
22
|
+
if isinstance(query, str):
|
|
23
|
+
query = query.encode(db.encoding)
|
|
24
|
+
|
|
25
|
+
if args is not None:
|
|
26
|
+
if isinstance(args, dict):
|
|
27
|
+
nargs = {}
|
|
28
|
+
for key, item in args.items():
|
|
29
|
+
if isinstance(key, str):
|
|
30
|
+
key = key.encode(db.encoding)
|
|
31
|
+
nargs[key] = db.literal(item)
|
|
32
|
+
args = nargs
|
|
33
|
+
else:
|
|
34
|
+
args = tuple(map(db.literal, args))
|
|
35
|
+
try:
|
|
36
|
+
query = query % args
|
|
37
|
+
except (TypeError, KeyError, ValueError) as m:
|
|
38
|
+
raise ProgrammingError(str(m)) from m
|
|
39
|
+
|
|
40
|
+
return query
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
|
|
3
|
+
from .base import BaseSQLCompiler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PostgresCompiler(BaseSQLCompiler):
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Compiles an executable SQL query from a parameterized query and arg list using the postgresql/psycopg2 backend.
|
|
10
|
+
Also reports the raw connection's client encoding as a Python codec name, for psycopg2 and psycopg 3.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
backend_name = 'django.db.backends.postgresql'
|
|
14
|
+
|
|
15
|
+
def encoding_map(self):
|
|
16
|
+
from psycopg2.extensions import encodings
|
|
17
|
+
return encodings
|
|
18
|
+
|
|
19
|
+
def python_encoding(self, connection):
|
|
20
|
+
raw = connection.connection
|
|
21
|
+
info = getattr(raw, 'info', None)
|
|
22
|
+
if info is not None and hasattr(info, 'encoding'):
|
|
23
|
+
# psycopg 3 already reports a Python codec name
|
|
24
|
+
return codecs.lookup(info.encoding).name
|
|
25
|
+
name = raw.encoding
|
|
26
|
+
try:
|
|
27
|
+
return self.encoding_map()[name]
|
|
28
|
+
except KeyError:
|
|
29
|
+
raise LookupError('unknown PostgreSQL client encoding: {}'.format(name)) from None
|
|
30
|
+
|
|
31
|
+
def compile_sql(self, connection, query, params):
|
|
32
|
+
with connection.cursor() as cursor:
|
|
33
|
+
compiled_sql = cursor.mogrify(query, params)
|
|
34
|
+
|
|
35
|
+
return compiled_sql
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PostgresTenantsCompiler(PostgresCompiler):
|
|
39
|
+
backend_name = 'django_tenants.postgresql_backend'
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Psycopg2Compiler(PostgresCompiler):
|
|
43
|
+
backend_name = 'django.db.backends.postgresql_psycopg2'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from django.db import connections
|
|
2
|
+
from django.db.models import QuerySet
|
|
3
|
+
from django.utils.functional import cached_property
|
|
4
|
+
from . import utils
|
|
5
|
+
from . import compilers
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SQLCompilerQuerySet(QuerySet):
|
|
9
|
+
|
|
10
|
+
@cached_property
|
|
11
|
+
def executable_query(self):
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Takes the parameterized SQL query with the arg list and compiles the query so
|
|
15
|
+
that it can be used anywhere - as a subquery in a Raw SQL query, in a SQL GUI interface, etc.
|
|
16
|
+
|
|
17
|
+
The SQL is built for the queryset's own database alias, so the dialect and the
|
|
18
|
+
escaping come from the same database.
|
|
19
|
+
|
|
20
|
+
The result is decoded with the encoding the driver reports for this connection,
|
|
21
|
+
and undecodable output raises instead of being garbled.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
alias = self.db
|
|
25
|
+
connection = utils.validate_connection(connections[alias])
|
|
26
|
+
|
|
27
|
+
if not connection:
|
|
28
|
+
raise NotImplementedError('{} backend is not supported.'.format(connections[alias].settings_dict['ENGINE']))
|
|
29
|
+
|
|
30
|
+
query, params = self.query.get_compiler(using=alias).as_sql()
|
|
31
|
+
|
|
32
|
+
backend_name = connection.settings_dict['ENGINE']
|
|
33
|
+
compiler = compilers.compiler_map[backend_name]()
|
|
34
|
+
compiled_query = compiler.compile_sql(connection, query, params)
|
|
35
|
+
if not isinstance(compiled_query, (bytes, bytearray)):
|
|
36
|
+
raise TypeError('compile_sql must return bytes, got {}'.format(type(compiled_query).__name__))
|
|
37
|
+
encoding = compiler.python_encoding(connection)
|
|
38
|
+
compiled_query = utils.bytes_to_string(compiled_query, encoding)
|
|
39
|
+
|
|
40
|
+
return compiled_query
|
|
@@ -11,18 +11,16 @@ def validate_connection(connection):
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
backend_name = connection.settings_dict['ENGINE']
|
|
14
|
-
|
|
15
|
-
if backend_name in ('django.db.backends.postgresql_psycopg2', 'django_tenants.postgresql_backend'):
|
|
16
|
-
backend_name = 'django.db.backends.postgresql'
|
|
17
|
-
|
|
18
14
|
if backend_name in SUPPORTED_BACKENDS:
|
|
19
15
|
return connection
|
|
20
16
|
|
|
21
17
|
|
|
22
|
-
def bytes_to_string(bytes_obj):
|
|
18
|
+
def bytes_to_string(bytes_obj, encoding):
|
|
23
19
|
|
|
24
20
|
"""
|
|
25
|
-
|
|
21
|
+
Decodes a bytes object to a string with the Python codec the connection's driver reports.
|
|
22
|
+
|
|
23
|
+
Decoding is strict: LookupError (unknown codec) and UnicodeDecodeError (invalid bytes) propagate.
|
|
26
24
|
"""
|
|
27
25
|
|
|
28
|
-
return
|
|
26
|
+
return bytes_obj.decode(encoding)
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: django-sql-compiler
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: A light-weight module to generate usable SQL from a Django QuerySet.
|
|
5
|
-
Home-page: https://github.com/GrokData/django-sql-compiler
|
|
6
|
-
Author: Grok Data / Jared Stufft
|
|
7
|
-
Author-email: jared@grokdata.tech
|
|
8
|
-
License: MIT
|
|
9
|
-
Description: # Django SQL Compiler
|
|
10
|
-
|
|
11
|
-
A light-weight module to generate usable SQL from a Django QuerySet.
|
|
12
|
-
|
|
13
|
-
## Backend Support
|
|
14
|
-
Currently, `django-sql-compiler` only supports connections made via the `django.db.backends.postgresql` backend.
|
|
15
|
-
|
|
16
|
-
## About Django SQL Compiler
|
|
17
|
-
The Django ORM is very useful for abstracting away SQL queries from the focus of the developer. This is very useful
|
|
18
|
-
for preventing SQL injection attacks and generating queries programmatically using applied logic in your Django app.
|
|
19
|
-
|
|
20
|
-
However, very complex queries (such as those used in reporting, analytics, or data science projects) can be difficult
|
|
21
|
-
or impossible to create with the Django ORM alone. The ORM provides the `.raw` query method and exposes the raw database
|
|
22
|
-
`connection` objects which can be used to execute arbitrary SQL against the database. In doing so, we lose the benefit
|
|
23
|
-
of the ORM with respect to dynamically adding components to the query (such as filters in a `WHERE` clause) in a way that
|
|
24
|
-
prevents injection attacks.
|
|
25
|
-
|
|
26
|
-
The base Django `QuerySet` object has a `Query` object available at the `.query` property. Casting this `Query` object as a `str`
|
|
27
|
-
prints out what looks like a valid SQL query. However, this version of the query is not properly escaped or quoted, meaning
|
|
28
|
-
it's not **actually** valid SQL unless there are no dynamic components (such as filters from user input) in the `QuerySet`.
|
|
29
|
-
|
|
30
|
-
`django-sql-compiler` aims to provide a way to generate clean, usable SQL from a given `QuerySet`, which can be used in
|
|
31
|
-
tandem with a raw SQL query to give SQL users more flexibility in querying their Django-connected database while still
|
|
32
|
-
retaining the Django ORM for security and dynamic query generation purposes.
|
|
33
|
-
|
|
34
|
-
## Usage
|
|
35
|
-
Install the package:
|
|
36
|
-
```
|
|
37
|
-
pip install django-sql-compiler
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Add `sql_compiler` to your `INSTALLED_APPS`:
|
|
41
|
-
|
|
42
|
-
```python
|
|
43
|
-
INSTALLED_APPS = [
|
|
44
|
-
...,
|
|
45
|
-
'sql_compiler'
|
|
46
|
-
]
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Add the `SQLCompilerManager` to your models:
|
|
50
|
-
|
|
51
|
-
```python
|
|
52
|
-
from django.db import models
|
|
53
|
-
from sql_compiler import SQLCompilerManager
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class MyModel(models.Model):
|
|
57
|
-
|
|
58
|
-
objects = SQLCompilerManager()
|
|
59
|
-
field_one = models.IntegerField()
|
|
60
|
-
field_two = models.CharField(max_length=200)
|
|
61
|
-
field_three = models.DateTimeField()
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
For a given `QuerySet` on the model, you can access the executable query be accessing the `.executable_query` property of the `QuerySet`.
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
query_set = MyModel.objects.filter(field_one__lte=10)
|
|
69
|
-
executable_query = query_set.executable_query
|
|
70
|
-
print(executable_query)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
This query can then be used in another query as a Common Table Expression or Subquery:
|
|
74
|
-
|
|
75
|
-
```python
|
|
76
|
-
# Basic usage
|
|
77
|
-
wrapper_query = """select * from ({}) a""".format(executable_query)
|
|
78
|
-
new_queryset = MyModel.objects.raw(wrapper_query)
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
While this is a very simple example, you can use this in more complicated queries:
|
|
83
|
-
```python
|
|
84
|
-
|
|
85
|
-
# inside a view
|
|
86
|
-
filtered_query_set = MyModel.objects.filter(
|
|
87
|
-
field_one__lte=request.data.get('field_one_filter', 0),
|
|
88
|
-
field_two__in=request.data.get('field_two_filter', [])
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
more_complex_query = """
|
|
93
|
-
select
|
|
94
|
-
RANK() over (partition by a.field_one order by a.field_three desc) as rnk
|
|
95
|
-
,a.field_one
|
|
96
|
-
,a.field_two
|
|
97
|
-
from
|
|
98
|
-
({}) a
|
|
99
|
-
""".format(filtered_query_set.executable_query)
|
|
100
|
-
|
|
101
|
-
more_complex_results = MyModel.objects.raw(more_complex_query)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Now, users who are more familiar with SQL rather than the Django ORM can use the ORM for security and conveniently
|
|
105
|
-
generating SQL queries and use SQL for the rest of their transformations.
|
|
106
|
-
Platform: UNKNOWN
|
|
107
|
-
Classifier: Development Status :: 3 - Alpha
|
|
108
|
-
Classifier: Environment :: Web Environment
|
|
109
|
-
Classifier: Framework :: Django
|
|
110
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
111
|
-
Classifier: Programming Language :: Python :: 3
|
|
112
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
113
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
114
|
-
Description-Content-Type: text/markdown
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: django-sql-compiler
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: A light-weight module to generate usable SQL from a Django QuerySet.
|
|
5
|
-
Home-page: https://github.com/GrokData/django-sql-compiler
|
|
6
|
-
Author: Grok Data / Jared Stufft
|
|
7
|
-
Author-email: jared@grokdata.tech
|
|
8
|
-
License: MIT
|
|
9
|
-
Description: # Django SQL Compiler
|
|
10
|
-
|
|
11
|
-
A light-weight module to generate usable SQL from a Django QuerySet.
|
|
12
|
-
|
|
13
|
-
## Backend Support
|
|
14
|
-
Currently, `django-sql-compiler` only supports connections made via the `django.db.backends.postgresql` backend.
|
|
15
|
-
|
|
16
|
-
## About Django SQL Compiler
|
|
17
|
-
The Django ORM is very useful for abstracting away SQL queries from the focus of the developer. This is very useful
|
|
18
|
-
for preventing SQL injection attacks and generating queries programmatically using applied logic in your Django app.
|
|
19
|
-
|
|
20
|
-
However, very complex queries (such as those used in reporting, analytics, or data science projects) can be difficult
|
|
21
|
-
or impossible to create with the Django ORM alone. The ORM provides the `.raw` query method and exposes the raw database
|
|
22
|
-
`connection` objects which can be used to execute arbitrary SQL against the database. In doing so, we lose the benefit
|
|
23
|
-
of the ORM with respect to dynamically adding components to the query (such as filters in a `WHERE` clause) in a way that
|
|
24
|
-
prevents injection attacks.
|
|
25
|
-
|
|
26
|
-
The base Django `QuerySet` object has a `Query` object available at the `.query` property. Casting this `Query` object as a `str`
|
|
27
|
-
prints out what looks like a valid SQL query. However, this version of the query is not properly escaped or quoted, meaning
|
|
28
|
-
it's not **actually** valid SQL unless there are no dynamic components (such as filters from user input) in the `QuerySet`.
|
|
29
|
-
|
|
30
|
-
`django-sql-compiler` aims to provide a way to generate clean, usable SQL from a given `QuerySet`, which can be used in
|
|
31
|
-
tandem with a raw SQL query to give SQL users more flexibility in querying their Django-connected database while still
|
|
32
|
-
retaining the Django ORM for security and dynamic query generation purposes.
|
|
33
|
-
|
|
34
|
-
## Usage
|
|
35
|
-
Install the package:
|
|
36
|
-
```
|
|
37
|
-
pip install django-sql-compiler
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Add `sql_compiler` to your `INSTALLED_APPS`:
|
|
41
|
-
|
|
42
|
-
```python
|
|
43
|
-
INSTALLED_APPS = [
|
|
44
|
-
...,
|
|
45
|
-
'sql_compiler'
|
|
46
|
-
]
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Add the `SQLCompilerManager` to your models:
|
|
50
|
-
|
|
51
|
-
```python
|
|
52
|
-
from django.db import models
|
|
53
|
-
from sql_compiler import SQLCompilerManager
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class MyModel(models.Model):
|
|
57
|
-
|
|
58
|
-
objects = SQLCompilerManager()
|
|
59
|
-
field_one = models.IntegerField()
|
|
60
|
-
field_two = models.CharField(max_length=200)
|
|
61
|
-
field_three = models.DateTimeField()
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
For a given `QuerySet` on the model, you can access the executable query be accessing the `.executable_query` property of the `QuerySet`.
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
query_set = MyModel.objects.filter(field_one__lte=10)
|
|
69
|
-
executable_query = query_set.executable_query
|
|
70
|
-
print(executable_query)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
This query can then be used in another query as a Common Table Expression or Subquery:
|
|
74
|
-
|
|
75
|
-
```python
|
|
76
|
-
# Basic usage
|
|
77
|
-
wrapper_query = """select * from ({}) a""".format(executable_query)
|
|
78
|
-
new_queryset = MyModel.objects.raw(wrapper_query)
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
While this is a very simple example, you can use this in more complicated queries:
|
|
83
|
-
```python
|
|
84
|
-
|
|
85
|
-
# inside a view
|
|
86
|
-
filtered_query_set = MyModel.objects.filter(
|
|
87
|
-
field_one__lte=request.data.get('field_one_filter', 0),
|
|
88
|
-
field_two__in=request.data.get('field_two_filter', [])
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
more_complex_query = """
|
|
93
|
-
select
|
|
94
|
-
RANK() over (partition by a.field_one order by a.field_three desc) as rnk
|
|
95
|
-
,a.field_one
|
|
96
|
-
,a.field_two
|
|
97
|
-
from
|
|
98
|
-
({}) a
|
|
99
|
-
""".format(filtered_query_set.executable_query)
|
|
100
|
-
|
|
101
|
-
more_complex_results = MyModel.objects.raw(more_complex_query)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Now, users who are more familiar with SQL rather than the Django ORM can use the ORM for security and conveniently
|
|
105
|
-
generating SQL queries and use SQL for the rest of their transformations.
|
|
106
|
-
Platform: UNKNOWN
|
|
107
|
-
Classifier: Development Status :: 3 - Alpha
|
|
108
|
-
Classifier: Environment :: Web Environment
|
|
109
|
-
Classifier: Framework :: Django
|
|
110
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
111
|
-
Classifier: Programming Language :: Python :: 3
|
|
112
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
113
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
114
|
-
Description-Content-Type: text/markdown
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
README.md
|
|
2
|
-
setup.py
|
|
3
|
-
django_sql_compiler.egg-info/PKG-INFO
|
|
4
|
-
django_sql_compiler.egg-info/SOURCES.txt
|
|
5
|
-
django_sql_compiler.egg-info/dependency_links.txt
|
|
6
|
-
django_sql_compiler.egg-info/not-zip-safe
|
|
7
|
-
django_sql_compiler.egg-info/requires.txt
|
|
8
|
-
django_sql_compiler.egg-info/top_level.txt
|
|
9
|
-
sql_compiler/__init__.py
|
|
10
|
-
sql_compiler/manager.py
|
|
11
|
-
sql_compiler/queryset.py
|
|
12
|
-
sql_compiler/utils.py
|
|
13
|
-
sql_compiler/compilers/__init__.py
|
|
14
|
-
sql_compiler/compilers/base.py
|
|
15
|
-
sql_compiler/compilers/mysql.py
|
|
16
|
-
sql_compiler/compilers/postgresql.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
django
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
sql_compiler
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from setuptools import setup, find_packages
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
setup(
|
|
5
|
-
name='django-sql-compiler',
|
|
6
|
-
version='0.1.0',
|
|
7
|
-
description="A light-weight module to generate usable SQL from a Django QuerySet.",
|
|
8
|
-
long_description=open('README.md').read(),
|
|
9
|
-
long_description_content_type='text/markdown',
|
|
10
|
-
author='Grok Data / Jared Stufft',
|
|
11
|
-
author_email='jared@grokdata.tech',
|
|
12
|
-
url='https://github.com/GrokData/django-sql-compiler',
|
|
13
|
-
license='MIT',
|
|
14
|
-
packages=find_packages(),
|
|
15
|
-
classifiers=[
|
|
16
|
-
"Development Status :: 3 - Alpha",
|
|
17
|
-
"Environment :: Web Environment",
|
|
18
|
-
"Framework :: Django",
|
|
19
|
-
"License :: OSI Approved :: MIT License",
|
|
20
|
-
"Programming Language :: Python :: 3",
|
|
21
|
-
"Topic :: Internet :: WWW/HTTP",
|
|
22
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
-
],
|
|
24
|
-
install_requires=[
|
|
25
|
-
'django',
|
|
26
|
-
],
|
|
27
|
-
include_package_data=True,
|
|
28
|
-
zip_safe=False,
|
|
29
|
-
)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from .postgresql import PostgresCompiler
|
|
2
|
-
|
|
3
|
-
__all__ = ['PostgresCompiler', 'compiler_map', 'SUPPORTED_BACKENDS']
|
|
4
|
-
|
|
5
|
-
compilers = [
|
|
6
|
-
PostgresCompiler
|
|
7
|
-
]
|
|
8
|
-
|
|
9
|
-
compiler_map = {
|
|
10
|
-
compiler.backend_name: compiler for compiler in compilers
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
SUPPORTED_BACKENDS = compiler_map.keys()
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
from .base import BaseSQLCompiler
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class PostgresCompiler(BaseSQLCompiler):
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
Compiles an executable SQL query from a parameterized query and arg list using the postgresql/psycopg2 backend.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
backend_name = 'django.db.backends.postgresql'
|
|
11
|
-
|
|
12
|
-
def compile_sql(self, connection, query, params):
|
|
13
|
-
with connection.cursor() as cursor:
|
|
14
|
-
compiled_sql = cursor.mogrify(query, params)
|
|
15
|
-
|
|
16
|
-
return compiled_sql
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from django.db import connections
|
|
2
|
-
from django.db.models import QuerySet
|
|
3
|
-
from django.utils.functional import cached_property
|
|
4
|
-
from . import utils
|
|
5
|
-
from . import compilers
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class SQLCompilerQuerySet(QuerySet):
|
|
9
|
-
|
|
10
|
-
@cached_property
|
|
11
|
-
def executable_query(self):
|
|
12
|
-
|
|
13
|
-
"""
|
|
14
|
-
Takes the parameterized SQL query with the arg list and compiles the query so
|
|
15
|
-
that it can be used anywhere - as a subquery in a Raw SQL query, in a SQL GUI interface, etc.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
connection = utils.validate_connection(connections[self.db])
|
|
19
|
-
|
|
20
|
-
if not connection:
|
|
21
|
-
raise NotImplementedError('{} backend is not supported.'.format(str(connection)))
|
|
22
|
-
|
|
23
|
-
query, params = self.query.sql_with_params()
|
|
24
|
-
|
|
25
|
-
backend_name = connection.settings_dict['ENGINE']
|
|
26
|
-
compiler = compilers.compiler_map[backend_name]()
|
|
27
|
-
compiled_query = compiler.compile_sql(connection, query, params)
|
|
28
|
-
compiled_query = utils.bytes_to_string(compiled_query)
|
|
29
|
-
|
|
30
|
-
return compiled_query
|
|
File without changes
|
|
File without changes
|