Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1015 Add basic math functions #1016

Merged
merged 1 commit into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/piccolo/functions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ Functions can be used to modify how queries are run, and what is returned.

./basic_usage
./string
./math
./type_conversion
./aggregate
28 changes: 28 additions & 0 deletions docs/src/piccolo/functions/math.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Math functions
==============

.. currentmodule:: piccolo.query.functions.math

Abs
---

.. autoclass:: Abs
:class-doc-from: class

Ceil
----

.. autoclass:: Ceil
:class-doc-from: class

Floor
-----

.. autoclass:: Floor
:class-doc-from: class

Round
-----

.. autoclass:: Round
:class-doc-from: class
5 changes: 5 additions & 0 deletions piccolo/query/functions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
from .aggregate import Avg, Count, Max, Min, Sum
from .math import Abs, Ceil, Floor, Round
from .string import Length, Lower, Ltrim, Reverse, Rtrim, Upper
from .type_conversion import Cast

__all__ = (
"Abs",
"Avg",
"Cast",
"Ceil",
"Count",
"Floor",
"Length",
"Lower",
"Ltrim",
"Max",
"Min",
"Reverse",
"Round",
"Rtrim",
"Sum",
"Upper",
Expand Down
48 changes: 48 additions & 0 deletions piccolo/query/functions/math.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
These functions mirror their counterparts in the Postgresql docs:

https://www.postgresql.org/docs/current/functions-math.html

"""

from .base import Function


class Abs(Function):
"""
Absolute value.
"""

function_name = "ABS"


class Ceil(Function):
"""
Nearest integer greater than or equal to argument.
"""

function_name = "CEIL"


class Floor(Function):
"""
Nearest integer less than or equal to argument.
"""

function_name = "FLOOR"


class Round(Function):
"""
Rounds to nearest integer.
"""

function_name = "ROUND"


__all__ = (
"Abs",
"Ceil",
"Floor",
"Round",
)
Empty file.
34 changes: 34 additions & 0 deletions tests/query/functions/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import typing as t
from unittest import TestCase

from piccolo.table import Table, create_db_tables_sync, drop_db_tables_sync
from tests.example_apps.music.tables import Band, Manager


class FunctionTest(TestCase):
tables: t.List[t.Type[Table]]

def setUp(self) -> None:
create_db_tables_sync(*self.tables)

def tearDown(self) -> None:
drop_db_tables_sync(*self.tables)


class BandTest(FunctionTest):
tables = [Band, Manager]

def setUp(self) -> None:
super().setUp()

manager = Manager({Manager.name: "Guido"})
manager.save().run_sync()

band = Band(
{
Band.name: "Pythonistas",
Band.manager: manager,
Band.popularity: 1000,
}
)
band.save().run_sync()
64 changes: 64 additions & 0 deletions tests/query/functions/test_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from piccolo.query.functions import Reverse, Upper
from piccolo.querystring import QueryString
from tests.base import engines_skip
from tests.example_apps.music.tables import Band

from .base import BandTest


@engines_skip("sqlite")
class TestNested(BandTest):
"""
Skip the the test for SQLite, as it doesn't support ``Reverse``.
"""

def test_nested(self):
"""
Make sure we can nest functions.
"""
response = Band.select(Upper(Reverse(Band.name))).run_sync()
self.assertListEqual(response, [{"upper": "SATSINOHTYP"}])

def test_nested_with_joined_column(self):
"""
Make sure nested functions can be used on a column from a joined table.
"""
response = Band.select(Upper(Reverse(Band.manager._.name))).run_sync()
self.assertListEqual(response, [{"upper": "ODIUG"}])

def test_nested_within_querystring(self):
"""
If we wrap a function in a custom QueryString - make sure the columns
are still accessible, so joins are successful.
"""
response = Band.select(
QueryString("CONCAT({}, '!')", Upper(Band.manager._.name)),
).run_sync()

self.assertListEqual(response, [{"concat": "GUIDO!"}])


class TestWhereClause(BandTest):

def test_where(self):
"""
Make sure where clauses work with functions.
"""
response = (
Band.select(Band.name)
.where(Upper(Band.name) == "PYTHONISTAS")
.run_sync()
)
self.assertListEqual(response, [{"name": "Pythonistas"}])

def test_where_with_joined_column(self):
"""
Make sure where clauses work with functions, when a joined column is
used.
"""
response = (
Band.select(Band.name)
.where(Upper(Band.manager._.name) == "GUIDO")
.run_sync()
)
self.assertListEqual(response, [{"name": "Pythonistas"}])
39 changes: 39 additions & 0 deletions tests/query/functions/test_math.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import decimal

from piccolo.columns import Numeric
from piccolo.query.functions.math import Abs, Ceil, Floor, Round
from piccolo.table import Table

from .base import FunctionTest


class Ticket(Table):
price = Numeric(digits=(5, 2))


class TestMath(FunctionTest):

tables = [Ticket]

def setUp(self):
super().setUp()
self.ticket = Ticket({Ticket.price: decimal.Decimal("36.50")})
self.ticket.save().run_sync()

def test_floor(self):
response = Ticket.select(Floor(Ticket.price, alias="price")).run_sync()
self.assertListEqual(response, [{"price": decimal.Decimal("36.00")}])

def test_ceil(self):
response = Ticket.select(Ceil(Ticket.price, alias="price")).run_sync()
self.assertListEqual(response, [{"price": decimal.Decimal("37.00")}])

def test_abs(self):
self.ticket.price = decimal.Decimal("-1.50")
self.ticket.save().run_sync()
response = Ticket.select(Abs(Ticket.price, alias="price")).run_sync()
self.assertListEqual(response, [{"price": decimal.Decimal("1.50")}])

def test_round(self):
response = Ticket.select(Round(Ticket.price, alias="price")).run_sync()
self.assertListEqual(response, [{"price": decimal.Decimal("37.00")}])
25 changes: 25 additions & 0 deletions tests/query/functions/test_string.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from piccolo.query.functions.string import Upper
from tests.example_apps.music.tables import Band

from .base import BandTest


class TestUpperFunction(BandTest):

def test_column(self):
"""
Make sure we can uppercase a column's value.
"""
response = Band.select(Upper(Band.name)).run_sync()
self.assertListEqual(response, [{"upper": "PYTHONISTAS"}])

def test_alias(self):
response = Band.select(Upper(Band.name, alias="name")).run_sync()
self.assertListEqual(response, [{"name": "PYTHONISTAS"}])

def test_joined_column(self):
"""
Make sure we can uppercase a column's value from a joined table.
"""
response = Band.select(Upper(Band.manager._.name)).run_sync()
self.assertListEqual(response, [{"upper": "GUIDO"}])
Loading
Loading