-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added ability to define custom Funcs as constraints to colocate the f…
…uction's definition
- Loading branch information
David Sanders
committed
Jul 12, 2024
1 parent
1dc5d70
commit a9f24ed
Showing
9 changed files
with
175 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
Functions as Constraints | ||
======================== | ||
|
||
July 2024 | ||
|
||
|
||
We've seen that we can use the constraints API to add artifacts to the database. An interesting application | ||
is to colocate the definition for custom functions defined as `Func`: | ||
|
||
|
||
```python | ||
class HelloWorld(Func): | ||
function = "hello_world" | ||
output_field = models.CharField() | ||
|
||
create_function = """\ | ||
CREATE OR REPLACE FUNCTION hello_world() | ||
RETURNS varchar | ||
AS $$ | ||
BEGIN | ||
RETURN 'Hello World!'; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
""" | ||
|
||
|
||
class PlaceholderModel(models.Model): | ||
class Meta: | ||
constraints = [ | ||
func_as_constraint(HelloWorld), | ||
] | ||
``` | ||
|
||
Using a simple function we can create a `RawSQL` constraint defined in [../abusing_constraints](Having Fun with Constraints) | ||
|
||
```python | ||
def func_as_constraint(func_class): | ||
sql = textwrap.dedent(func_class.create_function).strip() | ||
return RawSQL( | ||
name=func_class.function, | ||
sql=sql, | ||
... | ||
) | ||
``` | ||
|
||
A next step might be to create a reverse for RawSQL that drops the function: | ||
|
||
```python | ||
def func_as_constraint(func_class): | ||
sql = textwrap.dedent(func_class.create_function).strip() | ||
return RawSQL( | ||
name=func_class.function, | ||
sql=sql, | ||
reverse_sql=f"DROP FUNCTION IF EXISTS {func_class.function}", | ||
) | ||
``` | ||
|
||
We can even go so far as to add a comment to the function if the function class has a docstring: | ||
|
||
```python | ||
def func_as_constraint(func_class): | ||
sql = textwrap.dedent(func_class.create_function).strip() | ||
if func_class.__doc__: | ||
comment = psycopg_any_sql.quote(textwrap.dedent(func_class.__doc__).strip()) | ||
if sql[-1] != ";": | ||
sql += ";" | ||
sql += f"COMMENT ON FUNCTION {func_class.function} IS {comment};" | ||
return RawSQL( | ||
name=func_class.function, | ||
sql=sql, | ||
reverse_sql=f"DROP FUNCTION IF EXISTS {func_class.function}", | ||
) | ||
``` |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class FuncAsConstraintConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "func_as_constraint" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from django.db import migrations | ||
from django.db import models | ||
|
||
import abusing_constraints.constraints | ||
|
||
|
||
class Migration(migrations.Migration): | ||
initial = True | ||
|
||
dependencies = [] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="PlaceholderModel", | ||
fields=[ | ||
( | ||
"id", | ||
models.BigAutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
], | ||
), | ||
migrations.AddConstraint( | ||
model_name="placeholdermodel", | ||
constraint=abusing_constraints.constraints.RawSQL( | ||
name="hello_world", | ||
reverse_sql="DROP FUNCTION IF EXISTS hello_world", | ||
sql="CREATE OR REPLACE FUNCTION hello_world()\nRETURNS varchar\nAS $$\nBEGIN\n RETURN 'Hello World!';\nEND;\n$$ LANGUAGE plpgsql;COMMENT ON FUNCTION hello_world IS 'Print hello world!';", | ||
), | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import textwrap | ||
|
||
from django.db import models | ||
from django.db.backends.postgresql.psycopg_any import sql as psycopg_any_sql | ||
from django.db.models.expressions import Func | ||
|
||
from abusing_constraints.constraints import RawSQL | ||
|
||
|
||
def func_as_constraint(func_class): | ||
sql = textwrap.dedent(func_class.create_function).strip() | ||
if func_class.__doc__: | ||
comment = psycopg_any_sql.quote(textwrap.dedent(func_class.__doc__).strip()) | ||
if sql[-1] != ";": | ||
sql += ";" | ||
sql += f"COMMENT ON FUNCTION {func_class.function} IS {comment};" | ||
return RawSQL( | ||
name=func_class.function, | ||
sql=sql, | ||
reverse_sql=f"DROP FUNCTION IF EXISTS {func_class.function}", | ||
) | ||
|
||
|
||
class HelloWorld(Func): | ||
""" | ||
Print hello world! | ||
""" | ||
|
||
function = "hello_world" | ||
output_field = models.CharField() | ||
create_function = """\ | ||
CREATE OR REPLACE FUNCTION hello_world() | ||
RETURNS varchar | ||
AS $$ | ||
BEGIN | ||
RETURN 'Hello World!'; | ||
END; | ||
$$ LANGUAGE plpgsql; | ||
""" | ||
|
||
|
||
class PlaceholderModel(models.Model): | ||
class Meta: | ||
constraints = [ | ||
func_as_constraint(HelloWorld), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import pytest | ||
|
||
from .models import HelloWorld, PlaceholderModel | ||
|
||
pytestmark = pytest.mark.django_db | ||
|
||
|
||
def test_hello_world(): | ||
PlaceholderModel.objects.create() | ||
|
||
qs = PlaceholderModel.objects.annotate(hello_world=HelloWorld()) | ||
|
||
assert qs[0].hello_world == "Hello World!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters