Source code for kaiju_db.functions

"""SQL functions related classes."""

from typing import Tuple, Type

from kaiju_tools.registry import ClassRegistry
from sqlalchemy import DDL as BaseDDl
from sqlalchemy.event import listen
from sqlalchemy.sql.functions import GenericFunction


__all__ = ['DDL', 'UserFunction', 'SQLFunctionsRegistry', 'SQL_FUNCTIONS']


[docs]def DDL(target, identifier, body): # noqa """ Specifies literal SQL DDL to be executed by the database. DDL objects function as DDL event listeners, and can be subscribed to those events listed in :class:`.DDLEvents`, using either :class:`.Table` or :class:`.MetaData` objects as targets. Basic templating support allows a single DDL instance to handle repetitive tasks for multiple tables. example create function before create table task: .. code-block:: python DDL( task, "before_create", \""" CREATE OR REPLACE FUNCTION TaskSortingIncrement() RETURNS trigger AS $BODY$BEGIN NEW.sort:= (SELECT COALESCE(max(sort), 0) FROM task WHERE status_id = NEW.status_id) + 1; RETURN NEW; END $BODY$ LANGUAGE plpgsql VOLATILE COST 100; \""" ) example create trigger after create table task: .. code-block:: python DDL( task, "after_create", \""" DROP TRIGGER IF EXISTS TaskTriggerBeforeInsert ON task; CREATE TRIGGER TaskTriggerBeforeInsert BEFORE INSERT ON task FOR EACH ROW EXECUTE PROCEDURE TaskSortingIncrement(); \""" ) :param target: - table :param identifier: - see doc https://docs.sqlalchemy.org/en/13/core/event.html :param body: - sql """ func = BaseDDl(body) listen(target, identifier, func.execute_if(dialect='postgresql'))
[docs]class UserFunction(GenericFunction): """Function interface for SQL functions. .. code-block:: python class MyFunction(UserFunction): package = 'utils' name = 'my_func' type = sa.types.Integer body = "my_func(v integer) RETURNS integer AS $$ BEGIN RETURN v; END; $$ LANGUAGE PLPGSQL;" def __init__(self, v: int): return super().__init__(v) Now you can use this function from python. See `https://docs.sqlalchemy.org/en/13/core/functions.html` for detail about generic functions methods. .. code-block:: python await self.app.db.fetchval(MyFunction(42).select()) To make your function actually available in your app you need to tell the database service about it, i.e. register it in the function registry .. code-block:: from kaiju_db.services import functions_registry functions_registry.register_class(UserFunction) or (if you have many) .. code-block:: from kaiju_db.services import functions_registry import my_functions functions_registry.register_from_module(my_functions) """ body: str #: executable sql string @classmethod def sql(cls) -> str: body = cls.body.strip() if not body.lower().startswith('function'): body = f'FUNCTION {body}' if not body.endswith(';'): body += ';' return body
[docs]class SQLFunctionsRegistry(ClassRegistry): """Registry for SQL functions.""" @classmethod def get_base_classes(cls) -> tuple[type, ...]: return (UserFunction,) @classmethod def get_key(cls, obj) -> str: return obj.name
SQL_FUNCTIONS = SQLFunctionsRegistry(raise_if_exists=False) #: sql functions registry