"""You are an expert assistant specialized in the Ormophine PostgreSQL Python ORM.
The text below this line is the COMPLETE source code of the Ormophine PostgreSQL ORM library.
Your sole reference for answering any question is this code.
When a user asks about usage, errors, features, or implementation details, analyze the code and provide accurate, clear answers.
Include relevant code snippets and explain how they relate to the user's question.
Do not mention that you are an AI; simply respond as a knowledgeable human expert.
Be concise, helpful, and practical."""




from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            
<<<<<<< Updated upstream
=======


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            
>>>>>>> Stashed changes


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            
<<<<<<< Updated upstream
=======


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            
>>>>>>> Stashed changes


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            


from . import TableStructure, Table
from psycopg  import connect, OperationalError, ProgrammingError
from queue import SimpleQueue, Empty
from typing import Literal

            
class Driver():
    """High-level PostgreSQL driver for Ormophine.

    The driver is the main entry point for working with PostgreSQL databases.
    It manages connection pooling, auto-discovers existing tables, and exposes
    table objects directly as attributes on the driver instance. It also provides
    a Pythonic API for CRUD operations, joins, batch transactions, schema
    management, user/role administration, and privilege control.

    Tables are reflected automatically at construction time and can be accessed
    as attributes (``driver.users``, ``driver.orders``, ...). Each attribute is
    a :class:`Table` instance exposing column objects and high-level methods.

    Parameters
    ----------
    host : str
        Database server host.
    port : int
        Port number.
    username : str
        Database user name.
    password : str
        Database password.
    db_name : str
        Database name.
    create_new_db : bool, optional
        If ``True``, connect to the ``postgres`` maintenance database and
        issue ``CREATE DATABASE`` (with the given encoding and optional
        collation) before opening the pool. Defaults to ``False``.
    pool_size : int, optional
        Number of pooled connections. Defaults to ``5``.
    connect_timeout : int, optional
        Connection timeout in seconds. Defaults to ``10``.
    client_encoding : CHARSET, optional
        Connection encoding (e.g. ``"UTF8"``, ``"LATIN1"``). Defaults to
        ``"UTF8"``.
    collate : COLLATE or None, optional
        Collation used only when creating a new database
        (e.g. ``"en_US.UTF-8"``). Defaults to ``None``.
    isolation_level : ISOLATION_LEVEL, optional
        Transaction isolation level applied to every pooled session. One of
        ``'READ UNCOMMITTED'``, ``'READ COMMITTED'``, ``'REPEATABLE READ'``,
        or ``'SERIALIZABLE'``. Defaults to ``'READ COMMITTED'``.

    Attributes
    ----------
    CHARSET : Literal
        Allowed values for ``client_encoding``.
    COLLATE : Literal
        Allowed collations for ``collate``.
    ISOLATION_LEVEL : Literal
        Allowed transaction isolation levels.
    PRIVILEGES : Literal
        Allowed privilege names used by :meth:`grant_privileges` and
        :meth:`revoke_privileges`.

    Notes
    -----
    * The driver maintains a thread-safe connection pool (``SimpleQueue``) and
      transparently retries once when a transient connection error
      (SQLSTATE in ``CONNECTION_ERRORS``) is detected.
    * ``delete_table``, ``delete_database``, ``delete_column``,
      ``delete_index`` and similar destructive operations are guarded by
      three boolean confirmation flags (``are_you_sure``,
      ``are_you_really_sure``, ``for_sure``) that must all be ``True``.

    Example
    -------
    Connect and use CRUD::

        >>> from Ormophine.Postgresql import Driver, DataTypes, TableStructure
        >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
        >>> users = driver.users
        >>> structure = TableStructure("products")
        >>> structure.add_column("id", DataTypes.SERIAL(), primary_key=True)
        >>> structure.add_column("name", DataTypes.VARCHAR(100))
        >>> driver.create_table(structure)
        >>> driver.products.insert({driver.products.name: "Widget"})
        >>> driver.disconnect()

    Create a database and administer users::

        >>> driver = Driver(
        ...     "localhost", 5432, "postgres", "secret", "shop",
        ...     create_new_db=True, client_encoding="UTF8",
        ...     collate="en_US.UTF-8", isolation_level="SERIALIZABLE"
        ... )
        >>> driver.create_user("app_user", "s3cr3t")
        >>> driver.grant_privileges("app_user", "SELECT, INSERT", "shop")
        >>> driver.get_databases()

    Execute raw SQL and run maintenance::

        >>> driver.custom_execute("CREATE INDEX idx_users_name ON users (name);")
        >>> rows = driver.custom_execute_with_fetch(
        ...     "SELECT id FROM users WHERE age > %s", (18,)
        ... )
        >>> driver.optimize()   # VACUUM (ANALYZE) on all user tables
        >>> driver.disconnect()
    """
    CHARSET = Literal[
    "UTF8",
    "LATIN1",
    "SQL_ASCII",
    "WIN1252",
    "WIN1256",
    "KOI8R",
    "ISO_8859_5",
    "ISO_8859_6",
    "ISO_8859_7",
    "ISO_8859_8",
    "EUC_JP",
    "EUC_KR",
    "EUC_CN",
    "EUC_TW",
    "GB18030",
    "GBK",
    "BIG5",
    "SHIFT_JIS_2004",
    "UHC",
    "JOHAB"
    ]
    COLLATE = Literal[
    "en_US.UTF-8",
    "de_DE.UTF-8",
    "fr_FR.UTF-8",
    "fa_IR.UTF-8",
    "C",
    "POSIX"
    ]
    ISOLATION_LEVEL = Literal['READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ', 'SERIALIZABLE']
    PRIVILEGES = Literal['ALL PRIVILEGES', 'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER', 'CREATE', 'CONNECT', 'TEMPORARY', 'EXECUTE', 'USAGE']

    def __init__(self, host: str, port: int, username: str, password: str, db_name: str, create_new_db: bool = False, pool_size: int = 5, connect_timeout: int = 10, client_encoding: CHARSET = "UTF8", collate: COLLATE = None, isolation_level: ISOLATION_LEVEL = 'READ COMMITTED',timezone: str = '+00:00'):
        """Initializes a PostgreSQL driver with a connection pool and table reflection.

        Creates a pool of database connections using `psycopg` and reflects all
        user tables in the current schema as :class:`Table` attributes on the
        driver instance. Optionally creates the target database if it does not
        yet exist. Connection settings (host, port, credentials, encoding,
        collation, timeout, and transaction isolation) are stored for pool
        management and automatic reconnection on transient failures.

        Args:
            host (str): PostgreSQL server hostname or IP address.
            port (int): Port number (usually 5432).
            username (str): Database user name.
            password (str): User password.
            db_name (str): Name of the database to connect to (or to create).
            create_new_db (bool): If ``True``, the driver first connects to the
                ``postgres`` maintenance database and executes ``CREATE DATABASE``
                with the given encoding and optional collation, then connects to
                the newly created database. Defaults to ``False``.
            pool_size (int): Number of persistent connections maintained in the
                pool. Defaults to ``5``.
            connect_timeout (int): Maximum time in seconds to wait for a new
                connection. Defaults to ``10``.
            client_encoding (CHARSET): PostgreSQL encoding, e.g. ``"UTF8"``,
                ``"LATIN1"``. Defaults to ``"UTF8"``.
            collate (COLLATE): Collation and character type (LC_COLLATE,
                LC_CTYPE) used when creating a new database, e.g.
                ``"en_US.UTF-8"``. Only meaningful when ``create_new_db=True``.
                Defaults to ``None``.
            isolation_level (ISOLATION_LEVEL): Transaction isolation level for
                all sessions in the pool. Must be one of ``'READ UNCOMMITTED'``,
                ``'READ COMMITTED'``, ``'REPEATABLE READ'``, or
                ``'SERIALIZABLE'``. Defaults to ``'READ COMMITTED'``.
            timezone (str, optional): PostgreSQL session time zone applied to every
                pooled connection at startup (e.g. ``'UTC'``, ``'Europe/London'``,
                ``'+03:30'``, or ``'LOCAL'``). When ``None``, the server's default
                session zone is kept. Implemented via libpq's ``options`` parameter
                (``-c timezone=...``) so that newly created connections — including
                replacements built by :meth:`_handle_broken_connection` — come up
                already in the correct zone. See :meth:`set_timezone` for accepted
                forms and caveats.

        Returns:
            None

        Raises:
            Exception: If a connection to the given database (or to
                ``postgres`` when creating a new database) fails.
            RuntimeError: If attempting to create new connections after
                :meth:`disconnect` has been called.

        Example:
            Connect to an existing database:

            >>> db = Driver(
            ...     host='127.0.0.1',
            ...     port=5432,
            ...     username='postgres',
            ...     password='secret',
            ...     db_name='my_db',
            ...     pool_size=10
            ... )
            >>> # Access tables as attributes
            >>> users = db.users  # Table object

            Create a new database and connect:

            >>> db = Driver(
            ...     host='127.0.0.1',
            ...     port=5432,
            ...     username='postgres',
            ...     password='secret',
            ...     db_name='new_database',
            ...     create_new_db=True,
            ...     client_encoding='UTF8',
            ...     collate='en_US.UTF-8'
            ... )
        """
        self.CONNECTION_ERRORS = ('08003', '08006', '08001', '57P01', '57P02', '57P03', '53300', '53000')
        self.host = host
        self.port = port
        self._connected = True
        self.username = username
        self.password = password
        self.db_name = db_name
        self.client_encoding = client_encoding
        self.collate = collate
        self.connect_timeout = connect_timeout
        self.isolation_level = isolation_level
        self.timezone = timezone                    
        options = ""
        if timezone is not None:
            for ch in (';', "'"):
                if ch in timezone:
                    raise ValueError(
                        f"Invalid timezone {timezone!r}: contains forbidden "
                        f"character {ch!r}"
                    )
            options = f"-c timezone={timezone}"      
        self.config = {
            "host": self.host,
            "port": self.port,
            "user": self.username,
            "password": self.password,
            "dbname": self.db_name,
            "client_encoding": self.client_encoding,
            "connect_timeout": self.connect_timeout,
            "options": options                       
        }
        self.connection_pool = SimpleQueue()
        self.connection_pool_storage = []
        conf = {
            "host": self.host,
            "port": self.port,
            "user": self.username,
            "password": self.password,
            "client_encoding": self.client_encoding,
            "connect_timeout": self.connect_timeout
        }
        if not create_new_db:
            try:
                connection = connect(**self.config)
                connection.close()
            except Exception as e:
                if 'connection' in locals():
                    connection.close()
                raise            
        else:
            try:
                connection = connect(**conf, dbname='postgres')
                connection.autocommit = True
                cur = connection.cursor()
                query = f"CREATE DATABASE {self.db_name} ENCODING '{self.client_encoding}' TEMPLATE template0"
                if self.collate:
                    query += f" LC_COLLATE = '{self.collate}' LC_CTYPE = '{self.collate}'"
                cur.execute(query)
                connection.close()
            except Exception:
                connection.close()
                raise                

        [self._create_connection() for _ in range(pool_size)]

        for i in self.get_tables():
            self.__setattr__(i, Table(self, i))

    def _create_connection(self):
        """Create a new database connection and add it to the connection pool.

        This internal method establishes a fresh connection to the PostgreSQL
        database using the configuration stored in :attr:`config`. It also
        opens a cursor, appends the connection to
        :attr:`connection_pool_storage`, puts the (connection, cursor) tuple
        into :attr:`connection_pool`, and immediately sets the session
        transaction isolation level to :attr:`isolation_level`.

        If an :class:`OperationalError` is raised during connection and its
        ``sqlstate`` is one of the transient error codes listed in
        :attr:`CONNECTION_ERRORS`, the method retries once before giving up.
        All other exceptions (including non‑transient
        :class:`OperationalError`) are re‑raised.

        Args:
            None (``self`` only).

        Returns:
            None: The connection is placed in the pool; nothing is returned.

        Raises:
            RuntimeError: If :attr:`_connected` is ``False``, meaning the
                driver has been disconnected and no new connections can be
                created.
            OperationalError: If the connection attempt fails with a
                non‑transient error code, or if the retry also fails.

        Example:
            Typically called automatically when the pool is exhausted:

            >>> # Inside the driver, after checking pool:
            >>> self._create_connection()
            >>> # Now pool has one more (connection, cursor) pair.
        """
        if not self._connected:
            raise RuntimeError('You have closed the connection, you can not create new connections')
        try:
            con = connect(**self.config)
            cur = con.cursor()
            cur.execute(f"SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL {self.isolation_level};")
            con.commit()
            self.connection_pool.put((con, cur))
            self.connection_pool_storage.append(con)
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:
                con = connect(**self.config)
                cur = con.cursor()
                cur.execute(f"SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL {self.isolation_level};")
                con.commit()
                self.connection_pool.put((con, cur))
                self.connection_pool_storage.append(con)
            else:
                raise

    def _get_connection(self):
        """Retrieves a database connection and cursor from the connection pool.

        This internal method attempts to obtain a (connection, cursor) tuple
        from the thread‑safe :attr:`connection_pool` queue. If the pool is
        empty, a new connection is created via :meth:`_create_connection` and
        a second attempt is made. The method blocks for up to 0.5 seconds on
        each queue retrieval.

        Returns:
            tuple: A ``(psycopg.connection, psycopg.cursor)`` pair that can
            be used to execute queries. The connection's transaction isolation
            level is already set.

        Raises:
            Exception: If the connection pool remains empty even after
                attempting to create a new connection. The exception message
                suggests increasing the ``pool_size``.
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        try:
            return self.connection_pool.get(block=True, timeout=0.5)
        except Empty:
            self._create_connection()
            try:
                return self.connection_pool.get(block=True, timeout=0.5)
            except Empty as e:
                raise Exception(f'{e}\n\nEmpty connection pool, you better increase `pool_size`')#TODO Create get_schema() from table and db and column 

    def _excfp(self, query, params):
        """Execute a parameterized query, fetch all results, and return them.

        This internal method acquires a connection and cursor from the
        connection pool, executes the given SQL query with the provided
        parameters, fetches all rows, and then returns the result set after
        committing the transaction and releasing the connection back to the
        pool. If a connection-level error (e.g., broken connection) is
        detected, the method attempts to recover by discarding the broken
        connection and retrying the operation once with a newly created
        connection. In case of a programming error, the transaction is
        rolled back before re‑raising. 

        Args:
            query (str): The SQL statement to execute. Placeholders must be
                ``%s`` style (as used by ``psycopg``).
            params (tuple | list): The parameter values to substitute into
                the query. Can be ``None`` if the query has no placeholders
                (though :meth:`_excf` is preferred in that case).

        Returns:
            list[tuple]: The list of rows returned by the query, where each
            row is a tuple of column values in the order specified by the
            ``SELECT`` clause.

        Raises:
            Exception: If the query fails due to an operational error
                (including after a retry) or a programming error. The
                exception message includes the original error, the query,
                and the parameters for debugging.

        Example:
            >>> # Internal usage: fetch column info for a table
            >>> query = "SELECT column_name FROM information_schema.columns WHERE table_name = %s"
            >>> result = db._excfp(query, ('users',))
            >>> print(result)
            [('id',), ('name',), ('email',)]
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        con, cur = self._get_connection()
        try:
            cur.execute(query, params)
            res = cur.fetchall()
            con.commit()
            self.connection_pool.put((con, cur))
            return res
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:
                self._handle_broken_connection(con)
                con, cur = self._get_connection()
                try:
                    cur.execute(query, params)
                    res = cur.fetchall()
                    con.commit()
                    self.connection_pool.put((con, cur))
                    return res
                except OperationalError:
                    self._handle_broken_connection(con)
                    raise
            else:
                con.rollback()
                self.connection_pool.put((con, cur))
                raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')
        except ProgrammingError as e:
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')
        except Exception as e:   # <--- اضافه کنید
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')
    
    def _excf(self, query):
        """Execute a parameterless query and return all fetched rows.

        Obtains a connection and cursor from the internal connection pool,
        runs the SQL statement, fetches the complete result set, commits
        (if successful), and returns the data. The connection is always
        returned to the pool afterwards. When the connection is broken
        (e.g., due to a server restart), it transparently replaces the
        connection and retries the query once. If the error is a client-side
        programming mistake, a rollback is issued before re-raising.

        Args:
            query (str): The SQL query string to be executed. Must not contain
                parameters; use :meth:`_excfp` for parameterised queries.

        Returns:
            list[tuple]: A list of tuples, where each tuple represents a row.
            The order of values in each tuple corresponds to the columns in
            the ``SELECT`` list.

        Raises:
            Exception: If an :class:`OperationalError` or
                :class:`ProgrammingError` occurs. The exception message
                includes the original error and the failing query for
                debugging.

        Example:
            This method is normally called internally by higher-level APIs,
            but can be used directly for custom raw queries:

            >>> db = Driver(...)
            >>> rows = db._excf("SELECT * FROM users WHERE active = true;")
            >>> for row in rows:
            ...     print(row)
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        con, cur = self._get_connection()
        try:
            cur.execute(query)
            res = cur.fetchall()
            con.commit()
            self.connection_pool.put((con, cur))
            return res
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:
                self._handle_broken_connection(con)
                con, cur = self._get_connection()
                try:
                    cur.execute(query)
                    res = cur.fetchall()
                    con.commit()
                    self.connection_pool.put((con, cur))
                    return res
                except OperationalError:
                    self._handle_broken_connection(con)
                    raise
            else:
                con.rollback()
                self.connection_pool.put((con, cur))
                raise Exception(f'{e}\nQuery:\n\t{query}')
        except ProgrammingError as e:
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}')
        except Exception as e:   # <--- اضافه کنید
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}')

    def _excp(self, query, params):
        """Executes a parameterized query and commits the transaction immediately.

        This method obtains a connection from the driver's pool, executes the
        given SQL statement with the provided parameters, and commits the
        changes. If a connection error occurs (e.g., server restart), it
        discards the broken connection, acquires a new one, and retries the
        operation once. For other errors, the transaction is rolled back and
        a descriptive exception is raised. The connection is always returned
        to the pool after use (or after a failure cleanup).

        Args:
            query (str): The SQL statement to execute (e.g., ``INSERT``,
                ``UPDATE``, ``DELETE``). Use ``%s`` placeholders for parameters.
            params (tuple | list | None): The parameter values to bind to the
                query. Can be ``None`` if the query has no placeholders.

        Returns:
            None

        Raises:
            Exception: If the query fails due to a programming error (e.g.,
                invalid syntax, missing table) or a non-retryable operational
                error, the original exception is wrapped with the query text
                and parameters for debugging. Fatal connection errors are
                re-raised after a retry attempt.

        Example:
            >>> driver._excp(
            ...     "INSERT INTO users (name, age) VALUES (%s, %s)",
            ...     ("Alice", 30)
            ... )
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        con, cur = self._get_connection()
        try:
            cur.execute(query, params)
            con.commit()
            self.connection_pool.put((con, cur))
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:
                self._handle_broken_connection(con)
                con, cur = self._get_connection()
                try:
                    cur.execute(query, params)
                    con.commit()
                    self.connection_pool.put((con, cur))
                except OperationalError:
                    self._handle_broken_connection(con)
                    raise
            else:
                con.rollback()
                self.connection_pool.put((con, cur))
                raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')
        except ProgrammingError as e:
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')
        except Exception as e:   
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')

    def _exc(self, query):
        """Executes a SQL command without parameters and commits immediately.

        This internal helper obtains a connection from the driver's connection pool,
        executes the given query, and commits the transaction. If a recoverable
        connection error occurs (e.g., server restart), it discards the broken
        connection, creates a new one, and retries the operation once. For
        non-recoverable operational errors or programming mistakes, the transaction
        is rolled back and a descriptive exception is raised. The connection is
        always returned to the pool after use (or after a failure cleanup).

        Args:
            query (str): The SQL statement to execute. It should not contain
                placeholders; for parameterized queries use :meth:`_excp`.

        Returns:
            None

        Raises:
            Exception: If the query fails due to a programming error (e.g.,
                invalid syntax, missing table) or a non-retryable operational
                error, the original exception is wrapped with the query text
                for debugging. Fatal connection errors are re-raised after a
                retry attempt.

        Example:
            >>> driver._exc("DROP TABLE users;")
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        con, cur = self._get_connection()
        try:
            cur.execute(query)
            con.commit()
            self.connection_pool.put((con, cur))
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:
                self._handle_broken_connection(con)
                con, cur = self._get_connection()
                try:
                    cur.execute(query)
                    con.commit()
                    self.connection_pool.put((con, cur))
                except OperationalError:
                    self._handle_broken_connection(con)
                    raise
            else:
                con.rollback()
                self.connection_pool.put((con, cur))
                raise Exception(f'{e}\nQuery:\n\t{query}')
        except ProgrammingError as e:
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}')
        except Exception as e:   # <--- اضافه کنید
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}')
    
    def _excs(self, query_params: list):
        """Executes a batch of SQL statements within a single transaction.

        Iterates over a list of query specifications. Each item can be a plain
        SQL string (executed directly) or a two-element list/tuple ``[query,
        params]`` for parameterized execution. All statements are run on a
        single connection obtained from the driver's pool. If a connection‑loss
        error is detected, the broken connection is discarded, a new one is
        acquired, and the entire batch is retried once. For other operational
        or programming errors, the transaction is rolled back and a descriptive
        exception is raised, including the list of queries and their parameters.

        Args:
            query_params (list[tuple | str]): A list of query specifications.
                Each element may be:
                - a string containing the SQL statement, or
                - a list/tuple of exactly two elements: ``[query_string,
                params]``, where ``params`` is a tuple or list of parameter
                values to be passed to the driver's parameter substitution
                (``%s`` placeholders).

        Returns:
            None

        Raises:
            Exception: If any statement fails due to a programming error
                (e.g., invalid SQL) or a non‑retryable operational error. The
                exception message includes the original error and a summary of
                all queries and parameters. Fatal connection errors are
                re‑raised after a retry attempt.

        Example:
            >>> driver._excs([
            ...     "INSERT INTO log (msg) VALUES ('start')",
            ...     ["UPDATE users SET age = %s WHERE name = %s", (30, "Alice")]
            ... ])
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        con, cur = self._get_connection()
        try:
            for q in query_params:
                if len(q) == 2:
                    cur.execute(q[0], q[1])
                else:
                    cur.execute(q[0])
            con.commit()
            self.connection_pool.put((con, cur))
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:
                self._handle_broken_connection(con)
                con, cur = self._get_connection()
                try:
                    for q in query_params:
                        if len(q) == 2:
                            cur.execute(q[0], q[1])
                        else:
                            cur.execute(q[0])
                    con.commit()
                    self.connection_pool.put((con, cur))
                except OperationalError:
                    self._handle_broken_connection(con)
                    raise
            else:
                con.rollback()
                self.connection_pool.put((con, cur))
                queries_str = '\n'.join([f'Query: {q[0]}\nParams: {q[1] if len(q)>1 else ""}' for q in query_params])
                raise Exception(f'{e}\n{queries_str}')
        except ProgrammingError as e:
            con.rollback()
            self.connection_pool.put((con, cur))
            queries_str = '\n'.join([f'Query: {q[0]}\nParams: {q[1] if len(q)>1 else ""}' for q in query_params])
            raise Exception(f'{e}\n{queries_str}')
        except Exception as e:   # <--- اضافه کنید
            con.rollback()
            self.connection_pool.put((con, cur))
            queries_str = '\n'.join([f'Query: {q[0]}\nParams: {q[1] if len(q)>1 else ""}' for q in query_params])
            raise Exception(f'{e}\n{queries_str}')
    
    def _excm(self, query, params):
        """Executes a parameterized SQL statement with multiple rows using ``executemany``.

        Retrieves a connection from the driver's pool, runs the given query once
        for each element in ``params`` via the cursor's ``executemany`` method,
        and commits the transaction. If a connection error occurs (e.g., server
        restart), it discards the broken connection, acquires a new one, and
        retries the operation once. For other errors, the transaction is rolled
        back and a descriptive exception is raised. The connection is always
        returned to the pool after use.

        Args:
            query (str): The SQL statement to execute. Use ``%s`` placeholders
                for parameters.
            params (list[tuple] | list[list]): A sequence of parameter groups,
                where each group provides the values for the ``%s`` placeholders
                in one execution.

        Returns:
            None

        Raises:
            Exception: If the query fails due to a programming error (e.g.,
                invalid syntax, missing table) or a non-retryable operational
                error, the original exception is wrapped with the query text
                and parameters for debugging. Fatal connection errors are
                re-raised after a retry attempt.

        Example:
            >>> driver._excm(
            ...     "INSERT INTO users (name, age) VALUES (%s, %s)",
            ...     [("Alice", 30), ("Bob", 25), ("Charlie", 35)]
            ... )
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        con, cur = self._get_connection()
        
        try:
            cur.executemany(query, params)
            con.commit()
            self.connection_pool.put((con, cur))
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:
                self._handle_broken_connection(con)
                con, cur = self._get_connection()
                try:
                    cur.executemany(query, params)
                    con.commit()
                    self.connection_pool.put((con, cur))
                except OperationalError:
                    self._handle_broken_connection(con)
                    raise
            else:
                con.rollback()
                self.connection_pool.put((con, cur))
                raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')
        except Exception as e:
            con.rollback()
            self.connection_pool.put((con, cur))
            raise Exception(f'{e}\nQuery:\n\t{query}\nParams:\n\t{params}')


    def _handle_broken_connection(self, con):
        """Closes a broken connection, removes it from the pool, and creates a fresh one.

        This internal method is called when a database operation fails with a
        connection‑error SQLSTATE (e.g., ``08003``, ``08006``). It attempts to
        close the faulty connection safely, removes it from the driver's
        internal storage list, and then delegates to
        :meth:`_create_connection` to add a new, healthy connection to the pool.

        Args:
            con (psycopg.Connection): The broken database connection to be
                discarded.

        Returns:
            None

        Raises:
            OperationalError: Propagated from :meth:`_create_connection` if
                establishing a replacement connection fails.

        Example:
            >>> # Internally, after catching an OperationalError with
            >>> # a connection‑error SQLSTATE:
            >>> except OperationalError as e:
            ...     if e.sqlstate in self.CONNECTION_ERRORS:
            ...         self._handle_broken_connection(con)
            ...         con, cur = self._get_connection()
        """
        if not self._connected:
            raise RuntimeError("Driver disconnected")
        try:
            con.close()
        except:
            pass
        if con in self.connection_pool_storage:
            self.connection_pool_storage.remove(con)
        self._create_connection()

    def delete_table(self, table: Table, are_you_sure: bool, are_you_really_sure: bool, for_sure: bool):
        """Drops a table from the database and removes it from the driver instance.

        Executes the ``DROP TABLE`` statement for the given :class:`Table` object.
        The operation is gated by three explicit confirmation flags that must all be
        ``True`` to proceed, preventing accidental deletion. After successful
        execution, the corresponding attribute on the :class:`Driver` instance is
        deleted, so any subsequent access will raise an ``AttributeError``.

        Args:
            table (:class:`Table`): The table object to be dropped. Must exist in
                the database and be an attribute of this :class:`Driver`.
            are_you_sure (bool): First confirmation flag.
            are_you_really_sure (bool): Second confirmation flag.
            for_sure (bool): Third confirmation flag. All three must be ``True``
                to execute the deletion.

        Returns:
            None

        Raises:
            Exception: If the ``DROP TABLE`` statement fails (e.g., table does
                not exist, insufficient privileges, or connection error). The
                original error is re‑raised with query details.

        Example:
            >>> db = Driver(host='localhost', port=5432, username='user',
            ...             password='pass', db_name='mydb')
            >>> users = db.users  # existing Table object
            >>> db.delete_table(users, are_you_sure=True,
            ...                 are_you_really_sure=True, for_sure=True)
            >>> # Accessing db.users now raises AttributeError
        """
        if are_you_sure and are_you_really_sure and for_sure:
            self._exc(f'DROP TABLE {table.name_};')
            self.__delattr__(table.name_.strip('"'))

    def delete_database(self, database_name: str, are_you_sure: bool, are_you_really_sure: bool, for_sure: bool):
        """Drops an entire PostgreSQL database.

        This method deletes the specified database from the server. Because
        ``DROP DATABASE`` cannot execute inside a transaction block, the
        connection's autocommit mode is temporarily enabled for the duration
        of the command. The operation is gated by three explicit boolean
        flags that must all be ``True`` to proceed – a safety mechanism to
        prevent accidental database deletion. If any flag is ``False``, the
        method silently returns without performing any action.

        Args:
            database_name (str): The name of the database to drop.
            are_you_sure (bool): First confirmation flag.
            are_you_really_sure (bool): Second confirmation flag.
            for_sure (bool): Third confirmation flag. All three must be
                ``True`` for the deletion to execute.

        Returns:
            None: The database is dropped if all confirmation flags are
            ``True``; otherwise, the method returns immediately.

        Raises:
            Exception: If the ``DROP DATABASE`` command fails (e.g.,
                database does not exist or there are active connections).
                The original exception from the database driver is re‑raised
                after resetting autocommit.

        Example:
            >>> driver = Driver("localhost", 5432, "postgres", "secret", "mydb")
            >>> # Drop the database "old_project" with triple confirmation
            >>> driver.delete_database(
            ...     "old_project",
            ...     are_you_sure=True,
            ...     are_you_really_sure=True,
            ...     for_sure=True
            ... )
        """
        if are_you_sure and are_you_really_sure and for_sure:
            con, cur = self._get_connection()
            try:
                con.rollback()
                con.autocommit = True
                cur.execute(f'DROP DATABASE "{database_name}";')
                con.autocommit = False
                self.connection_pool.put((con, cur))
            except Exception:
                try:
                    con.autocommit = False
                except:
                    pass
                self.connection_pool.put((con, cur))
                raise

    def custom_execute_with_fetch(self, query, params=None):
        """Executes a raw SQL query and returns the fetched results.

        This method provides direct access to the database for custom
        ``SELECT`` or other read‑only queries. It automatically obtains a
        connection from the pool, executes the query, fetches all rows, and
        returns them. If a connection error occurs (e.g., server restart), it
        discards the broken connection, acquires a new one, and retries once.
        For other errors, the transaction is rolled back and a descriptive
        exception is raised, including the query text and parameters.

        Args:
            query (str): The SQL statement to execute. Use ``%s``
                placeholders for parameters.
            params (tuple | list | None): Parameter values to bind into the
                query. If ``None``, the query is executed without parameters.

        Returns:
            list[tuple]: A list of tuples, where each tuple represents a row
            of the result set. If the query returns no rows, an empty list is
            returned.

        Raises:
            Exception: If the query fails due to a programming error (e.g.,
                invalid syntax, missing table) or a non‑retryable operational
                error. The exception message includes the query text and
                parameters for debugging.

        Example:
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> rows = driver.custom_execute_with_fetch(
            ...     "SELECT id, name FROM users WHERE age > %s",
            ...     (25,)
            ... )
            >>> for row in rows:
            ...     print(row)
            (1, 'Alice')
            (2, 'Bob')
        """
        return self._excfp(query, params) if params else self._excf(query)

    def custom_execute(self, query, params=None):
        """Executes an arbitrary SQL statement with optional parameters and commits.

        This is a convenience method that wraps the driver's internal execution
        functions. If ``params`` is provided, the statement is executed with
        parameterized placeholders (``%s``) via :meth:`_excp`. Otherwise, the
        raw statement is executed via :meth:`_exc`. The transaction is
        committed immediately upon success. Connection errors are automatically
        retried once with a fresh connection.

        Args:
            query (str): The SQL statement to execute (e.g., ``INSERT``,
                ``UPDATE``, ``DELETE``, or any DDL/DML). Use ``%s``
                placeholders for parameters.
            params (tuple | list | None): The parameter values to bind to the
                query. Defaults to ``None`` for statements without parameters.

        Returns:
            None

        Raises:
            Exception: If the query fails due to a programming error (e.g.,
                invalid syntax, missing table) or a non‑retryable operational
                error. The exception message includes the original error,
                the query text, and the parameters (if any) for debugging.

        Example:
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> # Execute a parameterized INSERT
            >>> driver.custom_execute(
            ...     "INSERT INTO employees (name, salary) VALUES (%s, %s)",
            ...     ("Jane Doe", 75000)
            ... )
            >>> # Execute a DDL statement without parameters
            >>> driver.custom_execute("CREATE INDEX idx_name ON employees (name);")
        """
        return self._excp(query, params) if params else self._exc(query)

    def custom_execute_many(self, query: str, params: list) -> None:
        """Executes a SQL statement multiple times with different parameter sets.

        This is a convenience wrapper around :meth:`_excm` that uses the driver's
        connection pool to run a parameterized query with ``executemany``.
        It is suitable for bulk ``INSERT``, ``UPDATE``, or ``DELETE``
        operations where the same SQL template is executed with multiple
        parameter tuples. The operation is performed atomically on a single
        connection and commits after all statements have been processed.

        Args:
            query (str): The SQL template to execute. Use ``%s`` placeholders
                for parameters.
            params (list[tuple]): A list of parameter tuples, where each
                tuple contains the values to bind for one execution of the
                query. Each tuple must have the same length and order as the
                ``%s`` placeholders in the query.

        Returns:
            None: The method returns after the batch has been committed
            successfully.

        Raises:
            Exception: If the execution fails (e.g., connection error,
                programming error). The original ``psycopg`` error is wrapped
                with the query text and parameters for debugging. In case of
                connection errors, a retry attempt is made automatically.

        Example:
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> # Insert multiple rows into the 'users' table
            >>> query = "INSERT INTO users (name, age) VALUES (%s, %s)"
            >>> data = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]
            >>> driver.custom_execute_many(query, data)
        """
        return self._excm(query, params)

    def get_databases(self):
        """Retrieves a list of all user databases on the PostgreSQL server.

        Queries the ``pg_database`` system catalog, filtering out template
        databases (e.g., ``template0``, ``template1``). The result is a list
        of database names available to the current user.

        Returns:
            list[str]: A list of database names as strings. Only non‑template
            databases are included.

        Raises:
            Exception: If the underlying query fails (e.g., connection
                loss or permission error). The original exception from
                :meth:`_excf` is propagated.

        Example:
            >>> driver = Driver("localhost", 5432, "postgres", "secret", "mydb")
            >>> dbs = driver.get_databases()
            >>> print(dbs)
            ['mydb', 'testdb', 'analytics']
        """
        return [i[0] for i in self._excf('SELECT datname FROM pg_database WHERE datistemplate = false;')]

    def get_tables(self):
        """Retrieves the names of all tables in the current schema.

        Queries the PostgreSQL system catalog to obtain a list of table names
        that exist in the schema associated with the current connection's
        search path (typically ``public``). The result excludes system tables
        and views.

        Returns:
            list[str]: A list of table name strings, ordered arbitrarily by
            the database. An empty list is returned if no tables exist.

        Example:
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> tables = driver.get_tables()
            >>> print(tables)
            ['employees', 'departments', 'projects']
        """
        return [i[0] for i in self._excf("SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = current_schema();")]
    
    def create_table(self, table_structure: TableStructure):
        """Creates a new table in the database from a :class:`TableStructure` definition.

        Executes the SQL ``CREATE TABLE`` statement generated by
        :meth:`TableStructure.get_structure` and then attaches a :class:`Table`
        object as an attribute of the driver instance, using the table name
        (without quotes) as the attribute name. This allows direct access to the
        table via ``driver.table_name``.

        Args:
            table_structure (:class:`TableStructure`): A populated table
                structure object that defines columns, constraints, and
                foreign keys. Must have at least one column added via
                :meth:`~TableStructure.add_column`.

        Returns:
            None: The method does not return a value. After successful
            execution, the table can be accessed as an attribute of the
            :class:`Driver` instance (e.g., ``driver.mytable``).

        Raises:
            Exception: If the ``CREATE TABLE`` statement fails (e.g., table
                already exists, invalid column definition, or database
                connection error). The original database error is wrapped
                and re‑raised.

        Example:
            >>> from ormophine.Postgresql import Driver, DataTypes, TableStructure
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> structure = TableStructure("employees")
            >>> structure.add_column("id", DataTypes.SERIAL(), primary_key=True)
            >>> structure.add_column("name", DataTypes.VARCHAR(100), not_null=True)
            >>> structure.add_column("salary", DataTypes.NUMERIC(10, 2))
            >>> driver.create_table(structure)
            >>> # Now the table is available as driver.employees
            >>> employees_table = driver.employees
            >>> employees_table.insert({employees_table.name: "Alice",
            ...                          employees_table.salary: 75000.00})
        """
        self._exc(table_structure.get_structure())
        self.__setattr__(table_structure.name.strip('"'), Table(self, table_structure.name.strip('"')))

    def optimize(self):
        """Performs maintenance on all user tables to reclaim storage and update statistics.

        Runs ``VACUUM (ANALYZE)`` on every table in the current schema. This
        cleans up dead rows, reclaims disk space, and refreshes the query planner
        statistics, which can significantly improve performance after large
        inserts, updates, or deletes. The operation temporarily enables
        autocommit on a connection from the pool because ``VACUUM`` cannot run
        inside a transaction block. The connection is returned to the pool after
        completion, even if an error occurs.

        Returns:
            None: The method returns after all tables have been vacuumed and
            analyzed.

        Raises:
            Exception: If any ``VACUUM (ANALYZE)`` command fails (e.g.,
                insufficient privileges or a table that cannot be vacuumed).
                The original database error is re‑raised after resetting
                autocommit and returning the connection.

        Example:
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> # After bulk data changes, run maintenance
            >>> driver.optimize()
        """
        tables = self.get_tables()
        con, cur = self._get_connection()
        try:
            con.rollback()
            con.autocommit = True
            for i in tables:
                cur.execute(f'VACUUM (ANALYZE) "{i}";')
            con.autocommit = False
            self.connection_pool.put((con, cur))
        except Exception:
            try:
                con.autocommit = False
            except:
                pass
            self.connection_pool.put((con, cur))
            raise

    def create_user(self, username: str, password: str):
        """Creates a new PostgreSQL user (role) with a login password.

        Executes a ``CREATE USER`` statement to add a new database user.
        The username is escaped to prevent SQL injection (double quotes within
        the username are replaced with ``""``). The password is provided in
        plain text and will be stored encrypted by PostgreSQL.

        Args:
            username (str): The name of the user to create. Must be a valid
                PostgreSQL identifier. Double quotes in the name are escaped
                automatically.
            password (str): The password for the user. It will be passed as
                a literal string in the SQL statement.

        Returns:
            None: The method returns ``None`` after the user is created.

        Raises:
            Exception: If the ``CREATE USER`` command fails (e.g., the user
                already exists or the connection is broken). The original
                database error is wrapped and re‑raised.

        Example:
            >>> driver = Driver("localhost", 5432, "admin", "secret", "mydb")
            >>> # Create a new user 'john_doe' with password 's3cur3!'
            >>> driver.create_user("john_doe", "s3cur3!")
        """
        forbidden = (';', '--', '\0', "'")
        for ch in forbidden:
            if ch in username:
                raise Exception(
                    f"Invalid username: '{username}' contains forbidden character '{ch}'"
                )
        safe_username = username.replace('"', '""')
        self._exc(f'CREATE USER "{safe_username}" WITH PASSWORD \'{password}\';')

    def drop_user(self, username: str):
        """Drops (deletes) a PostgreSQL user/role.

        Executes a ``DROP USER`` statement for the given username. The username
        is safely escaped by doubling any embedded double quotes before being
        placed in the SQL command. The operation is performed on a connection
        from the driver's pool and committed immediately.

        Args:
            username (str): The name of the user/role to drop. Double quotes
                inside the name are escaped automatically.

        Returns:
            None

        Raises:
            Exception: If the ``DROP USER`` command fails (e.g., the user
                does not exist, or the current user lacks privileges). The
                original database error is wrapped and re‑raised.

        Example:
            >>> driver = Driver("localhost", 5432, "postgres", "secret", "mydb")
            >>> driver.drop_user("app_user")
        """
        query = f'DROP USER "{username.replace('"', '""')}";'
        self._exc(query)

    def change_password(self, username: str, new_password: str):
        """Changes the password for a PostgreSQL user.

        Executes an ``ALTER USER ... WITH PASSWORD`` SQL statement to set
        the new password for the specified database user. The username is
        escaped to prevent double‑quote injection, and the new password is
        passed directly into the command string. No confirmation flags are
        required.

        Args:
            username (str): The name of the existing database user whose
                password should be changed.
            new_password (str): The new plain‑text password to assign. Note
                that the password is interpolated into the SQL command; any
                single quotes in the password will cause a syntax error and
                should be avoided or escaped externally.

        Returns:
            None: The operation is committed immediately on the database.

        Raises:
            Exception: If the ``ALTER USER`` command fails (e.g., the user
                does not exist, insufficient permissions, or an invalid
                password syntax). The original database error is wrapped
                and re‑raised.

        Example:
            >>> driver = Driver("localhost", 5432, "admin", "secret", "mydb")
            >>> # Change password for user 'alice'
            >>> driver.change_password("alice", "new_secure_password")
        """
        query = f"ALTER USER \"{username.replace('\"', '\"\"')}\" WITH PASSWORD '{new_password}';"
        self._exc(query)

    def rename_user(self, old_username: str, new_username: str):
        """Renames a PostgreSQL user (role).

        Executes the ``ALTER USER ... RENAME TO ...`` command to change the
        name of an existing database user. The usernames are safely quoted
        and any embedded double quotes are escaped to prevent SQL injection.

        Args:
            old_username (str): The current name of the user to rename.
            new_username (str): The new name to assign to the user.

        Returns:
            None: The method does not return a value. The user is renamed
            immediately.

        Raises:
            Exception: If the ``ALTER USER`` command fails (e.g., the old user
                does not exist, the new name is already taken, or the caller
                lacks sufficient privileges). The original database error is
                wrapped and re‑raised.

        Example:
            >>> driver = Driver("localhost", 5432, "postgres", "secret", "mydb")
            >>> driver.rename_user("john_doe", "jane_doe")
        """
        query = f'ALTER USER "{old_username.replace('"', '""')}" RENAME TO "{new_username.replace('"', '""')}";'
        self._exc(query)

    def grant_privileges(self, username: str, privileges: PRIVILEGES, database: str, table: str = '*'):
        """Grants database or table privileges to a user.

        Executes the appropriate SQL ``GRANT`` statement to assign the specified
        privileges to the given user on either an entire database or a specific
        table. If ``table`` is ``'*'`` (the default), the privileges are granted
        at the database level; otherwise, they are granted on the specified table.

        Args:
            username (str): The name of the database user receiving the privileges.
            privileges (PRIVILEGES): One of the predefined privilege strings,
                e.g., ``'SELECT'``, ``'INSERT'``, ``'ALL PRIVILEGES'``. The
                allowed values are defined in the :class:`Driver` class attribute
                ``PRIVILEGES``.
            database (str): The name of the database on which to grant privileges.
            table (str): The table name for table‑level grants. Defaults to
                ``'*'``, which grants database‑wide privileges.

        Returns:
            None

        Raises:
            Exception: If the ``GRANT`` statement fails (e.g., insufficient
                privileges or invalid username), the original database error
                is re‑raised.

        Example:
            >>> driver = Driver("localhost", 5432, "admin", "secret", "mydb")
            >>> # Grant SELECT and INSERT on the entire database
            >>> driver.grant_privileges("alice", "SELECT, INSERT", "mydb")
            >>> # Grant ALL PRIVILEGES on a specific table
            >>> driver.grant_privileges(
            ...     "bob", "ALL PRIVILEGES", "mydb", table="employees"
            ... )
        """
        if table == '*':
            query = f'GRANT {privileges} ON DATABASE "{database}" TO "{username}";'
        else:
            query = f'GRANT {privileges} ON TABLE "{database}"."{table}" TO "{username}";'
        self._exc(query)

    def revoke_privileges(self, username: str, privileges: PRIVILEGES, database: str, table: str = '*'):
        """Revokes database or table privileges from a user.

        Executes the appropriate SQL ``REVOKE`` statement to remove the specified
        privileges from the given user on either an entire database or a specific
        table. If ``table`` is ``'*'`` (the default), the privileges are revoked
        at the database level; otherwise, they are revoked on the specified table.

        Args:
            username (str): The name of the database user whose privileges are
                being revoked.
            privileges (PRIVILEGES): One of the predefined privilege strings,
                e.g., ``'SELECT'``, ``'INSERT'``, ``'ALL PRIVILEGES'``. The
                allowed values are defined in the :class:`Driver` class attribute
                ``PRIVILEGES``.
            database (str): The name of the database on which to revoke
                privileges.
            table (str): The table name for table‑level revocation. Defaults to
                ``'*'``, which revokes database‑wide privileges.

        Returns:
            None

        Raises:
            Exception: If the ``REVOKE`` statement fails (e.g., insufficient
                privileges or invalid username), the original database error
                is re‑raised.

        Example:
            >>> driver = Driver("localhost", 5432, "admin", "secret", "mydb")
            >>> # Revoke SELECT and INSERT from the entire database
            >>> driver.revoke_privileges("alice", "SELECT, INSERT", "mydb")
            >>> # Revoke ALL PRIVILEGES from a specific table
            >>> driver.revoke_privileges(
            ...     "bob", "ALL PRIVILEGES", "mydb", table="employees"
            ... )
        """
        if table == '*':
            query = f'REVOKE {privileges} ON DATABASE "{database}" FROM "{username}";'
        else:
            query = f'REVOKE {privileges} ON TABLE "{database}"."{table}" FROM "{username}";'
        self._exc(query)

    def disconnect(self):
        """Closes all database connections and shuts down the driver.

        Sets the internal ``_connected`` flag to ``False``, preventing any new
        connections from being created. It then iterates over all connections in
        the pool's storage list, attempting to close each one. Finally, it drains
        the connection pool queue to remove any remaining references. After calling
        this method, the driver instance cannot be used for database operations.

        Returns:
            None

        Example:
            >>> driver.disconnect()
        """
        if not self._connected:
            raise RuntimeError("Already disconnected")
        self._connected = False
        for i in self.connection_pool_storage:
            try:
                i.close()
            except:
                pass
        while not self.connection_pool.empty():
            self.connection_pool.get_nowait()



from __future__ import annotations
from .. import Column, ColumnsOperation


class Builtins:
    """PostgreSQL SQL function helpers that always return a :class:`ColumnsOperation`.

    ``Builtins`` is a stateless namespace of static methods, each of which
    wraps a PostgreSQL function (or, where PostgreSQL lacks a native
    function, a short composition of PostgreSQL functions) into a
    :class:`ColumnsOperation` object. Because every helper returns a
    ``ColumnsOperation``, the result can be used anywhere a column
    expression is accepted: in the ``which_columns`` list of
    :meth:`Table.get_row`, in the ``where`` condition, in the ``update``
    dict of :meth:`Table.update`, inside :meth:`~Table.bulk_update`, and
    so on. Helpers can also be nested freely — e.g.
    ``Builtins.Round(Builtins.Avg(col) * 100) / 100`` compiles into a
    single SQL expression with all parameters collected in order.

    Design
    ------
    The class is deliberately a namespace, not an instance-based helper.
    You never write ``Builtins()``; every entry point is a
    ``@staticmethod`` you call directly::

        from Ormophine.Postgresql import Driver, Builtins

        rows = users.get_row(
            [users.id, Builtins.Len(users.username)],
            where=Builtins.Len(users.username) > 5,
        )

    All three of the standard operand types are accepted by every helper:

    - :class:`Column` — the fully qualified name (`` "table"."col" ``) is
      embedded verbatim; no parameters are consumed.
    - :class:`ColumnsOperation` — the previously-built SQL fragment and
      its parameter list are reused and extended.
    - Any raw Python value (``str``, ``int``, ``float``, ``bytes``,
      ``None``, ``bool``) — bound as a ``%s`` placeholder. Binding rather
      than interpolating means the value never reaches the SQL text, so
      injection is impossible.

    Datatype propagation
    --------------------
    Each helper sets ``current_datatype`` on the returned
    ``ColumnsOperation`` to a Python type (``int``, ``float``, ``str``,
    ``bytes``, ``bool``, or ``None`` when the result's type depends on its
    branches, as in :meth:`IIf`). The downstream operator dispatcher in
    :class:`ColumnsOperation` uses this attribute to choose between
    arithmetic (``+``) and concatenation (``||``) when the expression is
    chained. Helpers therefore document their result type so that call
    sites can reason about chained expressions without trial and error.

    Conventional result types:

    ===================================  =========
    Helper family                        datatype
    ===================================  =========
    ``Len``, ``Sum``, ``Min``, ``Max``,
    ``Count``, ``Abs``, ``Sign``, ``Floor``,
    ``Ceil``, ``Int``, ``Year``, ``Month``,
    ``Day``, ``Hour``, ``Minute``, ``Second``,
    ``DayOfWeek``, ``IsoWeekday``, ``Weekday``,
    ``DayOfYear``, ``WeekOfYear``, ``UnixNow``,
    ``UnixEpoch``, ``DateDiffDays``,
    ``DateDiffSeconds``                      ``int``
    ``Avg``, ``Round``, ``Sqrt``, ``Pow``,
    ``Float``, ``Total``, ``JulianDay``      ``float``
    ``Reverse``-equivalents, ``Upper``,
    ``Lower``, ``Trim``, ``Capitalize``,
    ``Str``, ``Date``, ``Time``, ``DateTime``,
    ``Now``, ``Today``, ``Strftime``,
    ``StrftimeMod``, ``Timediff``,
    ``DateAdd``, ``DateTimeAdd``,
    ``TimeAdd``, ``Format``, ``GroupConcat``,
    ``TypeOf``                               ``str``
    ``IsNull``, ``IsNotNull``, ``Between``,
    ``Bool``                                 ``bool``
    ``IIf``                                  ``None`` (branches may
                                             disagree)
    ===================================  =========

    Differences from the MySQL backend
    ----------------------------------
    The PostgreSQL helpers are deliberately *not* a mechanical port of the
    MySQL ones; PostgreSQL has different native functions, different type
    coercions, and a richer set of built-ins to expose. The important
    differences:

    **Helpers that exist only in PostgreSQL:**

    - :meth:`Total` — PostgreSQL's ``SUM`` returns ``NULL`` over empty
      groups, so ``Total`` wraps it in ``COALESCE(SUM(...), 0)`` — the
      SQL analogue of SQLite's ``TOTAL``.
    - :meth:`GroupConcat` — wraps ``STRING_AGG`` for
      ``','.join(...)``-style aggregation.
    - :meth:`TypeOf` — wraps ``PG_TYPEOF``, which returns the *actual*
      PostgreSQL type name (``'int4'``, ``'jsonb'``, ``'uuid'``, ...) —
      far more informative than SQLite's five storage classes.
    - :meth:`JulianDay` — wraps ``EXTRACT(JULIAN FROM ...)``, matching
      SQLite's ``julianday()``.
    - :meth:`Func` — a single-operand escape hatch for any PostgreSQL
      function not covered by a dedicated helper.
    - :meth:`Format` — wraps ``FORMAT`` for the ``%s``/``%I``/``%L``
      template language.
    - :meth:`Upper`, :meth:`Lower`, :meth:`Trim` — these are exposed as
      module-level helpers *in addition* to the corresponding ``Column``
      methods, so they can be applied to arbitrary expressions built from
      ``ColumnsOperation``.
    - :meth:`Capitalize` — PostgreSQL has no native ``CAPITALIZE``, so the
      helper composes ``UPPER(SUBSTRING(x,1,1)) || LOWER(SUBSTRING(x,2))``.

    **Helpers with PostgreSQL-specific semantics:**

    - :meth:`Bool` — PostgreSQL has a real ``BOOLEAN`` type, so the
      helper casts to ``BOOLEAN`` (not ``SIGNED`` as in MySQL) and sets
      ``current_datatype = bool``.
    - :meth:`DayOfWeek` — PostgreSQL's ``DOW`` is Sunday=0 through
      Saturday=6, not the ODBC 1–7 convention MySQL uses. Use
      :meth:`Weekday` or :meth:`IsoWeekday` if you want the Python or
      ISO numbering.
    - :meth:`Weekday` — composed as
      ``((EXTRACT(DOW FROM x)::INTEGER + 6) % 7)`` to match Python's
      ``date.weekday()``.
    - :meth:`IsoWeekday` — a direct ``EXTRACT(ISODOW ...)`` (no modulo
      arithmetic needed, unlike the SQLite backend's equivalent).
    - :meth:`WeekOfYear` — a direct ``EXTRACT(WEEK ...)`` with ISO
      semantics (weeks start on Monday, week 1 contains the first
      Thursday).
    - :meth:`DateAdd` / :meth:`DateTimeAdd` / :meth:`TimeAdd` /
      :meth:`StrftimeMod` — the ``*intervals`` argument takes PostgreSQL
      interval strings like ``'1 day'``, ``'-3 hours'``, ``'2 months'``
      — **not** the SQLite-style ``'start of month'`` tokens. Use
      ``DATE_TRUNC`` via :meth:`Func` for period truncation.
    - :meth:`UnixNow` / :meth:`UnixEpoch` — composed as
      ``EXTRACT(EPOCH FROM ...)::BIGINT``, not ``UNIX_TIMESTAMP()``.
    - :meth:`Timediff` — returns the interval cast to ``text``, e.g.
      ``'2 days 05:29:18'``.

    **MySQL helpers deliberately absent here:**

    - ``IfNull`` — use :meth:`Total` for the common ``SUM + 0`` case, or
      :meth:`Func` with ``'COALESCE'`` for the general two-argument form.
    - ``Pow`` — present, but the underlying PostgreSQL function is
      ``POWER`` (not ``POW``); the helper name is kept for API parity.
    - ``UnixNow``'s MySQL form (``UNIX_TIMESTAMP()``) is not used because
      PostgreSQL has no such function.

    SQLite-style modifiers
    ----------------------
    The ``*intervals`` argument accepted by :meth:`DateAdd`,
    :meth:`DateTimeAdd`, :meth:`TimeAdd` and :meth:`StrftimeMod` expects
    PostgreSQL interval strings rather than SQLite modifier tokens. The
    accepted forms include:

    - ``'1 day'``, ``'7 days'``, ``'-1 day'`` — day arithmetic
    - ``'1 month'``, ``'3 months'``, ``'-2 months'`` — month arithmetic
      (PostgreSQL clamps end-of-month correctly)
    - ``'1 year'``, ``'-1 year'`` — year arithmetic
    - ``'2 hours'``, ``'30 minutes'``, ``'15 seconds'`` — time arithmetic
    - ``'1 day 2 hours'`` — compound intervals
    - ``'1-2'`` — SQL standard year-month form
    - ``'3 04:05:06'`` — SQL standard day-time form

    Notes
    -----
    - The helpers do not cache or memoize; every call builds a fresh
      ``ColumnsOperation``. This is deliberate — the operations are cheap
      to construct and the alternative (mutating shared state) would break
      thread-safety.
    - The class is not instantiable for use; it exists only to group the
      static methods. Attempting ``Builtins()`` succeeds but produces an
      object with no useful behaviour.
    - All parameters collected by the helpers are appended to the
      ``_output[1]`` list in the order their placeholders appear in the
      SQL fragment, which is the order psycopg expects.

    Example:
        A quick tour of the helper categories::

            from Ormophine.Postgresql import Driver, Builtins

            db    = Driver("localhost", 5432, "user", "pass", "app")
            users = db.users

            # String
            Builtins.Len(users.username)
            Builtins.Upper(users.name)
            Builtins.Lower(users.email)
            Builtins.Trim(users.name)
            Builtins.Capitalize(users.name)
            Builtins.Find(users.email, '@')
            Builtins.Format('Hello %s', users.name)

            # Aggregate
            Builtins.Sum(users.balance)
            Builtins.Avg(users.age)
            Builtins.Min(users.created_at)
            Builtins.Max(users.score)
            Builtins.Count('*')
            Builtins.Total(users.balance)
            Builtins.GroupConcat(users.username)

            # Math
            Builtins.Abs(users.delta)
            Builtins.Round(users.price)
            Builtins.Floor(users.score)
            Builtins.Ceil(users.score)
            Builtins.Sign(users.delta)
            Builtins.Sqrt(users.x * users.x + users.y * users.y)
            Builtins.Pow(users.base, users.exp)

            # Type cast
            Builtins.Int(users.text_id)
            Builtins.Float(users.text_price)
            Builtins.Str(users.numeric_code)
            Builtins.Bool(users.flag)
            Builtins.TypeOf(users.payload)

            # Condition
            Builtins.IsNull(users.email)
            Builtins.IsNotNull(users.phone)
            Builtins.Between(users.age, 18, 65)
            Builtins.IIf(users.age >= 18, 'adult', 'minor')

            # Date and time
            Builtins.Year(users.created_at)
            Builtins.Month(users.created_at)
            Builtins.Day(users.created_at)
            Builtins.Hour(users.created_at)
            Builtins.Minute(users.created_at)
            Builtins.Second(users.created_at)
            Builtins.DayOfWeek(users.created_at)
            Builtins.DayOfYear(users.created_at)
            Builtins.WeekOfYear(users.created_at)
            Builtins.Weekday(users.created_at)
            Builtins.IsoWeekday(users.created_at)
            Builtins.Strftime('YYYY-MM-DD', users.created_at)
            Builtins.Now()
            Builtins.Today()
            Builtins.UnixNow()
            Builtins.UnixEpoch(users.created_at)
            Builtins.JulianDay(users.created_at)
            Builtins.DateAdd(users.created_at, '1 day', '2 hours')
            Builtins.DateTimeAdd(users.created_at, '-1 hour')
            Builtins.TimeAdd(users.created_at, '30 minutes')
            Builtins.StrftimeMod('YYYY-MM', users.created_at, '1 month')
            Builtins.DateDiffDays(Builtins.Now(), users.created_at)
            Builtins.DateDiffSeconds(Builtins.Now(), users.updated_at)
            Builtins.Timediff(Builtins.Now(), users.created_at)

            # Escape hatch
            Builtins.Func('MD5', users.email)
    """

    class _NullCol:
        """Fallback stand-in used when a :class:`ColumnsOperation` has no originating :class:`Column`.

        Several helpers in :class:`Builtins` (e.g. :meth:`Builtins.Count`
        with ``'*'``, :meth:`Builtins.Now`, :meth:`Builtins.Today`,
        :meth:`Builtins.UnixNow`) produce SQL that does not refer to any
        particular table or column. For example ``COUNT(*)`` has no
        column operand, and ``NOW()`` is a server-side constant. But every
        :class:`ColumnsOperation` in this ORM is expected to carry a
        ``col_obj`` attribute pointing at the :class:`Column` that
        originated the expression — the operator dispatcher reads
        ``col_obj.datatype``, ``col_obj.name``, and
        ``col_obj.table_obj._PlaceHolder`` when deciding how to render a
        chained operation.

        Rather than passing ``None`` and forcing every call site to
        null-check, helpers with no natural column use this class. It
        supplies the same attribute surface as a real :class:`Column` so
        chained operations such as ``Builtins.Now() + 1`` or
        ``Builtins.Count('*') * 2`` do not raise ``AttributeError``.

        Attributes:
            datatype (None): Always ``None``. Signals that the originating
                expression is not tied to a column type, so the operator
                dispatcher falls back to its safe defaults (arithmetic
                rather than concatenation when both sides are ambiguous).
            name (str): Always the empty string. Substituting it into SQL
                would produce invalid SQL, but this path is never taken
                because the helper's own SQL fragment already contains
                the complete expression.
            first_name (str): Always the empty string. Same reasoning as
                ``name``.
            table_obj (type): A *class* (not an instance) whose only
                attribute is ``_PlaceHolder`` set to ``type(None)``. This
                ensures that ``isinstance(value, self.col_obj.table_obj._PlaceHolder)``
                is ``False`` for every real Python value — so the operator
                dispatcher's numeric-vs-string branch in
                :meth:`ColumnsOperation.__add__` never accidentally treats
                a raw literal as a placeholder. The real
                :class:`Table._PlaceHolder` class is used only in the
                ``bulk_update`` context; helpers never participate in bulk
                updates, so ``_NullCol`` supplies a benign stub.

        Example:
            Internal usage within a helper::

                @staticmethod
                def Now(_=None):
                    return Builtins._make('(NOW())', [], str, Builtins._NullCol)

            The ``Builtins._NullCol`` class itself (not an instance) is
            passed because the helpers only need the attribute surface,
            never any per-instance state. Passing the class works because
            attribute lookup falls through to the class-level definitions.
        """
        datatype   = None
        name       = ''
        first_name = ''
        class table_obj:
            _PlaceHolder = type(None)   # Nothing isinstance-matches this

    @staticmethod
    def _normalize(value):
        """Coerce an operand into a uniform ``(sql, params, datatype, col_obj)`` tuple.

        This is the single point through which every :class:`Builtins`
        helper inspects its arguments. Because the three accepted operand
        types — :class:`Column`, :class:`ColumnsOperation`, and raw Python
        values — carry their SQL information in different shapes, the
        helpers cannot consume them directly; ``_normalize`` flattens each
        shape into a common four-element tuple so the helper body only
        ever deals with one representation.

        The returned tuple has the following layout:

        - ``sql`` (str): The SQL fragment representing the operand. For a
          :class:`Column` this is the fully qualified name
          (`` "table"."col" ``); for a :class:`ColumnsOperation` it is the
          previously-built fragment; for a raw value it is the placeholder
          ``'%s'``.
        - ``params`` (list): The parameters that must be bound to the
          placeholders in ``sql``. Always a fresh list — mutating it does
          not affect the source operand.
        - ``datatype`` (type | None): The Python type that the operand
          evaluates to on the server side, used by the helper to set
          ``current_datatype`` on its result. ``None`` for a raw literal
          whose type is unknown (e.g. when ``value is None``).
        - ``col_obj`` (Column | type[_NullCol]): The originating column,
          or the :class:`_NullCol` class when there is none. Never
          ``None`` — helpers can rely on the attribute surface being
          present.

        Args:
            value: The operand to normalize. Accepted types:

                - :class:`ColumnsOperation` — its ``_output`` tuple is
                  unpacked; the parameter list is copied so the caller
                  cannot accidentally mutate the source operation.
                - :class:`Column` — the fully qualified name is used and
                  no parameters are produced. ``col_obj`` is the column
                  itself.
                - Any other Python value — wrapped as ``('%s', [value])``
                  with ``type(value)`` as the datatype and ``_NullCol``
                  as the column stand-in.

        Returns:
            tuple[str, list, type | None, Column | type[_NullCol]]: A
            four-element tuple ``(sql, params, datatype, col_obj)`` as
            described above. The ``params`` list is always freshly
            allocated.

        Example:
            Internal usage inside a helper — :meth:`Len` delegates the
            inspection to ``_normalize`` and then builds its own SQL::

                sql, p, _, c = Builtins._normalize(value)
                return Builtins._make(f'(LENGTH({sql}))', p, int, c)

            Behaviour on each operand type::

                >>> Builtins._normalize(users.username)
                ('"users"."username"', [], str, <Column users.username>)

                >>> op = Builtins.Len(users.username)
                >>> Builtins._normalize(op)
                ('(LENGTH("users"."username"))', [], int, <Column users.username>)

                >>> Builtins._normalize('hello')
                ('%s', ['hello'], str, Builtins._NullCol)

                >>> Builtins._normalize(42)
                ('%s', [42], int, Builtins._NullCol)

                >>> Builtins._normalize(None)
                ('%s', [None], NoneType, Builtins._NullCol)
        """
        if isinstance(value, ColumnsOperation):
            raw    = value._output[1]
            params = list(raw) if isinstance(raw, list) else [raw]
            return (
                value._output[0],
                params,
                value.current_datatype,
                value.col_obj if value.col_obj is not None else Builtins._NullCol,
            )
        if isinstance(value, Column):
            return value.name, [], value.datatype, value
        return '%s', [value], type(value), Builtins._NullCol

    @staticmethod
    def _make(sql, params, datatype, col_obj):
        """Assemble a :class:`ColumnsOperation` from a raw SQL fragment and its metadata.

        Helpers build their SQL fragment by string concatenation (or by
        composing the fragments returned from :meth:`_normalize` of their
        operands), then hand the result to ``_make`` for packaging. The
        method creates a fresh ``ColumnsOperation`` **without** calling its
        ``__init__`` — it uses ``__new__`` instead — because the
        constructor initialises ``_output`` to ``('', [])`` and would
        overwrite the fragment we want to store. This is a deliberate
        optimisation: helpers are called far more often than the
        constructor, and skipping ``__init__`` avoids four attribute
        assignments per call.

        The returned object behaves exactly like any other
        ``ColumnsOperation``: it supports arithmetic, comparison, string
        methods, ``In``/``not_In``, ``If``, and so on. Its ``_output``
        tuple is the one passed in, and its ``current_datatype`` reflects
        the helper's declared result type.

        Args:
            sql (str): The complete SQL fragment for the expression,
                including any surrounding parentheses the helper chose to
                add. Must contain ``%s`` placeholders exactly as many times
                as ``params`` has elements.
            params (list): The parameter values to bind, in the order
                their placeholders appear in ``sql``. The list is stored
                by reference on the returned operation, so the caller
                should not retain and later mutate it.
            datatype (type | None): The Python type the expression
                evaluates to on the server. Used by the operator dispatcher
                when the operation is chained. Pass ``None`` when the type
                depends on runtime branches (e.g. :meth:`IIf`).
            col_obj (Column | type[_NullCol]): The originating column, or
                the :class:`_NullCol` class when there is none. Passing
                ``None`` is tolerated and treated as ``_NullCol``, but
                helpers should pass ``_NullCol`` explicitly for clarity.

        Returns:
            ColumnsOperation: A new operation whose ``_output`` is
            ``(sql, params)``, whose ``current_datatype`` is ``datatype``,
            and whose ``col_obj`` is ``col_obj`` (or ``_NullCol`` when
            ``col_obj is None``).

        Example:
            Internal usage inside a helper::

                # Len — PostgreSQL LENGTH()
                return Builtins._make(f'(LENGTH({sql}))', p, int, c)

                # Now — no operands at all
                return Builtins._make('(NOW())', [], str, Builtins._NullCol)

                # IIf — datatype intentionally None because the branches
                # may disagree
                return Builtins._make(
                    f'(CASE WHEN {cs} THEN {ts} ELSE {es} END)',
                    cp + tp + ep,
                    None,
                    c,
                )

                # DateDiffDays — nested CAST/EXTRACT
                return Builtins._make(
                    f'(CAST(EXTRACT(EPOCH FROM '
                    f'(CAST({s1} AS TIMESTAMP) - CAST({s2} AS TIMESTAMP))) '
                    f'/ 86400 AS INTEGER))',
                    p1 + p2, int, c,
                )

            The result composes with the rest of the expression system::

                >>> op = Builtins._make('(NOW())', [], str, Builtins._NullCol)
                >>> op._output
                ('(NOW())', [])
                >>> op.current_datatype
                <class 'str'>
                >>> # Chain arithmetic — the dispatcher sees str, so it
                >>> # would pick || unless overridden by a cast builtin.
                >>> (Builtins.Int(Builtins.UnixNow()) + 60)._output[0]
                '((CAST((EXTRACT(EPOCH FROM NOW())::BIGINT)) AS INTEGER) + %s)'
        """
        op = ColumnsOperation.__new__(ColumnsOperation)
        op._output          = (sql, params)
        op.col_obj          = col_obj if col_obj is not None else Builtins._NullCol
        op.current_datatype = datatype
        return op
    
    @staticmethod
    def Len(value):
        """Compute the length of a value using PostgreSQL's ``LENGTH()`` function.

        Generates a SQL expression that returns the number of characters in a
        string, or the number of bytes in a ``BYTEA``. This is the SQL
        equivalent of Python's built-in ``len()``. The result is always an
        ``INTEGER``, regardless of the input's datatype.

        The returned object is a :class:`ColumnsOperation`, so every operator
        and method of that class remains available on it — comparisons,
        string methods, slicing, ``In``, ``like``, arithmetic, and so on.

        Args:
            value: The expression whose length is computed. Supported types:

                - :class:`ColumnsOperation` — the generated SQL fragment and
                  its parameters are reused.
                - :class:`Column` — the fully qualified column name is used
                  directly in the SQL; no parameters are consumed.
                - Any raw Python value (``str``, ``int``, ``float``,
                  ``bytes``) — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(LENGTH(<expr>))`` and whose ``current_datatype`` is ``int``.

        Example:
            Basic filtering — find users whose username is longer than 5
            characters::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row(
                    [users.username],
                    where=Builtins.Len(users.username) > 5,
                )
                # SELECT "users"."username" FROM "users"
                # WHERE (LENGTH("users"."username") > %s)
                # Parameters: [5]

            As a SELECT column — the length of each username::

                rows = users.get_row([
                    users.username,
                    Builtins.Len(users.username),
                ])
                # SELECT "users"."username", (LENGTH("users"."username"))
                # FROM "users"

            Chained with other operations — the length padded up to the
            nearest multiple of 4, then selected::

                padded_len = ((Builtins.Len(users.username) + 3) / 4) * 4
                rows = users.get_row(
                    [users.username, padded_len],
                    where=Builtins.Len(users.username) > 0,
                )
                # SELECT "users"."username",
                #        ((((LENGTH("users"."username") + %s) / %s) * %s))
                # FROM "users"
                # WHERE (LENGTH("users"."username") > %s)

            Nested inside other builtins — the average username length::

                avg_len = Builtins.Avg(Builtins.Len(users.username))
                rows = users.get_row([avg_len])
                # SELECT (AVG((LENGTH("users"."username")))) FROM "users"

            Applied to a raw literal — the literal is bound as a parameter::

                rows = users.get_row([
                    Builtins.Len('hello'),   # -> (LENGTH(%s)) with param 'hello'
                    Builtins.Len(42),        # -> (LENGTH(%s)) with param 42
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(LENGTH({sql}))', p, int, c)

    @staticmethod
    def Sum(value):
        """Compute the sum of a set of values using PostgreSQL's ``SUM()`` aggregate.

        Generates an SQL ``SUM(<expr>)`` expression. When used in a SELECT
        list without a ``GROUP BY`` clause, it aggregates over all rows
        returned by the query. When used inside a ``WHERE`` clause it has no
        meaning on its own (PostgreSQL will reject it) — ``Sum`` is meant for
        the SELECT side of a query, not for filtering.

        The resulting ``current_datatype`` is propagated from the input:

        - ``int``   → ``int`` (PostgreSQL keeps integer sums as integers
          when the input is a smallint/integer/bigint)
        - ``float`` → ``float`` (numeric and real inputs propagate as float)
        - anything else → ``int`` (conservative default)

        This keeps arithmetic chains (``Builtins.Sum(col) + 1``) compiling
        to ``+`` and not ``||``.

        Args:
            value: The expression to sum. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder
                  (rarely useful as the sole argument of an aggregate, but
                  supported for completeness).

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(SUM(<expr>))`` and whose ``current_datatype`` matches the
            input's numeric type (or ``int`` if the input is not numeric).

        Example:
            Total salary across all employees::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "company")
                employees = db.employees

                total = Builtins.Sum(employees.salary)
                rows  = employees.get_row([total])
                # SELECT (SUM("employees"."salary")) FROM "employees"
                # -> [(1245000,)] for example

            Sum alongside other aggregates::

                rows = employees.get_row([
                    Builtins.Count('*'),
                    Builtins.Sum(employees.salary),
                    Builtins.Avg(employees.salary),
                ])
                # SELECT (COUNT(*)), (SUM("employees"."salary")),
                #        (AVG("employees"."salary"))
                # FROM "employees"

            Sum combined with arithmetic (stays numeric, not string)::

                with_bonus = Builtins.Sum(employees.salary) + 10000
                rows = employees.get_row([with_bonus])
                # SELECT ((SUM("employees"."salary")) + %s) FROM "employees"
                # Parameters: [10000]

            Sum of a computed expression::

                net_total = Builtins.Sum(employees.salary - employees.tax)
                rows = employees.get_row([net_total])
                # SELECT (SUM(("employees"."salary" - "employees"."tax")))
                # FROM "employees"

            Group totals — fetch each department's total in Python::

                rows = employees.get_row([
                    employees.department,
                    Builtins.Sum(employees.salary),
                ])
                # SELECT "employees"."department",
                #        (SUM("employees"."salary"))
                # FROM "employees"
                # -> [('Engineering', 320000), ('Sales', 180000), ...]
        """
        sql, p, dt, c = Builtins._normalize(value)
        result_dt = dt if dt in (int, float) else int
        return Builtins._make(f'(SUM({sql}))', p, result_dt, c)

    @staticmethod
    def Avg(value):
        """Compute the average of a set of values using PostgreSQL's ``AVG()`` aggregate.

        Generates an SQL ``AVG(<expr>)`` expression. PostgreSQL always returns
        the average as a ``NUMERIC`` (which psycopg delivers to Python as
        ``decimal.Decimal``), but this helper declares the result as
        ``float`` so downstream arithmetic chains stay in the floating-point
        domain — matching SQLite's behaviour and Python's ``statistics.mean``.

        Like :meth:`Sum`, this aggregate is meant for the SELECT side of a
        query. Using it in a ``WHERE`` clause will raise an SQL error unless
        the query contains a ``GROUP BY`` that makes the aggregate legal.

        Args:
            value: The expression to average. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(AVG(<expr>))`` and whose ``current_datatype`` is always
            ``float``.

        Example:
            Average salary across all employees::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "company")
                employees = db.employees

                rows = employees.get_row([Builtins.Avg(employees.salary)])
                # SELECT (AVG("employees"."salary")) FROM "employees"
                # -> [(78750.0,)] for example

            Average rounded to two decimals::

                rows = employees.get_row([
                    Builtins.Round(Builtins.Avg(employees.salary) * 100) / 100
                ])
                # SELECT ((ROUND(((AVG("employees"."salary")) * %s))) / %s)
                # FROM "employees"
                # Parameters: [100, 100]

            Average of a computed expression::

                rows = employees.get_row([
                    Builtins.Avg(employees.salary + employees.bonus)
                ])
                # SELECT (AVG(("employees"."salary" + "employees"."bonus")))
                # FROM "employees"

            Combined with other aggregates in a single row::

                rows = employees.get_row([
                    Builtins.Count('*'),
                    Builtins.Avg(employees.salary),
                    Builtins.Sum(employees.salary),
                ])
                # SELECT (COUNT(*)), (AVG("employees"."salary")),
                #        (SUM("employees"."salary"))
                # FROM "employees"

            Per-department averages — fetched as (department, avg) pairs::

                rows = employees.get_row([
                    employees.department,
                    Builtins.Avg(employees.salary),
                ])
                # -> [('Engineering', 82000.0), ('Sales', 64500.0), ...]
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(AVG({sql}))', p, float, c)

    @staticmethod
    def Min(value):
        """Compute the minimum of a set of values using PostgreSQL's ``MIN()`` aggregate.

        Generates an SQL ``MIN(<expr>)`` expression. When used with a single
        argument in the SELECT list, ``MIN`` acts as an aggregate over all
        rows of the group.

        The resulting ``current_datatype`` is propagated from the input,
        because PostgreSQL returns the same type as the operand:

        - ``int``   → ``int``
        - ``float`` → ``float``
        - ``str``   → ``str`` (lexicographic minimum)
        - ``bytes`` → ``bytes``

        This keeps downstream concatenation (``Builtins.Min(col) + '!'``)
        compiling to ``||`` when the input was text, and keeps arithmetic
        (``Builtins.Min(col) + 1``) numeric when the input was numeric.

        Args:
            value: The expression whose minimum is computed. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(MIN(<expr>))`` and whose ``current_datatype`` matches the
            input's datatype.

        Example:
            Lowest salary in the table::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "company")
                employees = db.employees

                rows = employees.get_row([Builtins.Min(employees.salary)])
                # SELECT (MIN("employees"."salary")) FROM "employees"
                # -> [(45000,)] for example

            Earliest signup date::

                users = db.users
                rows  = users.get_row([Builtins.Min(users.created_at)])
                # SELECT (MIN("users"."created_at")) FROM "users"
                # -> [('2021-03-04 08:15:42',)]

            Lexicographic minimum of a text column — note that
            ``current_datatype`` is ``str`` here, so chaining with ``+``
            produces ``||``::

                expr = Builtins.Min(employees.name) + ' (first alphabetically)'
                rows = employees.get_row([expr])
                # SELECT ((MIN("employees"."name")) || %s) FROM "employees"
                # Parameters: [' (first alphabetically)']

            Minimum of a computed expression::

                rows = employees.get_row([
                    Builtins.Min(employees.salary - employees.bonus)
                ])
                # SELECT (MIN(("employees"."salary" - "employees"."bonus")))
                # FROM "employees"

            Per-department minimums::

                rows = employees.get_row([
                    employees.department,
                    Builtins.Min(employees.salary),
                ])
                # -> [('Engineering', 45000), ('Sales', 42000), ...]
        """
        sql, p, dt, c = Builtins._normalize(value)
        return Builtins._make(f'(MIN({sql}))', p, dt, c)

    @staticmethod
    def Max(value):
        """Compute the maximum of a set of values using PostgreSQL's ``MAX()`` aggregate.

        Generates an SQL ``MAX(<expr>)`` expression. The mirror image of
        :meth:`Min`: same typing rules, same aggregate semantics, same
        single-argument signature.

        The resulting ``current_datatype`` is propagated from the input
        so that arithmetic and concatenation chains compile to the correct
        SQL operator.

        Args:
            value: The expression whose maximum is computed. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(MAX(<expr>))`` and whose ``current_datatype`` matches the
            input's datatype.

        Example:
            Highest salary in the table::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "company")
                employees = db.employees

                rows = employees.get_row([Builtins.Max(employees.salary)])
                # SELECT (MAX("employees"."salary")) FROM "employees"
                # -> [(180000,)] for example

            Latest signup date::

                users = db.users
                rows  = users.get_row([Builtins.Max(users.created_at)])
                # SELECT (MAX("users"."created_at")) FROM "users"
                # -> [('2024-11-22 17:04:11',)]

            The id of the top earner — combine a filter with ORDER BY and
            LIMIT, since SQLite-style ``MAX`` + other columns is not
            portable in PostgreSQL::

                top = employees.get_row(
                    [employees.id, employees.name, employees.salary],
                    order_by=employees.salary * -1,
                    limit=1,
                )
                # SELECT "employees"."id", "employees"."name",
                #        "employees"."salary"
                # FROM "employees"
                # ORDER BY ("employees"."salary" * %s)
                # LIMIT %s
                # Parameters: [-1, 1]

            Maximum of a computed expression::

                rows = employees.get_row([
                    Builtins.Max(employees.salary + employees.bonus)
                ])
                # SELECT (MAX(("employees"."salary" + "employees"."bonus")))
                # FROM "employees"

            Lexicographic maximum of a text column — ``current_datatype``
            is ``str``, so ``add_end`` chains as ``||``::

                expr = Builtins.Max(employees.name).add_end(' wins')
                rows = employees.get_row([expr])
                # SELECT ((MAX("employees"."name")) || %s) FROM "employees"
                # Parameters: [' wins']

            Per-department maximums::

                rows = employees.get_row([
                    employees.department,
                    Builtins.Max(employees.salary),
                ])
                # -> [('Engineering', 180000), ('Sales', 95000), ...]
        """
        sql, p, dt, c = Builtins._normalize(value)
        return Builtins._make(f'(MAX({sql}))', p, dt, c)

    @staticmethod
    def Count(value):
        """Count rows using PostgreSQL's ``COUNT()`` aggregate.

        Two forms are supported, mirroring SQL's own behaviour:

        * **Row count** — when ``value`` is the literal string ``'*'``,
          generates ``COUNT(*)`` which counts every row in the group,
          regardless of NULL values.
        * **Non-null count** — for any other input, generates
          ``COUNT(<expr>)`` which counts only the rows where the expression
          evaluates to a non-NULL value.

        The result is always an ``INTEGER`` (PostgreSQL returns ``BIGINT``
        from ``COUNT``, and psycopg delivers it as a Python ``int``).

        Args:
            value: What to count. Supported types:

                - The string ``'*'`` — produces ``COUNT(*)`` and no
                  parameters.
                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused; produces ``COUNT(<fragment>)``.
                - :class:`Column` — the fully qualified column name is used;
                  produces ``COUNT("table"."column")``.
                - Any other raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is either
            ``(COUNT(*))`` or ``(COUNT(<expr>))`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Count all rows in the table::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "company")
                employees = db.employees

                rows = employees.get_row([Builtins.Count('*')])
                # SELECT (COUNT(*)) FROM "employees"
                # -> [(250,)] for example

            Count rows where a column is non-NULL::

                users = db.users
                rows  = users.get_row([Builtins.Count(users.email)])
                # SELECT (COUNT("users"."email")) FROM "users"
                # -> [(198,)] — 52 users have no email on file

            Count with a filter — combine with ``where=`` in the usual way::

                rows = users.get_row(
                    [Builtins.Count('*')],
                    where=users.is_active == True,
                )
                # SELECT (COUNT(*)) FROM "users"
                # WHERE ("users"."is_active" = %s)
                # Parameters: [True]

            Count of a computed expression::

                rows = users.get_row([
                    Builtins.Count(Builtins.Upper(users.username))
                ])
                # SELECT (COUNT((UPPER("users"."username")))) FROM "users"

            Boolean count via a ``CASE`` — count how many rows satisfy a
            condition inside a single aggregate. The ``IIf`` builder emits
            a ``CASE WHEN ... THEN ... ELSE ... END``::

                adults = Builtins.Sum(
                    Builtins.IIf(users.age >= 18, 1, 0)
                )
                rows = users.get_row([adults])
                # SELECT (SUM((CASE WHEN ("users"."age" >= %s)
                #                    THEN %s ELSE %s END)))
                # FROM "users"
                # Parameters: [18, 1, 0]
        """
        if isinstance(value, str) and value == '*':
            return Builtins._make('(COUNT(*))', [], int, Builtins._NullCol)
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(COUNT({sql}))', p, int, c)

    @staticmethod
    def Abs(value):
        """Compute the absolute value of a number using PostgreSQL's ``ABS()`` function.

        Generates an SQL ``ABS(<expr>)`` expression. The return type mirrors
        the input's numeric type:

        - ``int``   → ``int``
        - ``float`` → ``float``
        - anything else → ``float`` (conservative default, since ``ABS``
          only makes sense for numeric inputs)

        This keeps arithmetic chains (``Builtins.Abs(col) + 1``) compiling
        to ``+`` rather than ``||``.

        Args:
            value: The expression whose absolute value is computed.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value (``int``, ``float``) — bound as a
                  ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(ABS(<expr>))`` and whose ``current_datatype`` matches the
            input's numeric type (or ``float`` if the input is not numeric).

        Example:
            Magnitude of a signed delta::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "analytics")
                movements = db.movements

                rows = movements.get_row([
                    movements.account_id,
                    Builtins.Abs(movements.delta),
                ])
                # SELECT "movements"."account_id",
                #        (ABS("movements"."delta"))
                # FROM "movements"

            Filter by absolute value::

                rows = movements.get_row(
                    [movements.account_id, movements.delta],
                    where=Builtins.Abs(movements.delta) > 1000,
                )
                # SELECT "movements"."account_id", "movements"."delta"
                # FROM "movements"
                # WHERE ((ABS("movements"."delta")) > %s)
                # Parameters: [1000]

            Absolute value of a computed expression::

                balance_change = movements.credit - movements.debit
                rows = movements.get_row([Builtins.Abs(balance_change)])
                # SELECT (ABS(("movements"."credit" - "movements"."debit")))
                # FROM "movements"

            Chained with arithmetic — stays numeric::

                padded = Builtins.Abs(movements.delta) + 1
                # SELECT ((ABS("movements"."delta")) + %s) FROM "movements"
                # Parameters: [1]

            Applied to a raw literal::

                rows = movements.get_row([
                    Builtins.Abs(-42),     # -> (ABS(%s)) with param -42  ->  42
                    Builtins.Abs(-3.14),   # -> (ABS(%s)) with param -3.14 -> 3.14
                ])
        """
        sql, p, dt, c = Builtins._normalize(value)
        result_dt = dt if dt in (int, float) else float
        return Builtins._make(f'(ABS({sql}))', p, result_dt, c)

    @staticmethod
    def Round(value):
        """Round a number to the nearest integer using PostgreSQL's ``ROUND()`` function.

        Generates an SQL ``ROUND(<expr>)`` expression. When called with a
        single argument, PostgreSQL rounds to zero decimal places and
        returns a ``NUMERIC`` value. This helper declares the result as
        ``float`` so Python-side arithmetic remains in the floating-point
        domain, matching SQLite's behaviour and Python's built-in ``round``.

        To round to a specific number of decimal places, use
        :meth:`Builtins.Func` with a two-argument ``ROUND`` — for example
        ``Builtins.Func('ROUND', users.price)`` handles the one-argument
        form, and the two-argument form can be constructed with a small
        helper or by wrapping the operand in an expression.

        Args:
            value: The expression whose value is rounded. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value (``int``, ``float``) — bound as a
                  ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(ROUND(<expr>))`` and whose ``current_datatype`` is always
            ``float``.

        Example:
            Round a computed price to the nearest integer::

                from Ormophine.Postgresql import Driver, Builtins

                db       = Driver("localhost", 5432, "user", "pass", "store")
                products = db.products

                rows = products.get_row([
                    products.name,
                    Builtins.Round(products.price * 1.07),
                ])
                # SELECT "products"."name",
                #        (ROUND(("products"."price" * %s)))
                # FROM "products"
                # Parameters: [1.07]

            Round then cast to integer — the classic "nearest whole
            number" pattern::

                rows = products.get_row([
                    Builtins.Int(Builtins.Round(products.price)),
                ])
                # SELECT (CAST((ROUND("products"."price")) AS INTEGER))
                # FROM "products"

            Round an aggregate to two decimals — multiply, round, divide::

                rows = products.get_row([
                    Builtins.Round(Builtins.Avg(products.price) * 100) / 100,
                ])
                # SELECT ((ROUND(((AVG("products"."price")) * %s))) / %s)
                # FROM "products"
                # Parameters: [100, 100]

            Filter by rounded value::

                rows = products.get_row(
                    [products.name, products.price],
                    where=Builtins.Round(products.price) > 100,
                )
                # SELECT "products"."name", "products"."price"
                # FROM "products"
                # WHERE ((ROUND("products"."price")) > %s)
                # Parameters: [100]

            Chained with arithmetic — ``current_datatype`` is ``float``, so
            numeric operators stay numeric::

                padded = Builtins.Round(products.price) * 1.1
                # SELECT ((ROUND("products"."price")) * %s) FROM "products"
                # Parameters: [1.1]
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(ROUND({sql}))', p, float, c)

    @staticmethod
    def Sign(value):
        """Return the sign of a number using PostgreSQL's ``SIGN()`` function.

        Generates a SQL ``SIGN(<expr>)`` expression. PostgreSQL returns:

        - ``-1`` if the value is negative
        - `` 0`` if the value is zero
        - `` 1`` if the value is positive

        This mirrors Python's ``(x > 0) - (x < 0)`` idiom and is useful for
        bucketing, sorting by direction, or normalising deltas.

        The result is always an ``INTEGER``, so it plays nicely with
        arithmetic, comparisons, and other numeric builtins.

        Args:
            value: The expression whose sign is computed. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value (``int``, ``float``) — bound as a
                  ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(SIGN(<expr>))`` and whose ``current_datatype`` is ``int``.

        Example:
            Bucket movements by direction — up, flat, or down::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "analytics")
                movements = db.movements

                rows = movements.get_row([
                    movements.account_id,
                    movements.delta,
                    Builtins.Sign(movements.delta),
                ])
                # SELECT "movements"."account_id", "movements"."delta",
                #        (SIGN("movements"."delta"))
                # FROM "movements"
                # -> [(1, 250.0, 1), (1, -80.0, -1), (1, 0.0, 0), ...]

            Filter only the positive movements::

                rows = movements.get_row(
                    [movements.account_id, movements.delta],
                    where=Builtins.Sign(movements.delta) == 1,
                )
                # SELECT "movements"."account_id", "movements"."delta"
                # FROM "movements"
                # WHERE ((SIGN("movements"."delta")) = %s)
                # Parameters: [1]

            Sum signs to count net direction — positive means more ups
            than downs, and vice versa::

                net = Builtins.Sum(Builtins.Sign(movements.delta))
                rows = movements.get_row([movements.account_id, net])
                # SELECT "movements"."account_id",
                #        (SUM((SIGN("movements"."delta"))))
                # FROM "movements"

            Sign of a computed expression::

                range_mid = (movements.high + movements.low) / 2
                rows = movements.get_row([
                    movements.symbol,
                    Builtins.Sign(movements.close - range_mid),
                ])
                # SELECT "movements"."symbol",
                #        (SIGN(("movements"."close"
                #               - (("movements"."high" + "movements"."low") / %s)))))
                # FROM "movements"
                # Parameters: [2]

            Applied to a raw literal::

                rows = movements.get_row([
                    Builtins.Sign(-5),    # -> (SIGN(%s)) with param -5  -> -1
                    Builtins.Sign(0),     # -> (SIGN(%s)) with param 0   ->  0
                    Builtins.Sign(3.14),  # -> (SIGN(%s)) with param 3.14 -> 1
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(SIGN({sql}))', p, int, c)

    @staticmethod
    def Floor(value):
        """Round a number down to the nearest integer using PostgreSQL's ``FLOOR()``.

        Generates a ``FLOOR(<expr>)`` expression. For positive numbers this
        behaves like truncation toward zero; for negative numbers it rounds
        away from zero (``FLOOR(-1.5) = -2``, unlike ``CAST(-1.5 AS
        INTEGER)`` which gives ``-1``).

        The result is always an ``INTEGER`` when the input is a numeric type
        PostgreSQL can safely cast — but be aware that ``FLOOR`` of a
        ``NUMERIC`` value with a large magnitude returns a ``NUMERIC``
        that psycopg may deliver as ``decimal.Decimal``. The
        ``current_datatype`` is set to ``int`` because the value is always
        whole.

        Args:
            value: The expression to floor. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value (``int``, ``float``) — bound as a
                  ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(FLOOR(<expr>))`` and whose ``current_datatype`` is ``int``.

        Example:
            Snap a price down to the whole dollar::

                from Ormophine.Postgresql import Driver, Builtins

                db       = Driver("localhost", 5432, "user", "pass", "store")
                products = db.products

                rows = products.get_row([
                    products.name,
                    products.price,
                    Builtins.Floor(products.price),
                ])
                # SELECT "products"."name", "products"."price",
                #        (FLOOR("products"."price"))
                # FROM "products"
                # -> [('Widget', 19.99, 19), ('Gadget', 24.50, 24), ...]

            Bucket scores into deciles::

                decile = Builtins.Floor(products.score / 10) * 10
                rows = products.get_row([decile, Builtins.Count('*')])
                # SELECT ((FLOOR(("products"."score" / %s))) * %s),
                #        (COUNT(*))
                # FROM "products"
                # Parameters: [10, 10]
                # -> [(0, 12), (10, 45), (20, 38), ...]

            Handle negative values correctly — FLOOR rounds down, not
            toward zero::

                rows = products.get_row([
                    Builtins.Floor(-1.5),   # -> -2
                    Builtins.Int(-1.5),     # -> -1 (truncation, not floor)
                ])

            Filter by floored value::

                rows = products.get_row(
                    [products.name, products.price],
                    where=Builtins.Floor(products.price) >= 20,
                )
                # SELECT "products"."name", "products"."price"
                # FROM "products"
                # WHERE ((FLOOR("products"."price")) >= %s)
                # Parameters: [20]

            Combined with arithmetic — stays numeric::

                discount = Builtins.Floor(products.price * 0.8)
                rows = products.get_row([products.name, discount])
                # SELECT "products"."name",
                #        (FLOOR(("products"."price" * %s)))
                # FROM "products"
                # Parameters: [0.8]
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(FLOOR({sql}))', p, int, c)

    @staticmethod
    def Ceil(value):
        """Round a number up to the nearest integer using PostgreSQL's ``CEIL()``.

        Generates a ``CEIL(<expr>)`` expression. For positive numbers this
        rounds away from zero; for negative numbers it rounds toward zero
        (``CEIL(-1.5) = -1``, unlike ``FLOOR(-1.5) = -2``). This is the
        mirror image of :meth:`Floor`.

        The result is always an ``INTEGER`` when the input is a numeric type
        PostgreSQL can safely cast — the same caveat about large ``NUMERIC``
        inputs applies as for :meth:`Floor`. The ``current_datatype`` is
        declared as ``int`` because the value is always whole.

        Args:
            value: The expression to ceil. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value (``int``, ``float``) — bound as a
                  ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CEIL(<expr>))`` and whose ``current_datatype`` is ``int``.

        Example:
            Snap a price up to the whole dollar — the common "don't lose
            money on rounding" pattern::

                from Ormophine.Postgresql import Driver, Builtins

                db       = Driver("localhost", 5432, "user", "pass", "store")
                products = db.products

                rows = products.get_row([
                    products.name,
                    products.price,
                    Builtins.Ceil(products.price),
                ])
                # SELECT "products"."name", "products"."price",
                #        (CEIL("products"."price"))
                # FROM "products"
                # -> [('Widget', 19.01, 20), ('Gadget', 24.00, 24), ...]

            Pagination — compute how many pages of 20 items each::

                page_count = Builtins.Ceil(Builtins.Count('*') / 20)
                rows = products.get_row([page_count])
                # SELECT (CEIL(((COUNT(*)) / %s))) FROM "products"
                # Parameters: [20]
                # -> [(13,)] if there are 250 products

            Bucket scores into the next higher 10::

                next_ten = Builtins.Ceil(products.score / 10) * 10
                rows = products.get_row([products.id, next_ten])
                # SELECT "products"."id",
                #        ((CEIL(("products"."score" / %s))) * %s)
                # FROM "products"
                # Parameters: [10, 10]

            Handle negative values correctly::

                rows = products.get_row([
                    Builtins.Ceil(-1.5),   # -> -1
                    Builtins.Int(-1.5),    # -> -1 (same here)
                ])

            Build a "bracketed price" pair from Floor and Ceil::

                low  = Builtins.Floor(products.price)
                high = Builtins.Ceil(products.price)
                rows = products.get_row([products.price, low, high])
                # SELECT "products"."price",
                #        (FLOOR("products"."price")),
                #        (CEIL("products"."price"))
                # FROM "products"
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CEIL({sql}))', p, int, c)

    @staticmethod
    def Sqrt(value):
        """Compute the square root of a number using PostgreSQL's ``SQRT()`` function.

        Generates a ``SQRT(<expr>)`` expression. The result is always a
        ``DOUBLE PRECISION`` (float) — even ``SQRT(4)`` returns ``2.0``,
        not ``2``. This is consistent with PostgreSQL's decision to keep
        math functions in the floating-point domain.

        Negative inputs raise a database error in PostgreSQL (unlike SQLite
        which silently returns ``NULL``). If your input might be negative,
        guard it — either by clamping with ``GREATEST(x, 0)`` via
        :meth:`Builtins.Func`, or by filtering rows with a plain comparison
        before the expression is evaluated.

        Args:
            value: The expression whose square root is computed. Supported
                types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value (``int``, ``float``) — bound as a
                  ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(SQRT(<expr>))`` and whose ``current_datatype`` is always
            ``float``.

        Example:
            Euclidean distance — the standard ``sqrt(a² + b²)`` pattern::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "geometry")
                points = db.points

                distance_from_origin = Builtins.Sqrt(
                    points.x * points.x + points.y * points.y
                )
                rows = points.get_row([
                    points.id,
                    distance_from_origin,
                ])
                # SELECT "points"."id",
                #        (SQRT((("points"."x" * "points"."x")
                #               + ("points"."y" * "points"."y"))))
                # FROM "points"

            Standard deviation of a small set — square root of the mean
            squared deviation::

                mean   = Builtins.Avg(points.x)
                sq_dev = Builtins.Avg((points.x - mean) * (points.x - mean))
                stddev = Builtins.Sqrt(sq_dev)
                rows = points.get_row([stddev])
                # SELECT (SQRT((AVG((("points"."x" - (AVG("points"."x")))
                #                     * ("points"."x" - (AVG("points"."x"))))))))
                # FROM "points"

            Filter by magnitude — points more than 10 units from the origin::

                rows = points.get_row(
                    [points.id, points.x, points.y],
                    where=Builtins.Sqrt(
                        points.x * points.x + points.y * points.y
                    ) > 10,
                )
                # SELECT "points"."id", "points"."x", "points"."y"
                # FROM "points"
                # WHERE ((SQRT((("points"."x" * "points"."x")
                #               + ("points"."y" * "points"."y")))) > %s)
                # Parameters: [10]

            Combine with Round for display::

                pretty = Builtins.Round(
                    Builtins.Sqrt(points.x * points.x + points.y * points.y) * 100
                ) / 100
                rows = points.get_row([points.id, pretty])
                # SELECT "points"."id",
                #        ((ROUND((SQRT((("points"."x" * "points"."x")
                #                       + ("points"."y" * "points"."y"))))
                #                * %s)) / %s)
                # FROM "points"
                # Parameters: [100, 100]

            Applied to a raw literal::

                rows = points.get_row([
                    Builtins.Sqrt(16),    # -> (SQRT(%s)) with param 16 -> 4.0
                    Builtins.Sqrt(2),     # -> 1.4142135623730951
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(SQRT({sql}))', p, float, c)

    @staticmethod
    def Pow(base, exp):
        """Raise a base value to a power using PostgreSQL's ``POWER(a, b)`` function.

        Generates a ``POWER(<base>, <exp>)`` expression. This is the SQL
        analogue of Python's built-in ``pow(base, exp)`` and the ``**``
        operator. Both operands may be any expression — a column, another
        builtin, a raw literal, or a nested :class:`ColumnsOperation`.

        The result is always a ``DOUBLE PRECISION`` (float) — even
        ``POWER(2, 3)`` returns ``8.0``, not ``8``, because PostgreSQL
        keeps ``POWER`` in the floating-point domain.

        Args:
            base: The base expression. Any of the standard operand types:

                - :class:`ColumnsOperation`
                - :class:`Column`
                - A raw Python number

            exp: The exponent expression. Same accepted types as ``base``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(POWER(<base>, <exp>))`` and whose ``current_datatype`` is
            always ``float``. Parameters are concatenated left-to-right:
            first ``base``'s parameters, then ``exp``'s.

        Example:
            Compound interest — principal times ``(1 + rate)^years``::

                from Ormophine.Postgresql import Driver, Builtins

                db       = Driver("localhost", 5432, "user", "pass", "bank")
                accounts = db.accounts

                future_value = accounts.principal * Builtins.Pow(
                    1 + accounts.rate,
                    accounts.years,
                )
                rows = accounts.get_row([
                    accounts.id,
                    future_value,
                ])
                # SELECT "accounts"."id",
                #        ("accounts"."principal"
                #         * (POWER((%s + "accounts"."rate"),
                #                  "accounts"."years")))
                # FROM "accounts"
                # Parameters: [1]

            Square or cube a value::

                rows = accounts.get_row([
                    Builtins.Pow(accounts.principal, 2),   # square
                    Builtins.Pow(accounts.principal, 3),   # cube
                ])
                # SELECT (POWER("accounts"."principal", %s)),
                #        (POWER("accounts"."principal", %s))
                # FROM "accounts"
                # Parameters: [2, 3]

            Square root via fractional exponent — equivalent to
            :meth:`Sqrt`::

                rows = accounts.get_row([
                    Builtins.Pow(accounts.principal, 0.5),
                ])
                # SELECT (POWER("accounts"."principal", %s))
                # FROM "accounts"
                # Parameters: [0.5]

            Inverse power — ``x⁻¹`` equals ``1/x``::

                rows = accounts.get_row([
                    Builtins.Pow(accounts.principal, -1),
                ])
                # SELECT (POWER("accounts"."principal", %s))
                # FROM "accounts"
                # Parameters: [-1]

            Distance squared without the square root — useful for
            comparisons where you don't need the exact distance::

                rows = accounts.get_row(
                    [accounts.id],
                    where=Builtins.Pow(accounts.x, 2)
                        + Builtins.Pow(accounts.y, 2) > 100,
                )
                # SELECT "accounts"."id" FROM "accounts"
                # WHERE (((POWER("accounts"."x", %s))
                #          + (POWER("accounts"."y", %s))) > %s)
                # Parameters: [2, 2, 100]

            Combine with Round for display::

                pretty = Builtins.Round(
                    Builtins.Pow(accounts.principal, 1.05) * 100
                ) / 100
                rows = accounts.get_row([accounts.id, pretty])
                # SELECT "accounts"."id",
                #        ((ROUND((POWER("accounts"."principal", %s) * %s))) / %s)
                # FROM "accounts"
                # Parameters: [1.05, 100, 100]

            Chained with arithmetic — stays numeric::

                doubled = Builtins.Pow(accounts.principal, 2) * 2
                # SELECT ((POWER("accounts"."principal", %s)) * %s)
                # FROM "accounts"
                # Parameters: [2, 2]
        """
        s1, p1, _, c = Builtins._normalize(base)
        s2, p2, _, _ = Builtins._normalize(exp)
        return Builtins._make(f'(POWER({s1}, {s2}))', p1 + p2, float, c)

    @staticmethod
    def Total(value):
        """Sum a set of values, returning zero instead of NULL for empty groups.

        Generates a ``COALESCE(SUM(<expr>), 0)`` expression. This is the
        PostgreSQL equivalent of SQLite's ``TOTAL()``: it behaves like
        :meth:`Sum` with one critical difference — **empty groups return
        ``0`` instead of ``NULL``**. This makes it the safer choice when
        you need a numeric result even when no rows match, or when the
        aggregated column contains only NULLs:

        .. code-block:: sql

            SUM  of (NULL, NULL) -> NULL
            COALESCE(SUM(...), 0) of (NULL, NULL) -> 0

            SUM  of ()          -> NULL
            COALESCE(SUM(...), 0) of ()          -> 0

        The result is declared as ``float`` so downstream arithmetic stays
        in the numeric domain and Python never has to distinguish between
        ``None`` and ``0`` after the fetch.

        Args:
            value: The expression to sum. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(COALESCE(SUM(<expr>), 0))`` and whose ``current_datatype``
            is always ``float``.

        Example:
            Sum with a guaranteed numeric result — no ``None`` to guard
            against in Python::

                from Ormophine.Postgresql import Driver, Builtins

                db       = Driver("localhost", 5432, "user", "pass", "store")
                products = db.products

                total = Builtins.Total(products.price)
                rows = products.get_row([total])
                # SELECT (COALESCE(SUM("products"."price"), 0))
                # FROM "products"
                # -> [(1234.56,)] even if the table is empty

            Compare with SUM to see the difference::

                rows = products.get_row([
                    Builtins.Sum(products.price),     # -> None when empty
                    Builtins.Total(products.price),   # -> 0.0 when empty
                ])
                # SELECT (SUM("products"."price")),
                #        (COALESCE(SUM("products"."price"), 0))
                # FROM "products"
                # -> [(None, 0.0)] on an empty table

            Sum a nullable column — the SUM ignores NULLs, and if every
            row is NULL, the COALESCE returns 0::

                rows = products.get_row([
                    Builtins.Total(products.discount),   # NULLs -> 0
                ])
                # SELECT (COALESCE(SUM("products"."discount"), 0))
                # FROM "products"

            Safety-check division — Total never produces NULL, so the
            denominator in a ratio is always numeric. Note that SQL still
            raises on division by zero, so guard against that separately::

                ratio = (Builtins.Total(products.revenue)
                         / Builtins.Func('NULLIF', Builtins.Total(products.cost)))
                rows = products.get_row([ratio])
                # SELECT ((COALESCE(SUM("products"."revenue"), 0))
                #         / (NULLIF((COALESCE(SUM("products"."cost"), 0)), %s)))
                # FROM "products"
                # Parameters: [0]

            Conditional aggregate — sum only the paid products::

                paid = Builtins.Total(
                    Builtins.IIf(products.paid == True,
                                 products.price,
                                 0)
                )
                rows = products.get_row([paid])
                # SELECT (COALESCE(SUM((CASE WHEN ("products"."paid" = %s)
                #                              THEN "products"."price"
                #                              ELSE %s END)), 0))
                # FROM "products"
                # Parameters: [True, 0]

            Chained arithmetic — stays numeric::

                padded = Builtins.Total(products.price) + 100
                # SELECT ((COALESCE(SUM("products"."price"), 0)) + %s)
                # FROM "products"
                # Parameters: [100]
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(COALESCE(SUM({sql}), 0))', p, float, c)

    @staticmethod
    def GroupConcat(value, sep=','):
        """Concatenate values from multiple rows into a single string.

        Generates a ``STRING_AGG(<expr>, <sep>)`` expression. This is the
        SQL analogue of Python's ``','.join(...)`` applied to a column:
        every non-NULL value in the group is converted to text, joined
        with the separator, and returned as a single TEXT value.

        The separator is bound as a ``%s`` parameter, so it is safe to
        pass user-supplied strings. Values are aggregated in an
        unspecified order by PostgreSQL unless you add an explicit
        ``ORDER BY`` to the ``STRING_AGG`` call, which this helper does
        not do — if deterministic ordering matters, use :meth:`Func` with
        the three-argument form: ``Func('STRING_AGG', col)`` and extend
        the SQL yourself.

        The result is always a ``TEXT`` string, so ``+`` chained after it
        produces concatenation (``||``) rather than arithmetic addition.

        Args:
            value: The expression whose values are concatenated. Supported
                types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

            sep (str, optional): The separator string inserted between
                values. Bound as a parameter. Defaults to ``','``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(STRING_AGG(<expr>, %s))`` and whose ``current_datatype`` is
            always ``str``. Parameters are ``value's_parameters + [sep]``.

        Example:
            Comma-separated list of all usernames::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([Builtins.GroupConcat(users.username)])
                # SELECT (STRING_AGG("users"."username", %s)) FROM "users"
                # Parameters: [',']
                # -> [('alice,bob,carol,dave',)]

            Custom separator — a pipe::

                rows = users.get_row([
                    Builtins.GroupConcat(users.username, ' | ')
                ])
                # SELECT (STRING_AGG("users"."username", %s)) FROM "users"
                # Parameters: [' | ']
                # -> [('alice | bob | carol',)]

            Emails for a specific domain — combine with a filter::

                rows = users.get_row(
                    [Builtins.GroupConcat(users.email, '; ')],
                    where=users.email.endswith('@example.com'),
                )
                # SELECT (STRING_AGG("users"."email", %s)) FROM "users"
                # WHERE ("users"."email" LIKE '%%' || %s)
                # Parameters: ['; ', '@example.com']

            Build a display string per group — combine with a grouped
            query by fetching the raw rows and doing the grouping in
            Python, since the ORM has no GROUP BY helper::

                rows = users.get_row([
                    users.department,
                    users.username,
                ])
                from collections import defaultdict
                grouped = defaultdict(list)
                for dept, username in rows:
                    grouped[dept].append(username)
                # {'Engineering': ['alice', 'bob'], 'Sales': ['carol'], ...}
                # then: ', '.join(names) in Python

            Concatenate a transformed column — upper-case usernames joined
            by commas::

                rows = users.get_row([
                    Builtins.GroupConcat(Builtins.Upper(users.username)),
                ])
                # SELECT (STRING_AGG((UPPER("users"."username")), %s))
                # FROM "users"
                # Parameters: [',']
                # -> [('ALICE,BOB,CAROL',)]

            Chained with string methods — the result is TEXT::

                expr = Builtins.GroupConcat(users.username)
                rows = users.get_row([expr.add_first('Users: ')])
                # SELECT (%s || (STRING_AGG("users"."username", %s)))
                # FROM "users"
                # Parameters: ['Users: ', ',']
                # -> [('Users: alice,bob,carol',)]
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(STRING_AGG({sql}, %s))', p + [sep], str, c)

    @staticmethod
    def Int(value):
        """Convert a value to an integer using ``CAST(x AS INTEGER)``.

        This is the SQL equivalent of Python's ``int()``. Generates a
        ``CAST(<expr> AS INTEGER)`` expression. PostgreSQL's conversion
        rules apply: text that looks like an integer is parsed, REAL is
        truncated toward zero (not rounded), and NULL stays NULL. If the
        input cannot be coerced, PostgreSQL raises an error rather than
        returning NULL.

        The result is always an ``INTEGER``, so arithmetic chains and
        comparisons behave as expected even when the original column was
        declared as TEXT or a floating-point type.

        Args:
            value: The expression to convert. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS INTEGER))`` and whose ``current_datatype`` is
            always ``int``.

        Example:
            Force a text column to be treated as an integer::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "app")
                events = db.events

                rows = events.get_row([
                    events.id,
                    Builtins.Int(events.payload),
                ])
                # SELECT "events"."id", (CAST("events"."payload" AS INTEGER))
                # FROM "events"

            Compare a text column numerically::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Int(events.amount_text) > 1000,
                )
                # SELECT "events"."id" FROM "events"
                # WHERE ((CAST("events"."amount_text" AS INTEGER)) > %s)
                # Parameters: [1000]

            Truncate a REAL toward zero — note this is *not* rounding::

                rows = events.get_row([
                    Builtins.Int(events.temperature),   # 23.7 -> 23
                    Builtins.Round(events.temperature), # 23.7 -> 24.0
                ])
                # SELECT (CAST("events"."temperature" AS INTEGER)),
                #        (ROUND("events"."temperature"))
                # FROM "events"

            Combine with arithmetic — stays numeric::

                doubled = Builtins.Int(events.count) * 2
                # SELECT ((CAST("events"."count" AS INTEGER)) * %s)
                # FROM "events"
                # Parameters: [2]

            Used inside an aggregate::

                rows = events.get_row([
                    Builtins.Sum(Builtins.Int(events.payload)),
                ])
                # SELECT (SUM((CAST("events"."payload" AS INTEGER))))
                # FROM "events"
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CAST({sql} AS INTEGER))', p, int, c)

    @staticmethod
    def Float(value):
        """Convert a value to a floating-point number using ``CAST(x AS DOUBLE PRECISION)``.

        This is the SQL equivalent of Python's ``float()``. Generates a
        ``CAST(<expr> AS DOUBLE PRECISION)`` expression. PostgreSQL's
        conversion rules apply: text that looks like a number is parsed,
        INTEGER is widened to DOUBLE PRECISION, and NULL stays NULL.
        Unlike ``CAST AS INTEGER``, this target type handles both integer
        and fractional inputs without truncation.

        The result is always a ``DOUBLE PRECISION`` (Python ``float``), so
        any chained arithmetic naturally stays in the floating-point domain
        — which is important because PostgreSQL's integer division returns
        an integer (``5 / 2 = 2``), while floating-point division returns
        the expected fractional result (``5.0 / 2 = 2.5``).

        Args:
            value: The expression to convert. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS DOUBLE PRECISION))`` and whose
            ``current_datatype`` is always ``float``.

        Example:
            Force integer division to produce a real result::

                from Ormophine.Postgresql import Driver, Builtins

                db         = Driver("localhost", 5432, "user", "pass", "analytics")
                statistics = db.statistics

                ratio = (Builtins.Float(statistics.successes)
                         / statistics.attempts)
                rows = statistics.get_row([ratio])
                # SELECT ((CAST("statistics"."successes" AS DOUBLE PRECISION))
                #         / "statistics"."attempts")
                # FROM "statistics"
                # -> [(0.9732,)]

            Without the cast, integer division silently truncates::

                wrong = statistics.successes / statistics.attempts
                rows = statistics.get_row([wrong])
                # SELECT ("statistics"."successes" / "statistics"."attempts")
                # FROM "statistics"
                # -> [(0,)] for 9732 successes out of 10000 attempts

            Parse a text column as a number and filter on it::

                rows = statistics.get_row(
                    [statistics.id,
                     Builtins.Float(statistics.amount_text)],
                    where=Builtins.Float(statistics.amount_text) >= 99.5,
                )
                # SELECT "statistics"."id",
                #        (CAST("statistics"."amount_text" AS DOUBLE PRECISION))
                # FROM "statistics"
                # WHERE ((CAST("statistics"."amount_text" AS DOUBLE PRECISION)) >= %s)
                # Parameters: [99.5]

            Widen an integer column before averaging — this is what
            :meth:`Avg` already does internally::

                rows = statistics.get_row([
                    Builtins.Avg(Builtins.Float(statistics.count)),
                ])
                # SELECT (AVG((CAST("statistics"."count" AS DOUBLE PRECISION))))
                # FROM "statistics"

            Multiply a converted value by a literal::

                scaled = Builtins.Float(statistics.score) * 1.5
                rows = statistics.get_row([scaled])
                # SELECT ((CAST("statistics"."score" AS DOUBLE PRECISION)) * %s)
                # FROM "statistics"
                # Parameters: [1.5]

            Applied to a raw literal::

                rows = statistics.get_row([
                    Builtins.Float('3.14'),   # -> (CAST(%s AS DOUBLE PRECISION))
                    Builtins.Float(42),       # -> 42.0
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CAST({sql} AS DOUBLE PRECISION))', p, float, c)

    @staticmethod
    def Str(value):
        """Convert a value to text using ``CAST(x AS TEXT)``.

        This is the SQL equivalent of Python's ``str()``. Generates a
        ``CAST(<expr> AS TEXT)`` expression. PostgreSQL's conversion rules
        apply: numbers are formatted as their decimal representation,
        booleans become ``'t'`` / ``'f'``, dates and timestamps use the
        configured ``DateStyle`` (usually ISO 8601), and NULL stays NULL.

        The result is always a ``TEXT`` (Python ``str``), so chaining with
        ``+`` produces string concatenation (``||``) rather than arithmetic
        addition. This is essential when you need to mix numeric columns
        into text — otherwise PostgreSQL will refuse the operation, since
        it has no implicit int-to-text coercion.

        Args:
            value: The expression to convert. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS TEXT))`` and whose ``current_datatype`` is
            always ``str``.

        Example:
            Safely concatenate a numeric column with text. Without the
            cast, PostgreSQL raises ``operator does not exist: integer
            || unknown``::

                from Ormophine.Postgresql import Driver, Builtins

                db        = Driver("localhost", 5432, "user", "pass", "company")
                employees = db.employees

                label = Builtins.Str(employees.salary) + ' USD'
                rows = employees.get_row([employees.name, label])
                # SELECT "employees"."name",
                #        ((CAST("employees"."salary" AS TEXT)) || %s)
                # FROM "employees"
                # Parameters: [' USD']
                # -> [('Alice', '75000 USD'), ...]

            Without Builtins.Str — this will error at execution time
            because PostgreSQL refuses to concatenate integer and text::

                wrong = employees.salary + ' USD'
                rows = employees.get_row([employees.name, wrong])
                # SELECT "employees"."name",
                #        ("employees"."salary" || %s)
                # FROM "employees"
                # ERROR: operator does not exist: integer || text

            Format an integer id for display::

                rows = employees.get_row([
                    Builtins.Str(employees.id).add_first('EMP-'),
                ])
                # SELECT (%s || (CAST("employees"."id" AS TEXT)))
                # FROM "employees"
                # Parameters: ['EMP-']
                # -> [('EMP-42',), ...]

            Use with string methods — ``add_end`` chains as ``||``::

                rows = employees.get_row([
                    Builtins.Str(employees.salary).add_end(' gross'),
                ])
                # SELECT ((CAST("employees"."salary" AS TEXT)) || %s)
                # FROM "employees"
                # Parameters: [' gross']

            Inside a LIKE pattern — match the leading digit of an integer
            id::

                rows = employees.get_row(
                    [employees.id],
                    where=Builtins.Str(employees.id).startswith('1'),
                )
                # SELECT "employees"."id" FROM "employees"
                # WHERE ((CAST("employees"."id" AS TEXT)) LIKE %s || '%%')
                # Parameters: ['1']

            Applied to a raw literal::

                rows = employees.get_row([
                    Builtins.Str(3.14),    # -> (CAST(%s AS TEXT)) -> '3.14'
                    Builtins.Str(True),    # -> (CAST(%s AS TEXT)) -> 'true'
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CAST({sql} AS TEXT))', p, str, c)

    @staticmethod
    def Bool(value):
        """Convert a value to a boolean using ``CAST(x AS BOOLEAN)``.

        PostgreSQL has a native ``BOOLEAN`` type, unlike SQLite. This
        helper mirrors Python's ``bool()`` by emitting a
        ``CAST(<expr> AS BOOLEAN)`` expression. PostgreSQL's conversion
        rules apply: text values ``'t'``, ``'true'``, ``'yes'``, ``'on'``,
        ``'1'`` become TRUE; ``'f'``, ``'false'``, ``'no'``, ``'off'``,
        ``'0'`` become FALSE; integers ``0`` become FALSE and any non-zero
        integer becomes TRUE.

        The result is a real Python ``bool`` (``True`` / ``False``), so it
        can be compared directly to Python booleans in the ORM's filter
        expressions.

        Args:
            value: The expression to convert. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value (``True``, ``False``, ``0``, ``1``, or
                  any coercible value) — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS BOOLEAN))`` and whose ``current_datatype``
            is ``bool``.

        Example:
            Normalise a status column that stores mixed values::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                normalised = Builtins.Bool(users.is_active)
                rows = users.get_row([users.id, normalised])
                # SELECT "users"."id",
                #        (CAST("users"."is_active" AS BOOLEAN))
                # FROM "users"
                # -> [(1, True), (2, False), ...]

            Compare a computed condition against a boolean literal::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.Bool(users.age >= 18) == True,
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((CAST(("users"."age" >= %s) AS BOOLEAN)) = %s)
                # Parameters: [18, True]

            Coerce a raw literal — useful when the surrounding API expects
            a ColumnsOperation-shaped object::

                rows = users.get_row([
                    Builtins.Bool(True),    # -> (CAST(%s AS BOOLEAN))
                    Builtins.Bool(False),   # -> (CAST(%s AS BOOLEAN))
                ])

            Use in a CASE expression — a boolean column drives the branch::

                status = Builtins.IIf(
                    Builtins.Bool(users.is_active),
                    'active',
                    'inactive',
                )
                rows = users.get_row([users.name, status])
                # SELECT "users"."name",
                #        (CASE WHEN (CAST("users"."is_active" AS BOOLEAN))
                #              THEN %s ELSE %s END)
                # FROM "users"
                # Parameters: ['active', 'inactive']

            Count active rows via SUM of the boolean cast — PostgreSQL
            promotes boolean to integer in arithmetic::

                active_count = Builtins.Sum(
                    Builtins.IIf(Builtins.Bool(users.is_active), 1, 0)
                )
                rows = users.get_row([active_count])
                # SELECT (SUM((CASE WHEN
                #               (CAST("users"."is_active" AS BOOLEAN))
                #               THEN %s ELSE %s END)))
                # FROM "users"
                # Parameters: [1, 0]
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CAST({sql} AS BOOLEAN))', p, bool, c)

    @staticmethod
    def TypeOf(value):
        """Return the PostgreSQL internal type name of a value using ``PG_TYPEOF()``.

        Generates a ``PG_TYPEOF(<expr>)`` expression. PostgreSQL returns
        the internal type name as a ``TEXT`` string — for example ``'int4'``
        for a regular ``integer``, ``'int8'`` for ``bigint``, ``'float8'``
        for ``double precision``, ``'text'`` for ``text``, ``'varchar'``
        for ``character varying``, ``'bool'`` for ``boolean``, ``'numeric'``
        for ``numeric``, ``'timestamptz'`` for ``timestamp with time zone``,
        ``'date'`` for ``date``, ``'jsonb'`` for ``jsonb``, ``'uuid'`` for
        ``uuid``.

        This is the closest SQL analogue of Python's ``type()`` for
        debugging and introspection. Because PostgreSQL is a strongly typed
        system, the result is *much* more useful than SQLite's ``TYPEOF`` —
        SQLite only returns the five storage classes, whereas PostgreSQL
        returns the actual declared type name.

        The result is always a ``TEXT`` string, so it plays nicely with
        ``.like``, ``.startswith``, ``.In``, and other text-oriented chains.

        Args:
            value: The expression whose storage class is inspected.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(PG_TYPEOF(<expr>))`` and whose ``current_datatype`` is always
            ``str``.

        Example:
            Audit a column that stores mixed types::

                from Ormophine.Postgresql import Driver, Builtins

                db      = Driver("localhost", 5432, "user", "pass", "app")
                records = db.records

                rows = records.get_row([
                    records.id,
                    Builtins.TypeOf(records.payload),
                ])
                # SELECT "records"."id", (PG_TYPEOF("records"."payload"))
                # FROM "records"
                # -> [(1, 'text'), (2, 'int4'), (3, 'jsonb'), ...]

            Filter only the rows that stored real numbers::

                rows = records.get_row(
                    [records.id, records.payload],
                    where=Builtins.TypeOf(records.payload) == 'float8',
                )
                # SELECT "records"."id", "records"."payload"
                # FROM "records"
                # WHERE ((PG_TYPEOF("records"."payload")) = %s)
                # Parameters: ['float8']

            Group records by storage class — count in Python::

                rows = records.get_row([Builtins.TypeOf(records.payload)])
                from collections import Counter
                histogram = Counter(t for (t,) in rows)
                # {'text': 120, 'int4': 45, 'float8': 18, 'jsonb': 3}

            Combine with a text-method chain::

                rows = records.get_row(
                    [records.id],
                    where=Builtins.TypeOf(records.payload).In(
                        ['int4', 'int8', 'float8']
                    ),
                )
                # SELECT "records"."id" FROM "records"
                # WHERE ((PG_TYPEOF("records"."payload")) IN (%s, %s, %s))
                # Parameters: ['int4', 'int8', 'float8']

            Inspect a raw literal — note that literals are typed by
            PostgreSQL's own rules, so ``42`` is ``int4`` and ``3.14`` is
            ``numeric``, not ``float8``::

                rows = records.get_row([
                    Builtins.TypeOf(42),      # -> 'int4'
                    Builtins.TypeOf(3.14),    # -> 'numeric'
                    Builtins.TypeOf('hello'), # -> 'text'
                    Builtins.TypeOf(None),    # -> 'unknown'
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(PG_TYPEOF({sql}))', p, str, c)

    @staticmethod
    def Upper(value):
        """Convert a text value to uppercase using PostgreSQL's ``UPPER()``.

        Generates an ``UPPER(<expr>)`` expression. The conversion is
        locale-sensitive and follows the database's collation rules for
        non-ASCII characters.

        This is the SQL equivalent of Python's ``str.upper()``. The result
        is always a ``TEXT`` string, so ``+`` chained after it produces
        concatenation (``||``) rather than arithmetic addition.

        The returned object is a :class:`ColumnsOperation`, so all string
        methods (``.contains``, ``.startswith``, ``.like``, ``.add_end``,
        slicing) remain available on it.

        Args:
            value: The expression whose text is uppercased. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(UPPER(<expr>))`` and whose ``current_datatype`` is always
            ``str``.

        Example:
            Case-insensitive comparison — the classic ``upper = upper``
            pattern::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row(
                    [users.id, users.name],
                    where=Builtins.Upper(users.name) == 'ALICE',
                )
                # SELECT "users"."id", "users"."name" FROM "users"
                # WHERE ((UPPER("users"."name")) = %s)
                # Parameters: ['ALICE']

            Case-insensitive with a raw literal on the right — same as
            above, written the other way round::

                rows = users.get_row(
                    [users.id],
                    where=users.name.upper() == 'ALICE',
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((UPPER("users"."name")) = %s)
                # Parameters: ['ALICE']

            As a SELECT column — display the uppercase name alongside the
            original::

                rows = users.get_row([
                    users.name,
                    Builtins.Upper(users.name),
                ])
                # SELECT "users"."name", (UPPER("users"."name")) FROM "users"
                # -> [('Alice', 'ALICE'), ('Bob', 'BOB'), ...]

            Chain with string methods — the result is TEXT::

                expr = Builtins.Upper(users.name).add_end(' (verified)')
                rows = users.get_row([users.name, expr])
                # SELECT "users"."name",
                #        ((UPPER("users"."name")) || %s)
                # FROM "users"
                # Parameters: [' (verified)']

            Case-insensitive sort — order by the uppercase version::

                rows = users.get_row(
                    [users.name],
                    order_by=Builtins.Upper(users.name),
                )
                # SELECT "users"."name" FROM "users"
                # ORDER BY (UPPER("users"."name"))

            Inside a LIKE pattern::

                rows = users.get_row(
                    [users.name],
                    where=Builtins.Upper(users.name).startswith('A'),
                )
                # SELECT "users"."name" FROM "users"
                # WHERE ((UPPER("users"."name")) LIKE %s || '%%')
                # Parameters: ['A']

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Upper('hello'),   # -> (UPPER(%s)) -> 'HELLO'
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(UPPER({sql}))', p, str, c)

    @staticmethod
    def Lower(value):
        """Convert a text value to lowercase using PostgreSQL's ``LOWER()``.

        Generates a ``LOWER(<expr>)`` expression. The conversion is
        locale-sensitive and follows the database's collation rules for
        non-ASCII characters.

        This is the SQL equivalent of Python's ``str.lower()``. The result
        is always a ``TEXT`` string, so ``+`` chained after it produces
        concatenation (``||``) rather than arithmetic addition.

        The returned object is a :class:`ColumnsOperation`, so all string
        methods remain available on it.

        Args:
            value: The expression whose text is lowercased. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(LOWER(<expr>))`` and whose ``current_datatype`` is always
            ``str``.

        Example:
            Case-insensitive email lookup — a very common pattern::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row(
                    [users.id, users.name],
                    where=Builtins.Lower(users.email) == 'alice@example.com',
                )
                # SELECT "users"."id", "users"."name" FROM "users"
                # WHERE ((LOWER("users"."email")) = %s)
                # Parameters: ['alice@example.com']

            Normalise user input before storing — a common update pattern::

                users.update(
                    update={users.email: Builtins.Lower(users.email)},
                    where=users.email != Builtins.Lower(users.email),
                )
                # UPDATE "users" SET "email" = (LOWER("users"."email"))
                # WHERE ("users"."email" != (LOWER("users"."email")));

            As a SELECT column — display the lowercase email alongside the
            original::

                rows = users.get_row([
                    users.email,
                    Builtins.Lower(users.email),
                ])
                # SELECT "users"."email", (LOWER("users"."email"))
                # FROM "users"

            Build a slug — lowercase and replace spaces::

                slug = Builtins.Lower(users.name).replace(' ', '-')
                rows = users.get_row([users.id, slug])
                # SELECT "users"."id",
                #        (REPLACE((LOWER("users"."name")), %s, %s))
                # FROM "users"
                # Parameters: [' ', '-']

            Combine with Upper for a case-folded comparison of two columns::

                same_ignoring_case = (
                    Builtins.Lower(users.first_name)
                    == Builtins.Lower(users.nickname)
                )
                rows = users.get_row([users.id], where=same_ignoring_case)
                # SELECT "users"."id" FROM "users"
                # WHERE ((LOWER("users"."first_name"))
                #        = (LOWER("users"."nickname")))

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Lower('HELLO'),   # -> (LOWER(%s)) -> 'hello'
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(LOWER({sql}))', p, str, c)

    @staticmethod
    def Trim(value):
        """Strip whitespace from both ends of a text value using ``TRIM()``.

        Generates a ``TRIM(<expr>)`` expression. By default, ``TRIM``
        removes spaces only — unlike Python's ``str.strip()`` which also
        removes tabs, newlines, and other whitespace. If you need to strip
        a specific set of characters, use :meth:`Builtins.Func` with the
        two-argument form, or chain the ``Column.strip`` method which
        already supports a ``chars`` argument.

        The result is always a ``TEXT`` string, so ``+`` chained after it
        produces concatenation (``||``) rather than arithmetic addition.

        Args:
            value: The expression whose text is trimmed. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(TRIM(<expr>))`` and whose ``current_datatype`` is always
            ``str``.

        Example:
            Clean up user-supplied input before storing::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                users.insert({
                    users.username: '  alice  ',
                })
                # Stored as-is with the padding

                users.update(
                    update={users.username: Builtins.Trim(users.username)},
                    where=users.username != Builtins.Trim(users.username),
                )
                # UPDATE "users" SET "username" = (TRIM("users"."username"))
                # WHERE ("users"."username" != (TRIM("users"."username")));

            Filter on trimmed values — match against a clean literal::

                rows = users.get_row(
                    [users.id, users.username],
                    where=Builtins.Trim(users.username) == 'alice',
                )
                # SELECT "users"."id", "users"."username" FROM "users"
                # WHERE ((TRIM("users"."username")) = %s)
                # Parameters: ['alice']

            Chain with other string methods — the result is TEXT::

                expr = Builtins.Trim(users.name).add_end('!')
                rows = users.get_row([expr])
                # SELECT ((TRIM("users"."name")) || %s) FROM "users"
                # Parameters: ['!']

            Chain with ``Upper`` for a full normalisation::

                clean = Builtins.Upper(Builtins.Trim(users.username))
                rows = users.get_row([users.id, clean])
                # SELECT "users"."id",
                #        (UPPER((TRIM("users"."username"))))
                # FROM "users"
                # -> [(1, 'ALICE'), (2, 'BOB'), ...]

            Sort ignoring leading/trailing whitespace::

                rows = users.get_row(
                    [users.username],
                    order_by=Builtins.Trim(users.username),
                )
                # SELECT "users"."username" FROM "users"
                # ORDER BY (TRIM("users"."username"))

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Trim('  hello  '),   # -> (TRIM(%s)) -> 'hello'
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(TRIM({sql}))', p, str, c)

    @staticmethod
    def Capitalize(value):
        """Capitalise a text value — first character upper, rest lower.

        PostgreSQL has no native ``INITCAP``-like two-step for this exact
        behaviour when applied to a *single* string (``INITCAP`` capitalises
        every word). Instead, this helper composes the Python-equivalent
        behaviour from two SQL expressions:

        .. code-block:: sql

            UPPER(SUBSTRING(<expr>, 1, 1)) || LOWER(SUBSTRING(<expr>, 2))

        The result is a new string with the first character converted to
        uppercase and every subsequent character converted to lowercase.
        This matches Python's ``str.capitalize()`` behaviour exactly for
        the common case.

        The result is always a ``TEXT`` string, so ``+`` chained after it
        produces concatenation (``||``) rather than arithmetic addition.

        Args:
            value: The expression whose first character is capitalised.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused. The fragment is embedded twice in the output
                  (once for the first char, once for the rest), so any
                  parameters it carries are also duplicated.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string — bound as a ``%s`` placeholder (also
                  duplicated).

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(UPPER(SUBSTRING(<expr>, 1, 1)) || LOWER(SUBSTRING(<expr>, 2)))``
            and whose ``current_datatype`` is always ``str``.

        Example:
            Title-case names for display — even if the stored values are
            all-uppercase or mixed-case::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    users.id,
                    Builtins.Capitalize(users.name),
                ])
                # SELECT "users"."id",
                #        (UPPER(SUBSTRING("users"."name", 1, 1))
                #         || LOWER(SUBSTRING("users"."name", 2)))
                # FROM "users"
                # -> [(1, 'Alice'), (2, 'Bob'), (3, 'Carol'), ...]
                #    even if the stored values were 'ALICE', 'bob', 'cAROL'

            Sort case-insensitively by name::

                rows = users.get_row(
                    [users.name],
                    order_by=Builtins.Capitalize(users.name),
                )
                # SELECT "users"."name" FROM "users"
                # ORDER BY (UPPER(SUBSTRING("users"."name", 1, 1))
                #           || LOWER(SUBSTRING("users"."name", 2)))

            Normalise before display::

                rows = users.get_row([
                    users.id,
                    Builtins.Capitalize(users.first_name).add_end(' ').add_end(
                        Builtins.Capitalize(users.last_name)
                    ),
                ])
                # SELECT "users"."id",
                #        ((UPPER(SUBSTRING("users"."first_name", 1, 1))
                #          || LOWER(SUBSTRING("users"."first_name", 2)))
                #         || %s
                #         || (UPPER(SUBSTRING("users"."last_name", 1, 1))
                #             || LOWER(SUBSTRING("users"."last_name", 2))))
                # FROM "users"
                # Parameters: [' ']

            Chain with other string builtins::

                expr = Builtins.Capitalize(users.name).add_end('!')
                rows = users.get_row([expr])
                # -> 'Alice!', 'Bob!', ...

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Capitalize('hELLO wORLD'),   # -> 'Hello world'
                ])
                # SQL: (UPPER(SUBSTRING(%s, 1, 1))
                #       || LOWER(SUBSTRING(%s, 2)))
                # Parameters: ['hELLO wORLD', 'hELLO wORLD']

            Combine with ``Trim`` to clean up and capitalise in one go::

                clean = Builtins.Capitalize(Builtins.Trim(users.name))
                rows = users.get_row([clean])
                # SELECT (UPPER(SUBSTRING((TRIM("users"."name")), 1, 1))
                #         || LOWER(SUBSTRING((TRIM("users"."name")), 2)))
                # FROM "users"
        """
        sql, p, _, c = Builtins._normalize(value)
        inner = f'UPPER(SUBSTRING({sql}, 1, 1)) || LOWER(SUBSTRING({sql}, 2))'
        return Builtins._make(f'({inner})', p, str, c)

    @staticmethod
    def Find(value, sub):
        """Locate a substring within a value using PostgreSQL's ``STRPOS()`` function.

        Generates a ``STRPOS(<value>, <sub>)`` expression. Returns the
        1-based position of the first occurrence of ``sub`` inside
        ``value``, or ``0`` if ``sub`` is not present.

        .. warning::
            SQLite's ``INSTR`` and PostgreSQL's ``STRPOS`` have the same
            semantics, but both differ from Python's ``str.find`` in two
            ways that routinely trip people up:

            ==================  =========  =========
            Behaviour           Python     SQL
            ==================  =========  =========
            Index of first char    0          1
            "Not found" value     -1          0
            ==================  =========  =========

            If you need strict Python semantics — e.g. so that
            ``Find(col, 'x') >= 0`` means "contains x" — add ``- 1`` to the
            result at the call site::

                py_find = Builtins.Find(users.name, 'x') - 1
                # py_find == -1 when not found, == 0 when found at position 0

            For a plain "does it contain this" check, :meth:`Column.contains`
            is usually the better choice.

        Args:
            value: The haystack. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

            sub: The needle. Same accepted types as ``value``. Usually a
                plain string literal, but any expression is allowed.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(STRPOS(<value>, <sub>))`` and whose ``current_datatype`` is
            ``int``. Parameters are concatenated left-to-right: first the
            haystack's parameters, then the needle's.

        Example:
            Find the position of a delimiter inside a text column::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "app")
                emails = db.emails

                at_pos = Builtins.Find(emails.address, '@')
                rows = emails.get_row([
                    emails.id,
                    emails.address,
                    at_pos,
                ])
                # SELECT "emails"."id", "emails"."address",
                #        (STRPOS("emails"."address", %s))
                # FROM "emails"
                # Parameters: ['@']
                # -> [(1, 'alice@example.com', 6),
                #     (2, 'bob@x.io', 4), ...]

            Detect the presence of a substring — use ``> 0`` because
            PostgreSQL returns 0 (not -1) when the needle is missing::

                rows = emails.get_row(
                    [emails.id, emails.address],
                    where=Builtins.Find(emails.address, 'spam') > 0,
                )
                # SELECT "emails"."id", "emails"."address"
                # FROM "emails"
                # WHERE ((STRPOS("emails"."address", %s)) > %s)
                # Parameters: ['spam', 0]

            Python-equivalent "not found" check — add ``- 1`` so -1 means
            "missing"::

                py_find = Builtins.Find(emails.address, 'example') - 1
                rows = emails.get_row(
                    [emails.address],
                    where=py_find == -1,
                )
                # SELECT "emails"."address" FROM "emails"
                # WHERE (((STRPOS("emails"."address", %s)) - %s) = %s)
                # Parameters: ['example', 1, -1]

            Use a column as the needle — find where one column appears
            inside another::

                rows = emails.get_row([
                    emails.id,
                    Builtins.Find(emails.address, emails.username),
                ])
                # SELECT "emails"."id",
                #        (STRPOS("emails"."address", "emails"."username"))
                # FROM "emails"

            Combine with slicing to extract everything before the '@'.
            Note that the ORM slice is start-inclusive, stop-exclusive on
            top of a 0-based expression, so we subtract one extra from the
            1-based STRPO S position::

                at_pos = Builtins.Find(emails.address, '@')
                local_part = emails.address[:at_pos - 1]
                rows = emails.get_row([emails.id, local_part])
                # The generated slice expression handles the -1 internally
                # and produces the substring before the '@'.

            Applied to raw literals::

                rows = emails.get_row([
                    Builtins.Find('hello', 'l'),   # -> 3 (first 'l' at 1-based pos 3)
                    Builtins.Find('hello', 'z'),   # -> 0
                ])
        """
        s1, p1, _, c = Builtins._normalize(value)
        s2, p2, _, _ = Builtins._normalize(sub)
        return Builtins._make(f'(STRPOS({s1}, {s2}))', p1 + p2, int, c)

    @staticmethod
    def Format(fmt, *args):
        """Format a string using PostgreSQL's ``FORMAT()`` function.

        Generates a ``FORMAT(<fmt>, <arg1>, <arg2>, ...)`` expression.
        Unlike SQLite's ``PRINTF`` which supports ``%d``, ``%f``, ``%s``,
        and ``%x``, PostgreSQL's ``FORMAT`` supports only the following
        placeholders:

        - ``%s`` — string substitution (the argument is cast to text)
        - ``%I`` — quoted identifier (useful for dynamic SQL)
        - ``%L`` — quoted literal (null → ``NULL``, others → quoted text)
        - ``%%`` — literal percent sign

        Numeric formatting with padding, decimals, or thousands separators
        is done via ``TO_CHAR`` instead — for example
        ``Builtins.Strftime('999,999.99', col)`` works on numeric inputs
        too, despite the name. See the ORM's ``Strftime`` helper.

        .. warning::
            The format string is bound as a parameter (``%s``), but the
            *format specifier* inside it is interpreted by PostgreSQL at
            execution time. A malformed format string will raise an SQL
            error; a mismatched number of specifiers versus arguments will
            silently substitute the wrong values or raise a runtime error.

        Args:
            fmt: The format string. Usually a Python ``str`` literal, but
                any expression is allowed. It is bound as a parameter, so
                user-supplied format strings are safe from SQL injection —
                though you should still validate them against an allowlist
                if security matters.

            *args: Zero or more arguments to substitute into ``fmt``.
                Each may be a :class:`ColumnsOperation`, a :class:`Column`,
                or a raw Python value. They are concatenated left-to-right
                and their parameters appended in the same order.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(FORMAT(<fmt>, <arg1>, ...))`` and whose ``current_datatype``
            is always ``str``.

        Example:
            Classic greeting — the SQL analogue of ``'Hi, %s' % name``::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                greeting = Builtins.Format('Hello, %s!', users.name)
                rows = users.get_row([greeting])
                # SELECT (FORMAT(%s, "users"."name")) FROM "users"
                # Parameters: ['Hello, %s!']
                # -> [('Hello, Alice!',), ('Hello, Bob!',), ...]

            Build a CSV line from multiple columns::

                row_csv = Builtins.Format(
                    '%s,%s,%s',
                    users.name,
                    users.email,
                    users.age,
                )
                rows = users.get_row([row_csv])
                # SELECT (FORMAT(%s, "users"."name",
                #                "users"."email", "users"."age"))
                # FROM "users"
                # Parameters: ['%s,%s,%s']
                # -> [('Alice,alice@x.com,30',), ...]

            Null-safe literal via ``%L`` — NULL becomes the SQL keyword
            NULL, others are quoted::

                expr = Builtins.Format('SELECT %L', users.name)
                rows = users.get_row([expr])
                # -> [("SELECT 'Alice'",), ("SELECT 'Bob'",), ...]

            Quoted identifier via ``%I`` — for dynamic-SQL generation::

                expr = Builtins.Format('SELECT * FROM %I', users.name)
                rows = users.get_row([expr])
                # -> [('SELECT * FROM "Alice"',), ...]

            Escape a literal percent sign with ``%%``::

                percent = Builtins.Format('%s%% complete', users.progress)
                rows = users.get_row([percent])
                # Parameters: ['%s%% complete']
                # -> [('75% complete',), ('100% complete',), ...]

            Combine with other builtins::

                total = Builtins.Format(
                    'Total: %s USD',
                    Builtins.Sum(users.balance),
                )
                rows = users.get_row([total])
                # SELECT (FORMAT(%s, (SUM("users"."balance"))))
                # FROM "users"
                # Parameters: ['Total: %s USD']

            Numeric formatting is NOT handled by FORMAT — use Strftime
            (which maps to TO_CHAR) for that::

                pretty = Builtins.Strftime('999,999.99', users.balance)
                rows = users.get_row([pretty])
                # SELECT (TO_CHAR("users"."balance", %s)) FROM "users"
                # Parameters: ['999,999.99']
                # -> [('  123,456.78',), ...]
        """
        s0, p0, _, c = Builtins._normalize(fmt)
        parts, params = [s0], list(p0)
        for v in args:
            s, p, _, _ = Builtins._normalize(v)
            parts.append(s)
            params.extend(p)
        return Builtins._make(f'(FORMAT({", ".join(parts)}))', params, str, c)

    @staticmethod
    def IsNull(value):
        """Test whether a value is NULL.

        Generates an ``(<expr> IS NULL)`` predicate. PostgreSQL evaluates
        this to a boolean ``TRUE`` when the expression produces NULL, and
        ``FALSE`` otherwise.

        This is the explicit, always-correct way to check for NULL. Using
        the ordinary comparison operator — ``column == None`` — is also
        supported by this ORM (see :meth:`Column.eq`) but only when the
        right-hand side is literally ``None``. ``IsNull`` works uniformly
        with any expression:

        - A column that may contain NULL
        - A computed expression that may return NULL (e.g. ``Sqrt`` of a
          negative number raises, ``Func('NULLIF', …)`` returns NULL, an
          aggregate over an empty group with ``Sum``)
        - A raw Python ``None`` literal

        The result is a real Python ``bool``, so it can be compared
        directly to Python booleans in the ORM's filter expressions.

        Args:
            value: The expression to test. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder. In
                  particular, ``IsNull(None)`` produces ``(%s IS NULL)``
                  with the ``None`` bound as a parameter, which is always
                  true.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``((<expr>) IS NULL)`` and whose ``current_datatype`` is
            ``bool``.

        Example:
            Select users who have not yet provided an email::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row(
                    [users.id, users.username],
                    where=Builtins.IsNull(users.email),
                )
                # SELECT "users"."id", "users"."username" FROM "users"
                # WHERE (("users"."email") IS NULL)

            Negate the check — see :meth:`IsNotNull`::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.IsNotNull(users.email),
                )

            Combine with AND to build richer filters::

                rows = users.get_row(
                    [users.id, users.username],
                    where=Builtins.IsNull(users.email)
                        & (users.created_at > '2024-01-01'),
                )
                # SELECT "users"."id", "users"."username" FROM "users"
                # WHERE ((("users"."email") IS NULL)
                #        AND ("users"."created_at" > %s))
                # Parameters: ['2024-01-01']

            Test a computed expression that might return NULL — for
            example the ``NULLIF`` of a field against itself::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.IsNull(
                        Builtins.Func('NULLIF', users.email)
                    ),
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((NULLIF("users"."email")) IS NULL)

            Count how many rows have a NULL value in one column. PostgreSQL
            does not allow boolean values to be cast to integer implicitly,
            so this uses a CASE to convert to 0/1::

                missing_emails = Builtins.Sum(
                    Builtins.IIf(Builtins.IsNull(users.email), 1, 0)
                )
                rows = users.get_row([missing_emails])
                # SELECT (SUM((CASE WHEN (("users"."email") IS NULL)
                #                    THEN %s ELSE %s END)))
                # FROM "users"
                # Parameters: [1, 0]
                # -> [(42,)] if 42 users have no email

            Check for a raw literal ``None``::

                rows = users.get_row([
                    Builtins.IsNull(None),   # -> ((%s) IS NULL) -> always TRUE
                ])
                # Parameters: [None]
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(({sql}) IS NULL)', p, bool, c)

    @staticmethod
    def IsNotNull(value):
        """Test whether a value is not NULL.

        Generates an ``(<expr> IS NOT NULL)`` predicate. PostgreSQL
        evaluates this to a boolean ``TRUE`` when the expression produces
        any non-NULL value, and ``FALSE`` when it evaluates to NULL.

        The exact logical negation of :meth:`IsNull`. Use whichever reads
        more naturally at the call site — both compile to distinct SQL but
        produce the same set of rows when their results are inverted.

        The result is a real Python ``bool``, so it can be compared
        directly to Python booleans in the ORM's filter expressions.

        Args:
            value: The expression to test. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder. In
                  particular, ``IsNotNull(42)`` produces ``(%s IS NOT NULL)``
                  with the ``42`` bound as a parameter, which is always true;
                  ``IsNotNull(None)`` is always false.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``((<expr>) IS NOT NULL)`` and whose ``current_datatype`` is
            ``bool``.

        Example:
            Select users who have provided an email::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row(
                    [users.id, users.username, users.email],
                    where=Builtins.IsNotNull(users.email),
                )
                # SELECT "users"."id", "users"."username", "users"."email"
                # FROM "users"
                # WHERE (("users"."email") IS NOT NULL)

            Combine with OR to allow either of two identifier columns::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.IsNotNull(users.email)
                        | Builtins.IsNotNull(users.phone),
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((("users"."email") IS NOT NULL)
                #        OR (("users"."phone") IS NOT NULL))

            Count non-NULL values in a column — the inverse of the
            ``IsNull`` count::

                have_email = Builtins.Sum(
                    Builtins.IIf(Builtins.IsNotNull(users.email), 1, 0)
                )
                rows = users.get_row([have_email])
                # SELECT (SUM((CASE WHEN (("users"."email") IS NOT NULL)
                #                    THEN %s ELSE %s END)))
                # FROM "users"
                # Parameters: [1, 0]
                # -> [(198,)]

            Test a computed expression whose non-NULLness is meaningful —
            the classic "only rows where the ratio is defined"::

                rows = users.get_row(
                    [users.id, users.username],
                    where=Builtins.IsNotNull(
                        Builtins.Func('NULLIF', users.attempts)
                    ),
                )
                # SELECT "users"."id", "users"."username" FROM "users"
                # WHERE ((NULLIF("users"."attempts")) IS NOT NULL)

            "Both or neither" validation — the classic pattern to find
            inconsistent rows::

                both_set = (
                    Builtins.IsNotNull(users.email)
                    & Builtins.IsNotNull(users.verified_at)
                )
                neither_set = (
                    Builtins.IsNull(users.email)
                    & Builtins.IsNull(users.verified_at)
                )
                rows = users.get_row(
                    [users.id],
                    where=(both_set | neither_set) == False,
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((((("users"."email") IS NOT NULL)
                #          AND (("users"."verified_at") IS NOT NULL))
                #         OR ((("users"."email") IS NULL)
                #             AND (("users"."verified_at") IS NULL))) = %s)
                # Parameters: [False]

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.IsNotNull(42),    # -> ((%s) IS NOT NULL) -> TRUE
                    Builtins.IsNotNull(None),  # -> ((%s) IS NOT NULL) -> FALSE
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(({sql}) IS NOT NULL)', p, bool, c)

    @staticmethod
    def Between(value, low, high):
        """Test whether a value lies within an inclusive range.

        Generates an ``(<expr> BETWEEN <low> AND <high>)`` predicate.
        PostgreSQL evaluates this to a boolean ``TRUE`` when
        ``low <= value <= high`` and ``FALSE`` otherwise. Both bounds are
        inclusive, matching Python's ``low <= x <= high`` idiom exactly.

        .. note::
            SQL's ``BETWEEN`` is equivalent to
            ``(value >= low) AND (value <= high)`` but is a single operator
            with well-defined semantics for all three operand types. When
            any of the three operands is NULL the result is NULL, which the
            surrounding query treats as "false" in a ``WHERE`` clause.

        The result is a real Python ``bool``, so it can be compared
        directly to Python booleans in the ORM's filter expressions.

        Args:
            value: The expression to test. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

            low: The lower bound (inclusive). Same accepted types as
                ``value``.

            high: The upper bound (inclusive). Same accepted types as
                ``value``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``((<value>) BETWEEN <low> AND <high>)`` and whose
            ``current_datatype`` is ``bool``. Parameters are concatenated
            left-to-right: first ``value``'s, then ``low``'s, then
            ``high``'s.

        Example:
            Select products in a price band::

                from Ormophine.Postgresql import Driver, Builtins

                db       = Driver("localhost", 5432, "user", "pass", "store")
                products = db.products

                rows = products.get_row(
                    [products.name, products.price],
                    where=Builtins.Between(products.price, 10.0, 50.0),
                )
                # SELECT "products"."name", "products"."price"
                # FROM "products"
                # WHERE (("products"."price") BETWEEN %s AND %s)
                # Parameters: [10.0, 50.0]

            Date-range filter — the most common real-world use::

                users = db.users
                rows = users.get_row(
                    [users.id, users.created_at],
                    where=Builtins.Between(
                        users.created_at,
                        '2024-01-01',
                        '2024-12-31',
                    ),
                )
                # SELECT "users"."id", "users"."created_at" FROM "users"
                # WHERE (("users"."created_at") BETWEEN %s AND %s)
                # Parameters: ['2024-01-01', '2024-12-31']

            Filter on a computed expression::

                rows = products.get_row(
                    [products.name],
                    where=Builtins.Between(
                        products.price * 0.9,      # discounted price
                        5.0,
                        45.0,
                    ),
                )
                # SELECT "products"."name" FROM "products"
                # WHERE (("products"."price" * %s) BETWEEN %s AND %s)
                # Parameters: [0.9, 5.0, 45.0]

            Boundaries using other columns — margins within 10% of cost::

                rows = products.get_row(
                    [products.name, products.price],
                    where=Builtins.Between(
                        products.price,
                        products.cost,
                        products.cost * 1.1,
                    ),
                )
                # SELECT "products"."name", "products"."price"
                # FROM "products"
                # WHERE (("products"."price")
                #        BETWEEN "products"."cost"
                #            AND ("products"."cost" * %s))
                # Parameters: [1.1]

            Negate the check — rows outside the band. PostgreSQL returns
            boolean, so the ``== False`` comparison is clean::

                in_band = Builtins.Between(products.price, 10.0, 50.0)
                rows = products.get_row(
                    [products.name],
                    where=in_band == False,
                )
                # SELECT "products"."name" FROM "products"
                # WHERE ((("products"."price") BETWEEN %s AND %s) = %s)
                # Parameters: [10.0, 50.0, False]

            NULL handling — a NULL value in any operand makes the whole
            predicate NULL (treated as false). To include NULLs, add an
            explicit OR::

                rows = products.get_row(
                    [products.name],
                    where=Builtins.Between(products.price, 10.0, 50.0)
                        | Builtins.IsNull(products.price),
                )
                # SELECT "products"."name" FROM "products"
                # WHERE ((("products"."price") BETWEEN %s AND %s)
                #        OR (("products"."price") IS NULL))
                # Parameters: [10.0, 50.0]

            Applied to raw literals::

                rows = products.get_row([
                    Builtins.Between(5, 1, 10),     # -> TRUE
                    Builtins.Between(15, 1, 10),    # -> FALSE
                ])
        """
        s, p, _, c = Builtins._normalize(value)
        lo, lp, _, _ = Builtins._normalize(low)
        hi, hp, _, _ = Builtins._normalize(high)
        return Builtins._make(
            f'(({s}) BETWEEN {lo} AND {hi})', p + lp + hp, bool, c)

    @staticmethod
    def IIf(condition, then_value, else_value):
        """One-line conditional — the SQL analogue of Python's ``a if cond else b``.

        Generates a ``CASE WHEN <cond> THEN <then> ELSE <else> END``
        expression. PostgreSQL has no built-in ``IIF`` function (unlike
        SQLite 3.32+ and SQL Server), so this helper emits the standard
        SQL ``CASE`` form directly — which works on every version of
        PostgreSQL and is also more portable across other database engines.

        This is the value-producing sibling of the SQL ``CASE`` statement
        and the direct equivalent of Python's ternary conditional operator::

            Python:   x if cond else y
            SQL:      CASE WHEN cond THEN x ELSE y END
            ORM:      Builtins.IIf(cond, x, y)

        .. note::
            PostgreSQL evaluates both branches before choosing one — same
            as Python's conditional expression. If a branch divides by
            zero, that will raise at execution time even when the condition
            would have skipped that branch. Guard the divisor with a nested
            ``IIf`` or use ``Func('NULLIF', …)`` if this matters.

        Args:
            condition: The test expression. May be any standard operand:

                - :class:`ColumnsOperation` — typically a comparison like
                  ``users.age >= 18``.
                - :class:`Column` — used directly as a boolean.
                - A raw Python value (``True``, ``False``, ``0``, ``1``)
                  — bound as a parameter.

            then_value: What to return when ``condition`` is truthy.
                Same accepted types as ``condition``.

            else_value: What to return when ``condition`` is falsy.
                Same accepted types as ``condition``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CASE WHEN <cond> THEN <then> ELSE <else> END)`` and whose
            ``current_datatype`` is ``None``. The datatype is not
            propagated because the two branches may disagree (e.g. ``str``
            versus ``int``); downstream ``+`` operators will choose
            arithmetic by default, which the caller may override by
            wrapping the result in :meth:`Str`, :meth:`Int`, or
            :meth:`Float`. Parameters are concatenated in the order:
            condition, then, else.

        Example:
            Categorise ages into minor / adult — the classic pattern::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                category = Builtins.IIf(users.age >= 18, 'adult', 'minor')
                rows = users.get_row([
                    users.name,
                    users.age,
                    category,
                ])
                # SELECT "users"."name", "users"."age",
                #        (CASE WHEN ("users"."age" >= %s)
                #              THEN %s ELSE %s END)
                # FROM "users"
                # Parameters: [18, 'adult', 'minor']
                # -> [('Alice', 30, 'adult'), ('Bob', 15, 'minor'), ...]

            Fill NULL with a fallback — equivalent to COALESCE but with
            a boolean test::

                display_email = Builtins.IIf(
                    Builtins.IsNull(users.email),
                    'no email',
                    users.email,
                )
                rows = users.get_row([users.username, display_email])
                # SELECT "users"."username",
                #        (CASE WHEN (("users"."email") IS NULL)
                #              THEN %s ELSE "users"."email" END)
                # FROM "users"
                # Parameters: ['no email']

            Nested conditionals — a two-threshold bucketing. Each nested
            call goes inside the ``else_value`` slot::

                label = Builtins.IIf(
                    users.score >= 90,
                    'A',
                    Builtins.IIf(
                        users.score >= 80,
                        'B',
                        Builtins.IIf(
                            users.score >= 70,
                            'C',
                            'F',
                        ),
                    ),
                )
                rows = users.get_row([users.name, users.score, label])
                # SELECT "users"."name", "users"."score",
                #        (CASE WHEN ("users"."score" >= %s) THEN %s
                #              ELSE (CASE WHEN ("users"."score" >= %s) THEN %s
                #                         ELSE (CASE WHEN ("users"."score" >= %s)
                #                                    THEN %s ELSE %s END) END) END)
                # FROM "users"
                # Parameters: [90, 'A', 80, 'B', 70, 'C', 'F']

            Conditional arithmetic — bonus only for top performers::

                adjusted_salary = Builtins.IIf(
                    users.rating > 4,
                    users.salary * 1.1,
                    users.salary,
                )
                rows = users.get_row([
                    users.name,
                    users.salary,
                    adjusted_salary,
                ])
                # SELECT "users"."name", "users"."salary",
                #        (CASE WHEN ("users"."rating" > %s)
                #              THEN ("users"."salary" * %s)
                #              ELSE "users"."salary" END)
                # FROM "users"
                # Parameters: [4, 1.1]

            Coerce the result to a specific type with a cast builtin::

                label_num = Builtins.Int(
                    Builtins.IIf(users.is_active == True, 100, 0)
                )
                rows = users.get_row([label_num])
                # SELECT (CAST((CASE WHEN ("users"."is_active" = %s)
                #                    THEN %s ELSE %s END) AS INTEGER))
                # FROM "users"
                # Parameters: [True, 100, 0]

            Count matches via SUM of IIF — a common "conditional
            aggregate" idiom::

                active_count = Builtins.Sum(
                    Builtins.IIf(users.is_active == True, 1, 0)
                )
                rows = users.get_row([active_count])
                # SELECT (SUM((CASE WHEN ("users"."is_active" = %s)
                #                   THEN %s ELSE %s END)))
                # FROM "users"
                # Parameters: [True, 1, 0]

            Diverging branches with different datatypes — the reason
            ``current_datatype`` is ``None`` on the result::

                mixed = Builtins.IIf(users.has_phone == True,
                                     users.phone,
                                     0)
                # If has_phone, returns a string; else returns integer 0.
                # Wrap with Builtins.Str if the surrounding context expects
                # a string:
                safe = Builtins.Str(mixed)
                rows = users.get_row([safe])
                # SELECT (CAST((CASE WHEN ("users"."has_phone" = %s)
                #                    THEN "users"."phone"
                #                    ELSE %s END) AS TEXT))
                # FROM "users"
                # Parameters: [True, 0]
        """
        cs, cp, _, c = Builtins._normalize(condition)
        ts, tp, _, _ = Builtins._normalize(then_value)
        es, ep, _, _ = Builtins._normalize(else_value)
        return Builtins._make(
            f'(CASE WHEN {cs} THEN {ts} ELSE {es} END)',
            cp + tp + ep, None, c)
    
    @staticmethod
    def Func(name, value):
        """Apply an arbitrary SQL function to a single operand.

        Generates a ``(<name>(<expr>))`` expression. This is the escape
        hatch for any PostgreSQL function whose return type cannot be
        inferred from the ORM's built-in catalogue — for example
        ``COALESCE``, ``NULLIF``, ``GREATEST``, ``LEAST``, ``INITCAP``,
        ``MD5``, ``ENCODE``, ``DECODE``, ``REGEXP_REPLACE``, or any
        user-defined function.

        The helper is deliberately minimal: it takes exactly two
        arguments — the function name as a Python string, and the single
        operand to feed it. Multi-argument functions need to be built by
        chaining, or by using the ``Column`` methods that already wrap the
        common cases (``.replace`` for ``REPLACE``, ``.like`` for ``LIKE``,
        etc.).

        Because the return type is unknown, ``current_datatype`` is
        propagated from the input as a best-effort heuristic:

        - ``str``   → ``str``   (safe for text functions)
        - ``int``   → ``int``
        - ``float`` → ``float``
        - ``bool``  → ``bool``

        This keeps arithmetic chains (``Func('ABS', col) + 1``) compiling
        to ``+`` when the input was numeric, and keeps concatenation
        (``Func('INITCAP', col) + '!'``) compiling to ``||`` when the input
        was text. If the actual return type differs from the input's type
        — for example ``Func('LENGTH', some_text)`` returns an integer —
        wrap the result in :meth:`Int`, :meth:`Float`, :meth:`Str`, or
        :meth:`Bool` to force the correct ``current_datatype`` before
        chaining further.

        Args:
            name: The PostgreSQL function name. Must be a non-empty string.
                Case is preserved as written, so if the function is defined
                in lowercase (PostgreSQL's default), pass it in lowercase.

            value: The single operand. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python value — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(<name>(<expr>))`` and whose ``current_datatype`` is
            propagated from the input.

        Raises:
            ValueError: If ``name`` is not a non-empty string.

        Example:
            ``COALESCE`` — return the first non-NULL argument. Note that
            since the multi-argument form is not supported by ``Func``,
            this example wraps a single column that is *already* known to
            contain NULLs and provides no fallback; use :meth:`Column.If`
            for that pattern instead::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    Builtins.Func('INITCAP', users.name),
                ])
                # SELECT (INITCAP("users"."name")) FROM "users"
                # -> [('Alice Smith',), ('Bob Jones',), ...]

            ``MD5`` — a text function that returns text::

                rows = users.get_row([
                    Builtins.Func('MD5', users.email),
                ])
                # SELECT (MD5("users"."email")) FROM "users"
                # -> [('5d41402abc4b2a76b9719d911017c592',), ...]

            ``LENGTH`` on a text column — the return type is ``int`` but
            the input's ``current_datatype`` is ``str``, so we wrap with
            :meth:`Int` to force the correct chaining semantics::

                length_int = Builtins.Int(Builtins.Func('LENGTH', users.name))
                rows = users.get_row(
                    [users.name, length_int],
                    where=length_int > 5,
                )
                # SELECT "users"."name",
                #        (CAST((LENGTH("users"."name")) AS INTEGER))
                # FROM "users"
                # WHERE ((CAST((LENGTH("users"."name")) AS INTEGER)) > %s)
                # Parameters: [5]

            ``REGEXP_REPLACE`` — strip non-ASCII from a column. The
            pattern and replacement are baked into the function via
            Python-side string composition, since ``Func`` accepts only
            one operand::

                expr = Builtins.Func('REGEXP_REPLACE', users.name)
                rows = users.get_row([expr])
                # SELECT (REGEXP_REPLACE("users"."name")) FROM "users"
                # NOTE: the two-argument REGEXP_REPLACE requires a second
                # argument — this will fail at execution. Use a raw
                # expression or the Column API instead.

            ``GREATEST`` on two columns — again, the multi-argument case.
            Build the SQL manually via ``Column`` operators::

                from Ormophine.Postgresql import ColumnsOperation
                # The built-in way to get the larger of two columns:
                expr = users.updated_at   # placeholder for illustration
                # For "greatest of two columns", use a CASE:
                later = Builtins.IIf(
                    users.updated_at > users.created_at,
                    users.updated_at,
                    users.created_at,
                )
                rows = users.get_row([later])
                # SELECT (CASE WHEN ("users"."updated_at" > "users"."created_at")
                #              THEN "users"."updated_at"
                #              ELSE "users"."created_at" END)
                # FROM "users"

            Chained with arithmetic — ``current_datatype`` propagates
            from the input::

                doubled = Builtins.Func('ABS', users.balance) * 2
                rows = users.get_row([doubled])
                # SELECT ((ABS("users"."balance")) * %s) FROM "users"
                # Parameters: [2]

            Chained with string operations — same idea, text side::

                expr = Builtins.Func('INITCAP', users.name).add_end('!')
                rows = users.get_row([expr])
                # SELECT ((INITCAP("users"."name")) || %s) FROM "users"
                # Parameters: ['!']

            Raises on empty name::

                Builtins.Func('', users.name)
                # ValueError: Func requires a non-empty function name string
        """
        if not isinstance(name, str) or not name:
            raise ValueError('Func requires a non-empty function name string')
        sql, p, dt, c = Builtins._normalize(value)
        return Builtins._make(f'({name}({sql}))', p, dt, c)

    @staticmethod
    def Date(value):
        """Cast a value to a DATE, discarding any time-of-day component.

        Generates a ``CAST(<expr> AS DATE)`` expression. This is the SQL
        equivalent of taking ``datetime.date()`` from a Python
        ``datetime.datetime`` object — it strips the time portion and
        keeps only the calendar date.

        The result is a real Python ``datetime.date`` after fetching (the
        psycopg driver converts PostgreSQL's ``DATE`` type automatically).
        The ``current_datatype`` is set to ``str`` because the ORM's
        datatype dispatch treats dates as text-like values in comparisons
        and concatenation.

        PostgreSQL accepts a wide range of inputs for the cast:

        - A ``TIMESTAMP`` or ``TIMESTAMPTZ`` column (the common case)
        - An ISO 8601 text literal like ``'2024-03-15'``
        - An ISO 8601 timestamp string like ``'2024-03-15 09:30:00'``
        - A ``CURRENT_DATE`` or ``NOW()`` expression

        Args:
            value: The expression whose date component is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS DATE))`` and whose ``current_datatype`` is
            ``str``.

        Example:
            Get the signup date for every user — strip the time component::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    users.id,
                    users.created_at,
                    Builtins.Date(users.created_at),
                ])
                # SELECT "users"."id", "users"."created_at",
                #        (CAST("users"."created_at" AS DATE))
                # FROM "users"
                # -> [(1, datetime(2024, 3, 15, 9, 30, 42), date(2024, 3, 15)), ...]

            Filter by a specific day — everything created on March 15::

                rows = users.get_row(
                    [users.id, users.username],
                    where=Builtins.Date(users.created_at)
                        == Builtins.Date('2024-03-15'),
                )
                # SELECT "users"."id", "users"."username" FROM "users"
                # WHERE ((CAST("users"."created_at" AS DATE))
                #        = (CAST(%s AS DATE)))
                # Parameters: ['2024-03-15']

            Compare against today's date — the ``CURRENT_DATE`` keyword::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.Date(users.created_at)
                        == Builtins.Func('CURRENT_DATE', 'dummy'),
                )
                # NOTE: CURRENT_DATE is a keyword, not a function. Use
                # Builtins.Today() for a cleaner equivalent (see below).

            The idiomatic "today" comparison uses :meth:`Today`::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.Date(users.created_at) == Builtins.Today(),
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((CAST("users"."created_at" AS DATE)) = (CURRENT_DATE))

            Group by day and count — do the grouping in Python after
            fetching, since the ORM has no GROUP BY helper::

                rows = users.get_row([Builtins.Date(users.created_at)])
                from collections import Counter
                daily = Counter(d for (d,) in rows)
                # {date(2024, 3, 15): 12, date(2024, 3, 16): 8, ...}

            Chain with date arithmetic via :meth:`DateAdd`::

                next_week = Builtins.DateAdd(users.created_at, '7 days')
                rows = users.get_row([
                    users.id,
                    next_week,
                ])
                # SELECT "users"."id",
                #        (CAST((CAST("users"."created_at" AS TIMESTAMP)
                #               + %s::interval) AS DATE))
                # FROM "users"
                # Parameters: ['7 days']

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Date('2024-03-15 14:30:00'),  # -> date(2024, 3, 15)
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CAST({sql} AS DATE))', p, str, c)

    @staticmethod
    def Time(value):
        """Cast a value to a TIME, discarding any date component.

        Generates a ``CAST(<expr> AS TIME)`` expression. This is the SQL
        equivalent of taking ``datetime.time()`` from a Python
        ``datetime.datetime`` object — it strips the date portion and keeps
        only the time of day.

        The result is a real Python ``datetime.time`` after fetching (the
        psycopg driver converts PostgreSQL's ``TIME`` type automatically).
        The ``current_datatype`` is set to ``str`` because the ORM's
        datatype dispatch treats times as text-like values in comparisons
        and concatenation.

        Note that ``CAST(<expr> AS TIME)`` produces a ``TIME WITHOUT TIME
        ZONE`` by default. To preserve a time-zone offset, cast to
        ``TIMETZ`` instead — use :meth:`Func` with the target type, or
        rely on the column's declared type.

        PostgreSQL accepts a wide range of inputs for the cast:

        - A ``TIMESTAMP`` or ``TIMESTAMPTZ`` column
        - An ISO 8601 text literal like ``'09:30:00'``
        - An ISO 8601 timestamp string like ``'2024-03-15 09:30:00'``
        - A ``NOW()`` expression

        Args:
            value: The expression whose time component is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS TIME))`` and whose ``current_datatype`` is
            ``str``.

        Example:
            Get the signup time for every user — strip the date component::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    users.id,
                    users.created_at,
                    Builtins.Time(users.created_at),
                ])
                # SELECT "users"."id", "users"."created_at",
                #        (CAST("users"."created_at" AS TIME))
                # FROM "users"
                # -> [(1, datetime(2024, 3, 15, 9, 30, 42), time(9, 30, 42)), ...]

            Filter by time-of-day range — office-hours signups::

                rows = users.get_row(
                    [users.id, users.username],
                    where=Builtins.Time(users.created_at) >= '09:00:00',
                )
                # SELECT "users"."id", "users"."username" FROM "users"
                # WHERE ((CAST("users"."created_at" AS TIME)) >= %s)
                # Parameters: ['09:00:00']

            Half-open range — the BETWEEN helper is inclusive, so use two
            comparisons for a strict upper bound::

                t = Builtins.Time(users.created_at)
                rows = users.get_row(
                    [users.id],
                    where=(t >= '09:00:00') & (t < '17:00:00'),
                )
                # SELECT "users"."id" FROM "users"
                # WHERE (((CAST("users"."created_at" AS TIME)) >= %s)
                #        AND ((CAST("users"."created_at" AS TIME)) < %s))
                # Parameters: ['09:00:00', '17:00:00']

            Extract the hour via :meth:`Hour` instead of slicing in Python::

                rows = users.get_row([
                    users.id,
                    Builtins.Hour(users.created_at),
                ])
                # SELECT "users"."id",
                #        (EXTRACT(HOUR FROM "users"."created_at")::INTEGER)
                # FROM "users"

            Compare against the current time — the ``'now'`` keyword is
            not a thing in PostgreSQL; use :meth:`Now` instead::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.Time(users.created_at)
                        < Builtins.Time(Builtins.Now()),
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((CAST("users"."created_at" AS TIME))
                #        < (CAST((NOW()) AS TIME)))

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Time('2024-03-15 14:30:00'),  # -> time(14, 30)
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CAST({sql} AS TIME))', p, str, c)

    @staticmethod
    def DateTime(value):
        """Cast a value to a TIMESTAMP (without time zone).

        Generates a ``CAST(<expr> AS TIMESTAMP)`` expression. This is the
        SQL equivalent of Python's ``datetime.datetime`` — a date and time
        together, without any time-zone awareness.

        The result is a real Python ``datetime.datetime`` after fetching
        (the psycopg driver converts PostgreSQL's ``TIMESTAMP`` type
        automatically). The ``current_datatype`` is set to ``str`` because
        the ORM's datatype dispatch treats timestamps as text-like values
        in comparisons and concatenation.

        Common uses:

        - **Normalising** text or mixed-shape inputs into a canonical
          ``TIMESTAMP`` form so they compare consistently.
        - **Stripping the time zone** from a ``TIMESTAMPTZ`` value — for
          example, to compare against a ``TIMESTAMP`` column without
          time-zone conversion.
        - **Upgrading** a ``DATE`` value to a ``TIMESTAMP`` at midnight.

        To preserve a time zone, cast to ``TIMESTAMPTZ`` instead — use
        :meth:`Func` with the target type, or rely on the column's
        declared type.

        Args:
            value: The expression whose full datetime is produced.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS TIMESTAMP))`` and whose ``current_datatype``
            is ``str``.

        Example:
            Normalise mixed-format timestamps for display::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "app")
                events = db.events

                rows = events.get_row([
                    events.id,
                    events.occurred_at,
                    Builtins.DateTime(events.occurred_at),
                ])
                # SELECT "events"."id", "events"."occurred_at",
                #        (CAST("events"."occurred_at" AS TIMESTAMP))
                # FROM "events"
                # -> [(1, datetime(2024, 3, 15, 0, 0), datetime(2024, 3, 15, 0, 0)), ...]

            Strip the time zone from a ``TIMESTAMPTZ`` column for
            comparison against a ``TIMESTAMP`` column::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.DateTime(events.occurred_at)
                        > Builtins.DateTime(events.created_at),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE ((CAST("events"."occurred_at" AS TIMESTAMP))
                #        > (CAST("events"."created_at" AS TIMESTAMP)))

            Get the current timestamp as a SELECT column — see
            :meth:`Now` for a cleaner alternative::

                rows = events.get_row([
                    Builtins.DateTime(Builtins.Now()),
                ])
                # SELECT (CAST((NOW()) AS TIMESTAMP)) FROM "events"
                # -> [(datetime(2024, 3, 15, 14, 30, 42),)]

            "Last 24 hours" filter — combine with :meth:`DateTimeAdd`::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.DateTime(events.occurred_at)
                        >= Builtins.DateTimeAdd(Builtins.Now(), '-1 day'),
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE ((CAST("events"."occurred_at" AS TIMESTAMP))
                #        >= ((CAST((NOW()) AS TIMESTAMP) + %s::interval)))
                # Parameters: ['-1 day']

            Bucket events into "recent / older" via IIF::

                recent = Builtins.IIf(
                    Builtins.DateTime(events.occurred_at)
                        >= Builtins.DateTimeAdd(Builtins.Now(), '-1 day'),
                    'recent',
                    'older',
                )
                rows = events.get_row([events.id, recent])
                # SELECT "events"."id",
                #        (CASE WHEN ((CAST("events"."occurred_at" AS TIMESTAMP))
                #                    >= ((CAST((NOW()) AS TIMESTAMP)
                #                         + %s::interval)))
                #              THEN %s ELSE %s END)
                # FROM "events"
                # Parameters: ['-1 day', 'recent', 'older']

            Store a computed timestamp during an UPDATE::

                events.update(
                    update={events.last_seen: Builtins.Now()},
                    where=events.id == 42,
                )
                # UPDATE "events" SET "last_seen" = (NOW())
                # WHERE ("events"."id" = %s);
                # Parameters: [42]

            Applied to a raw literal to normalise at the SQL layer::

                rows = events.get_row([
                    Builtins.DateTime('2024-03-15'),           # -> midnight
                    Builtins.DateTime('2024-03-15 09:30:00'),  # unchanged
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(CAST({sql} AS TIMESTAMP))', p, str, c)

    @staticmethod
    def Year(value):
        """Extract the four-digit year from a date/time value.

        Generates an ``EXTRACT(YEAR FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``EXTRACT`` returns a ``NUMERIC`` by default, so the
        explicit ``::INTEGER`` cast is required to keep the datatype
        honest — otherwise a downstream ``+ 1`` would keep the numeric
        domain but psycopg would deliver a ``decimal.Decimal`` instead of
        a Python ``int``.

        The result is always an ``INTEGER``, so it plays nicely with
        arithmetic, comparisons, and other numeric builtins.

        Args:
            value: The date/time expression whose year is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(YEAR FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Filter rows by year::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row(
                    [users.id, users.created_at],
                    where=Builtins.Year(users.created_at) == 2024,
                )
                # SELECT "users"."id", "users"."created_at" FROM "users"
                # WHERE ((EXTRACT(YEAR FROM "users"."created_at")::INTEGER) = %s)
                # Parameters: [2024]

            Year-over-year reporting — group in Python after fetching::

                rows = users.get_row([Builtins.Year(users.created_at)])
                from collections import Counter
                by_year = Counter(y for (y,) in rows)
                # {2022: 120, 2023: 340, 2024: 88}

            Extract the year as a SELECT column::

                rows = users.get_row([
                    users.username,
                    Builtins.Year(users.created_at),
                ])
                # SELECT "users"."username",
                #        (EXTRACT(YEAR FROM "users"."created_at")::INTEGER)
                # FROM "users"
                # -> [('Alice', 2024), ('Bob', 2024), ...]

            Range filter across years — combine with :meth:`Between`::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.Between(
                        Builtins.Year(users.created_at), 2023, 2024
                    ),
                )
                # SELECT "users"."id" FROM "users"
                # WHERE (((EXTRACT(YEAR FROM "users"."created_at")::INTEGER))
                #        BETWEEN %s AND %s)
                # Parameters: [2023, 2024]

            Compare years with arithmetic — stays numeric because
            ``current_datatype`` is ``int``::

                next_year = Builtins.Year(users.created_at) + 1
                rows = users.get_row([users.id, next_year])
                # SELECT "users"."id",
                #        ((EXTRACT(YEAR FROM "users"."created_at")::INTEGER) + %s)
                # FROM "users"
                # Parameters: [1]

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Year('2024-03-15'),  # -> 2024
                    Builtins.Year(Builtins.Now()),  # -> current year
                ])
                # SELECT (EXTRACT(YEAR FROM %s)::INTEGER),
                #        (EXTRACT(YEAR FROM (NOW()))::INTEGER)
                # Parameters: ['2024-03-15']
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(EXTRACT(YEAR FROM CAST({sql} AS TIMESTAMP))::INTEGER)', p, int, c)

    @staticmethod
    def Month(value):
        """Extract the month number (1–12) from a date/time value.

        Generates an ``EXTRACT(MONTH FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``EXTRACT`` returns a ``NUMERIC`` by default, so the
        explicit ``::INTEGER`` cast is required to produce a plain Python
        ``int`` after fetching.

        The result is always an ``INTEGER`` in the range 1 through 12, so
        it compares correctly with numeric literals (``== 3`` rather than
        ``== '03'``, which is the common pitfall when using ``TO_CHAR``).

        Args:
            value: The date/time expression whose month is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(MONTH FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Filter by month — pick a specific month regardless of year::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row(
                    [users.id, users.created_at],
                    where=Builtins.Month(users.created_at) == 3,
                )
                # SELECT "users"."id", "users"."created_at" FROM "users"
                # WHERE ((EXTRACT(MONTH FROM "users"."created_at")::INTEGER) = %s)
                # Parameters: [3]
                # -> everything created in March, any year

            Seasonal filter — Q1 only (January through March)::

                q1 = Builtins.Between(Builtins.Month(users.created_at), 1, 3)
                rows = users.get_row(
                    [users.username],
                    where=q1,
                )
                # SELECT "users"."username" FROM "users"
                # WHERE (((EXTRACT(MONTH FROM "users"."created_at")::INTEGER))
                #        BETWEEN %s AND %s)
                # Parameters: [1, 3]

            Monthly histogram — compute in Python after fetching::

                rows = users.get_row([Builtins.Month(users.created_at)])
                from collections import Counter
                by_month = Counter(m for (m,) in rows)
                # {1: 45, 2: 38, 3: 52, ...} — a 12-bucket histogram

            Order by month — chronological within the year::

                rows = users.get_row(
                    [users.username, users.created_at],
                    order_by=Builtins.Month(users.created_at),
                )
                # SELECT "users"."username", "users"."created_at"
                # FROM "users"
                # ORDER BY (EXTRACT(MONTH FROM "users"."created_at")::INTEGER)

            Select with formatted month name — combine with ``IIf`` for
            the common "Jan / Feb / ..." display. Extend the nesting for
            all twelve months in real code::

                name = Builtins.IIf(
                    Builtins.Month(users.created_at) == 1, 'Jan',
                    Builtins.IIf(
                        Builtins.Month(users.created_at) == 2, 'Feb',
                        Builtins.IIf(
                            Builtins.Month(users.created_at) == 3, 'Mar',
                            'other',
                        ),
                    ),
                )
                rows = users.get_row([users.id, name])

            Same-month filter — users whose signup month matches their
            birth month::

                rows = users.get_row(
                    [users.username],
                    where=Builtins.Month(users.created_at)
                        == Builtins.Month(users.birth_date),
                )
                # SELECT "users"."username" FROM "users"
                # WHERE ((EXTRACT(MONTH FROM "users"."created_at")::INTEGER)
                #        = (EXTRACT(MONTH FROM "users"."birth_date")::INTEGER))

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Month('2024-03-15'),   # -> 3
                    Builtins.Month(Builtins.Now()),  # -> current month
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(MONTH FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def Day(value):
        """Extract the day of the month (1–31) from a date/time value.

        Generates an ``EXTRACT(DAY FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``EXTRACT`` returns a ``NUMERIC`` by default, so the
        explicit ``::INTEGER`` cast is required to produce a plain Python
        ``int`` after fetching.

        The result is always an ``INTEGER`` in the range 1 through 31, so
        it compares correctly with numeric literals.

        Args:
            value: The date/time expression whose day of month is
                extracted. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(DAY FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Find rows created on the first of the month::

                from Ormophine.Postgresql import Driver, Builtins

                db       = Driver("localhost", 5432, "user", "pass", "app")
                invoices = db.invoices

                rows = invoices.get_row(
                    [invoices.id, invoices.issued_at, invoices.total],
                    where=Builtins.Day(invoices.issued_at) == 1,
                )
                # SELECT "invoices"."id", "invoices"."issued_at",
                #        "invoices"."total"
                # FROM "invoices"
                # WHERE ((EXTRACT(DAY FROM "invoices"."issued_at")::INTEGER) = %s)
                # Parameters: [1]

            Billing-cycle filter — mid-month settlements, days 13 to 15::

                rows = invoices.get_row(
                    [invoices.id],
                    where=Builtins.Between(
                        Builtins.Day(invoices.issued_at), 13, 15
                    ),
                )
                # SELECT "invoices"."id" FROM "invoices"
                # WHERE (((EXTRACT(DAY FROM "invoices"."issued_at")::INTEGER))
                #        BETWEEN %s AND %s)
                # Parameters: [13, 15]

            Detect first-of-month runs — useful for cron job auditing::

                rows = invoices.get_row(
                    [invoices.id, invoices.issued_at],
                    where=Builtins.Day(invoices.issued_at) <= 2,
                )
                # SELECT "invoices"."id", "invoices"."issued_at"
                # FROM "invoices"
                # WHERE ((EXTRACT(DAY FROM "invoices"."issued_at")::INTEGER) <= %s)
                # Parameters: [2]

            Day-of-month histogram::

                rows = invoices.get_row([Builtins.Day(invoices.issued_at)])
                from collections import Counter
                by_day = Counter(d for (d,) in rows)
                # {1: 45, 2: 12, ..., 31: 3}

            End-of-month filter — days 28 through 31::

                rows = invoices.get_row(
                    [invoices.id],
                    where=Builtins.Between(
                        Builtins.Day(invoices.issued_at), 28, 31
                    ),
                )
                # -> all invoices issued in the last few days of any month

            Compare day-of-month across two date columns::

                same_day = (Builtins.Day(invoices.issued_at)
                            == Builtins.Day(invoices.due_at))
                rows = invoices.get_row([invoices.id], where=same_day)
                # SELECT "invoices"."id" FROM "invoices"
                # WHERE ((EXTRACT(DAY FROM "invoices"."issued_at")::INTEGER)
                #        = (EXTRACT(DAY FROM "invoices"."due_at")::INTEGER))

            Applied to a raw literal::

                rows = invoices.get_row([
                    Builtins.Day('2024-03-15'),   # -> 15
                    Builtins.Day(Builtins.Now()),  # -> current day
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(DAY FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def Hour(value):
        """Extract the hour (0–23) from a date/time value.

        Generates an ``EXTRACT(HOUR FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``EXTRACT`` returns a ``NUMERIC`` by default, so the
        explicit ``::INTEGER`` cast is required to produce a plain Python
        ``int`` after fetching.

        The result is always an ``INTEGER`` in the range 0 through 23,
        using the 24-hour clock. Comparisons and arithmetic work as you
        would expect in Python.

        Args:
            value: The date/time expression whose hour is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(HOUR FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Business-hours filter — signups between 9 AM and 5 PM::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                hours = Builtins.Hour(users.created_at)
                rows = users.get_row(
                    [users.id, users.created_at],
                    where=(hours >= 9) & (hours < 17),
                )
                # SELECT "users"."id", "users"."created_at" FROM "users"
                # WHERE (((EXTRACT(HOUR FROM "users"."created_at")::INTEGER) >= %s)
                #        AND ((EXTRACT(HOUR FROM "users"."created_at")::INTEGER) < %s))
                # Parameters: [9, 17]

            Time-of-day bucketing — morning / afternoon / evening / night::

                bucket = Builtins.IIf(
                    Builtins.Hour(users.created_at) < 6, 'night',
                    Builtins.IIf(
                        Builtins.Hour(users.created_at) < 12, 'morning',
                        Builtins.IIf(
                            Builtins.Hour(users.created_at) < 18, 'afternoon',
                            'evening',
                        ),
                    ),
                )
                rows = users.get_row([users.id, bucket])
                # SELECT "users"."id",
                #        (CASE WHEN ((EXTRACT(HOUR FROM "users"."created_at")::INTEGER) < %s)
                #              THEN %s
                #              ELSE (CASE WHEN ((EXTRACT(HOUR FROM "users"."created_at")::INTEGER) < %s)
                #                         THEN %s
                #                         ELSE (CASE WHEN ((EXTRACT(HOUR FROM "users"."created_at")::INTEGER) < %s)
                #                                    THEN %s
                #                                    ELSE %s END) END) END)
                # FROM "users"
                # Parameters: [6, 'night', 12, 'morning', 18, 'afternoon', 'evening']

            Hourly histogram — a 24-bucket count of signups::

                rows = users.get_row([Builtins.Hour(users.created_at)])
                from collections import Counter
                by_hour = Counter(h for (h,) in rows)
                # {0: 3, 1: 1, ..., 9: 45, 10: 52, ..., 23: 8}
                # Great for finding the quietest hour to run maintenance.

            Filter by hour range with a wrap-around — "off-hours" (22:00
            through 05:59)::

                h = Builtins.Hour(users.created_at)
                off_hours = (h >= 22) | (h < 6)
                rows = users.get_row([users.id], where=off_hours)
                # SELECT "users"."id" FROM "users"
                # WHERE (((EXTRACT(HOUR FROM "users"."created_at")::INTEGER) >= %s)
                #        OR ((EXTRACT(HOUR FROM "users"."created_at")::INTEGER) < %s))
                # Parameters: [22, 6]

            Order by hour-of-day — clusters activity by time of day
            regardless of date::

                rows = users.get_row(
                    [users.username, users.created_at],
                    order_by=Builtins.Hour(users.created_at),
                )
                # SELECT "users"."username", "users"."created_at"
                # FROM "users"
                # ORDER BY (EXTRACT(HOUR FROM "users"."created_at")::INTEGER)

            Compare hours across two columns — same-hour activity::

                same_hour = (Builtins.Hour(users.created_at)
                             == Builtins.Hour(users.last_login))
                rows = users.get_row([users.id], where=same_hour)

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.Hour('2024-03-15 09:30:42'),  # -> 9
                    Builtins.Hour(Builtins.Now()),          # -> current hour
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(HOUR FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def Minute(value):
        """Extract the minute (0–59) from a date/time value.

        Generates an ``EXTRACT(MINUTE FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``EXTRACT`` returns a ``NUMERIC`` by default, so the
        explicit ``::INTEGER`` cast is required to produce a plain Python
        ``int`` after fetching.

        The result is always an ``INTEGER`` in the range 0 through 59.

        Args:
            value: The date/time expression whose minute is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(MINUTE FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Extract the minute component of a timestamp::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "analytics")
                events = db.events

                rows = events.get_row([
                    events.id,
                    Builtins.Minute(events.occurred_at),
                ])
                # SELECT "events"."id",
                #        (EXTRACT(MINUTE FROM "events"."occurred_at")::INTEGER)
                # FROM "events"
                # -> [(1, 42), (2, 0), (3, 15), ...]

            Filter events in the last 5 minutes of an hour::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.Minute(events.occurred_at) >= 55,
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE ((EXTRACT(MINUTE FROM "events"."occurred_at")::INTEGER) >= %s)
                # Parameters: [55]

            Bucket events by 15-minute intervals — combine with
            integer division on the extracted minute::

                minute = Builtins.Minute(events.occurred_at)
                bucket = Builtins.Int(minute / 15) * 15
                rows = events.get_row([events.id, bucket])
                # SELECT "events"."id",
                #        ((CAST(((EXTRACT(MINUTE FROM "events"."occurred_at")::INTEGER) / %s)
                #                AS INTEGER)) * %s)
                # FROM "events"
                # Parameters: [15, 15]
                # -> [(1, 30), (2, 0), (3, 45), ...]

            Detect the top of the hour — a common "cleanup" filter::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Minute(events.occurred_at) == 0,
                )
                # SELECT "events"."id" FROM "events"
                # WHERE ((EXTRACT(MINUTE FROM "events"."occurred_at")::INTEGER) = %s)
                # Parameters: [0]

            Minute histogram across the hour::

                rows = events.get_row([Builtins.Minute(events.occurred_at)])
                from collections import Counter
                by_minute = Counter(m for (m,) in rows)
                # {0: 8, 1: 3, ..., 30: 22, ..., 59: 5}

            Order by minute-of-hour — groups events with the same minute
            regardless of which hour they occurred in::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    order_by=Builtins.Minute(events.occurred_at),
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # ORDER BY (EXTRACT(MINUTE FROM "events"."occurred_at")::INTEGER)

            Applied to a raw literal::

                rows = events.get_row([
                    Builtins.Minute('2024-03-15 09:30:42'),  # -> 30
                    Builtins.Minute(Builtins.Now()),          # -> current minute
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(MINUTE FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def Second(value):
        """Extract the second (0–59) from a date/time value.

        Generates an ``EXTRACT(SECOND FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``EXTRACT(SECOND ...)`` returns a ``NUMERIC`` that
        includes fractional seconds (e.g. ``42.123456`` for a
        ``TIMESTAMP(6)``). The explicit ``::INTEGER`` cast truncates the
        fractional part, giving a plain Python ``int`` in the range 0
        through 59.

        To retain the fractional seconds, cast the result to ``DOUBLE
        PRECISION`` instead — use :meth:`Builtins.Func` with
        ``'EXTRACT'``, or fetch the raw value and format in Python.

        The result is always an ``INTEGER`` in the range 0 through 59.
        PostgreSQL does not represent leap seconds, so the upper bound is
        strictly 59.

        Args:
            value: The date/time expression whose second is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(SECOND FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Extract the second component of a timestamp::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "analytics")
                events = db.events

                rows = events.get_row([
                    events.id,
                    events.occurred_at,
                    Builtins.Second(events.occurred_at),
                ])
                # SELECT "events"."id", "events"."occurred_at",
                #        (EXTRACT(SECOND FROM "events"."occurred_at")::INTEGER)
                # FROM "events"
                # -> [(1, datetime(2024, 3, 15, 9, 30, 42), 42), ...]

            Find events that landed on a round minute — second == 0::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.Second(events.occurred_at) == 0,
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE ((EXTRACT(SECOND FROM "events"."occurred_at")::INTEGER) = %s)
                # Parameters: [0]

            Detect rapid-fire activity — events within the first 5 seconds
            of each minute::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Second(events.occurred_at) < 5,
                )
                # SELECT "events"."id" FROM "events"
                # WHERE ((EXTRACT(SECOND FROM "events"."occurred_at")::INTEGER) < %s)
                # Parameters: [5]

            Reconstruct a full HH:MM:SS display from components — combine
            with :meth:`Hour`, :meth:`Minute`, and :meth:`Format`::

                hh = Builtins.Hour(events.occurred_at)
                mm = Builtins.Minute(events.occurred_at)
                ss = Builtins.Second(events.occurred_at)
                display = Builtins.Format('%s:%s:%s', hh, mm, ss)
                rows = events.get_row([events.id, display])
                # SELECT "events"."id",
                #        (FORMAT(%s,
                #                (EXTRACT(HOUR FROM "events"."occurred_at")::INTEGER),
                #                (EXTRACT(MINUTE FROM "events"."occurred_at")::INTEGER),
                #                (EXTRACT(SECOND FROM "events"."occurred_at")::INTEGER)))
                # FROM "events"
                # Parameters: ['%s:%s:%s']
                # -> [(1, '9:30:42'), (2, '9:30:43'), ...]

            Second-of-minute histogram — useful for spotting scheduling
            jitter::

                rows = events.get_row([Builtins.Second(events.occurred_at)])
                from collections import Counter
                by_second = Counter(s for (s,) in rows)
                # {0: 45, 1: 2, 2: 1, ..., 42: 3, ...}

            Compare seconds across two columns — same-second events::

                same_second = (
                    Builtins.Second(events.started_at)
                    == Builtins.Second(events.finished_at)
                )
                rows = events.get_row([events.id], where=same_second)
                # SELECT "events"."id" FROM "events"
                # WHERE ((EXTRACT(SECOND FROM "events"."started_at")::INTEGER)
                #        = (EXTRACT(SECOND FROM "events"."finished_at")::INTEGER))

            Applied to a raw literal::

                rows = events.get_row([
                    Builtins.Second('2024-03-15 09:30:42'),  # -> 42
                    Builtins.Second(Builtins.Now()),          # -> current second
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(SECOND FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def DayOfWeek(value):
        """Extract the day-of-week number using PostgreSQL's ``DOW`` convention.

        Generates an ``EXTRACT(DOW FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``EXTRACT(DOW FROM ...)`` returns a number in the
        range 0 through 6 using the following convention:

        =====  ==========
        Value  Day
        =====  ==========
        0      Sunday
        1      Monday
        2      Tuesday
        3      Wednesday
        4      Thursday
        5      Friday
        6      Saturday
        =====  ==========

        .. warning::
            This is the **PostgreSQL / POSIX** convention, not Python's.
            Python's ``date.weekday()`` returns 0 for Monday, and
            ``date.isoweekday()`` returns 1 for Monday. If you want Python
            semantics, use :meth:`Weekday` instead. If you want ISO 8601
            semantics (1 = Monday, 7 = Sunday), use :meth:`IsoWeekday`.

        The result is always an ``INTEGER`` in the range 0 through 6.

        Args:
            value: The date/time expression whose weekday is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(DOW FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Filter for Sundays — in PostgreSQL's convention, Sunday is 0::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "analytics")
                events = db.events

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.DayOfWeek(events.occurred_at) == 0,
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE ((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER) = %s)
                # Parameters: [0]

            Weekend filter — Saturday (6) or Sunday (0)::

                dow = Builtins.DayOfWeek(events.occurred_at)
                rows = events.get_row(
                    [events.id],
                    where=(dow == 0) | (dow == 6),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE (((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER) = %s)
                #        OR ((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER) = %s))
                # Parameters: [0, 6]

            Weekday-only filter — Monday through Friday (1..5)::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Between(
                        Builtins.DayOfWeek(events.occurred_at), 1, 5
                    ),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE (((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER))
                #        BETWEEN %s AND %s)
                # Parameters: [1, 5]

            Day-of-week histogram — how activity spreads across the week::

                rows = events.get_row([Builtins.DayOfWeek(events.occurred_at)])
                from collections import Counter
                by_dow = Counter(w for (w,) in rows)
                # {0: 15, 1: 42, 2: 38, ..., 6: 22}

            Order by day-of-week — groups rows by weekday, Sunday first::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    order_by=Builtins.DayOfWeek(events.occurred_at),
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # ORDER BY (EXTRACT(DOW FROM "events"."occurred_at")::INTEGER)

            Build a readable day name via ``IIf``::

                name = Builtins.IIf(
                    Builtins.DayOfWeek(events.occurred_at) == 0, 'Sun',
                    Builtins.IIf(
                        Builtins.DayOfWeek(events.occurred_at) == 1, 'Mon',
                        Builtins.IIf(
                            Builtins.DayOfWeek(events.occurred_at) == 2, 'Tue',
                            Builtins.IIf(
                                Builtins.DayOfWeek(events.occurred_at) == 3, 'Wed',
                                Builtins.IIf(
                                    Builtins.DayOfWeek(events.occurred_at) == 4, 'Thu',
                                    Builtins.IIf(
                                        Builtins.DayOfWeek(events.occurred_at) == 5, 'Fri',
                                        'Sat',
                                    ),
                                ),
                            ),
                        ),
                    ),
                )
                rows = events.get_row([events.id, name])

            Applied to a raw literal::

                rows = events.get_row([
                    Builtins.DayOfWeek('2024-03-15'),  # -> 5 (Friday)
                    Builtins.DayOfWeek(Builtins.Now()),  # -> current DOW
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(DOW FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def IsoWeekday(value):
        """Extract the ISO 8601 weekday (1 = Monday, 7 = Sunday).

        Generates an ``EXTRACT(ISODOW FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``ISODOW`` field is a native companion to ``DOW`` that
        returns the ISO 8601 weekday number directly:

        =====  ==========
        Value  Day
        =====  ==========
        1      Monday
        2      Tuesday
        3      Wednesday
        4      Thursday
        5      Friday
        6      Saturday
        7      Sunday
        =====  ==========

        This matches Python's ``datetime.date.isoweekday()`` method and
        the ISO 8601 standard. Because PostgreSQL provides ``ISODOW`` as a
        first-class field, this helper is a direct extraction — no modulo
        arithmetic is involved, unlike the SQLite backend's equivalent.

        Use this method when you want an unambiguous 1-based weekday
        number. ISO 8601 uses this convention throughout its date and
        duration standards, so it is the "neutral" choice when talking to
        systems that have no Python or PostgreSQL heritage.

        Args:
            value: The date/time expression whose ISO weekday is
                extracted. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(ISODOW FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Filter for Mondays — ``== 1`` in ISO convention::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "analytics")
                events = db.events

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.IsoWeekday(events.occurred_at) == 1,
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE ((EXTRACT(ISODOW FROM "events"."occurred_at")::INTEGER) = %s)
                # Parameters: [1]

            Weekend filter — ISO 6 (Saturday) or 7 (Sunday)::

                dow = Builtins.IsoWeekday(events.occurred_at)
                rows = events.get_row(
                    [events.id],
                    where=(dow == 6) | (dow == 7),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE (((EXTRACT(ISODOW FROM "events"."occurred_at")::INTEGER) = %s)
                #        OR ((EXTRACT(ISODOW FROM "events"."occurred_at")::INTEGER) = %s))
                # Parameters: [6, 7]

            Weekday-only filter — Monday through Friday (1..5)::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Between(
                        Builtins.IsoWeekday(events.occurred_at), 1, 5
                    ),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE (((EXTRACT(ISODOW FROM "events"."occurred_at")::INTEGER))
                #        BETWEEN %s AND %s)
                # Parameters: [1, 5]

            ISO weekday histogram — 1 through 7 in ISO order::

                rows = events.get_row([Builtins.IsoWeekday(events.occurred_at)])
                from collections import Counter
                by_dow = Counter(w for (w,) in rows)
                # {1: 42, 2: 38, 3: 40, 4: 45, 5: 44, 6: 15, 7: 22}
                # Monday through Sunday, 1-based

            Build a readable day name via ``IIf`` — ISO order::

                name = Builtins.IIf(
                    Builtins.IsoWeekday(events.occurred_at) == 1, 'Mon',
                    Builtins.IIf(
                        Builtins.IsoWeekday(events.occurred_at) == 2, 'Tue',
                        Builtins.IIf(
                            Builtins.IsoWeekday(events.occurred_at) == 3, 'Wed',
                            Builtins.IIf(
                                Builtins.IsoWeekday(events.occurred_at) == 4, 'Thu',
                                Builtins.IIf(
                                    Builtins.IsoWeekday(events.occurred_at) == 5, 'Fri',
                                    Builtins.IIf(
                                        Builtins.IsoWeekday(events.occurred_at) == 6, 'Sat',
                                        'Sun',
                                    ),
                                ),
                            ),
                        ),
                    ),
                )
                rows = events.get_row([events.id, name])

            Order by ISO weekday::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    order_by=Builtins.IsoWeekday(events.occurred_at),
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # ORDER BY (EXTRACT(ISODOW FROM "events"."occurred_at")::INTEGER)

            Applied to a raw literal::

                rows = events.get_row([
                    Builtins.IsoWeekday('2024-03-15'),  # -> 5 (Friday)
                    Builtins.IsoWeekday(Builtins.Now()),  # -> current ISO DOW
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(ISODOW FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def Weekday(value):
        """Extract the Python-style weekday (0 = Monday, 6 = Sunday).

        Generates an expression that converts PostgreSQL's Sunday-based
        ``DOW`` convention into Python's Monday-based weekday convention:

        .. code-block:: sql

            (EXTRACT(DOW FROM <expr>)::INTEGER + 6) % 7

        This matches Python's ``datetime.date.weekday()`` method exactly:

        =====  ==========
        Value  Day
        =====  ==========
        0      Monday
        1      Tuesday
        2      Wednesday
        3      Thursday
        4      Friday
        5      Saturday
        6      Sunday
        =====  ==========

        Use this method when you want to write conditions that read the
        same as Python code. Use :meth:`DayOfWeek` when you need
        PostgreSQL's native convention, and :meth:`IsoWeekday` when you
        need ISO 8601 (1 = Monday, 7 = Sunday).

        The result is always an ``INTEGER`` in the range 0 through 6.

        Args:
            value: The date/time expression whose Python-style weekday is
                extracted. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``((EXTRACT(DOW FROM <expr>)::INTEGER + 6) % 7)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Filter for Mondays — ``== 0`` matches Python's convention::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "analytics")
                events = db.events

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.Weekday(events.occurred_at) == 0,
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE (((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER + %s) % %s) = %s)
                # Parameters: [6, 7, 0]

            Weekend filter — Saturday (5) or Sunday (6):::

                dow = Builtins.Weekday(events.occurred_at)
                rows = events.get_row(
                    [events.id],
                    where=(dow == 5) | (dow == 6),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE ((((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER + %s) % %s) = %s)
                #        OR (((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER + %s) % %s) = %s))
                # Parameters: [6, 7, 5, 6, 7, 6]

            Weekday-only filter — Monday through Friday (0..4)::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Between(
                        Builtins.Weekday(events.occurred_at), 0, 4
                    ),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE ((((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER + %s) % %s))
                #        BETWEEN %s AND %s)
                # Parameters: [6, 7, 0, 4]

            Python-style weekday histogram::

                rows = events.get_row([Builtins.Weekday(events.occurred_at)])
                from collections import Counter
                by_dow = Counter(w for (w,) in rows)
                # {0: 42, 1: 38, 2: 40, 3: 45, 4: 44, 5: 15, 6: 22}
                # Monday through Sunday, matching Python's weekday() order

            Same-weekday filter — events that happened on the same
            weekday as today::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Weekday(events.occurred_at)
                        == Builtins.Weekday(Builtins.Now()),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE (((EXTRACT(DOW FROM "events"."occurred_at")::INTEGER + %s) % %s)
                #        = ((EXTRACT(DOW FROM (NOW()))::INTEGER + %s) % %s))
                # Parameters: [6, 7, 6, 7]

            Build a readable day name via ``IIf`` — the list is now in
            Python order::

                name = Builtins.IIf(
                    Builtins.Weekday(events.occurred_at) == 0, 'Mon',
                    Builtins.IIf(
                        Builtins.Weekday(events.occurred_at) == 1, 'Tue',
                        Builtins.IIf(
                            Builtins.Weekday(events.occurred_at) == 2, 'Wed',
                            Builtins.IIf(
                                Builtins.Weekday(events.occurred_at) == 3, 'Thu',
                                Builtins.IIf(
                                    Builtins.Weekday(events.occurred_at) == 4, 'Fri',
                                    Builtins.IIf(
                                        Builtins.Weekday(events.occurred_at) == 5, 'Sat',
                                        'Sun',
                                    ),
                                ),
                            ),
                        ),
                    ),
                )
                rows = events.get_row([events.id, name])

            Applied to a raw literal::

                rows = events.get_row([
                    Builtins.Weekday('2024-03-15'),  # -> 4 (Friday)
                    Builtins.Weekday(Builtins.Now()),  # -> current Python DOW
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'((EXTRACT(DOW FROM {sql})::INTEGER + 6) % 7)', p, int, c)

    @staticmethod
    def DayOfYear(value):
        """Extract the day of the year (1–366) from a date/time value.

        Generates an ``EXTRACT(DOY FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``DOY`` field returns the ordinal day of the year,
        1-based. The explicit ``::INTEGER`` cast is required to produce a
        plain Python ``int`` after fetching.

        The result is always an ``INTEGER`` in the range 1 through 366.
        Leap years produce 366; non-leap years produce at most 365.
        PostgreSQL determines leap-ness from the actual input date, so
        this helper is completely accurate across century boundaries.

        Args:
            value: The date/time expression whose ordinal day is extracted.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(DOY FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Ordinal day for every row — the day of the year, 1-based::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "analytics")
                events = db.events

                rows = events.get_row([
                    events.id,
                    events.occurred_at,
                    Builtins.DayOfYear(events.occurred_at),
                ])
                # SELECT "events"."id", "events"."occurred_at",
                #        (EXTRACT(DOY FROM "events"."occurred_at")::INTEGER)
                # FROM "events"
                # -> [(1, date(2024, 3, 15), 75), ...]

            Day-of-year histogram — a true 365-bucket distribution of
            activity::

                rows = events.get_row([Builtins.DayOfYear(events.occurred_at)])
                from collections import Counter
                by_doy = Counter(j for (j,) in rows)
                # {1: 12, 2: 8, ..., 75: 22, ..., 366: 4}

            Same-weekday-anywhere comparison — day-of-year modulo 7 groups
            rows into the same weekday::

                same_weekday = Builtins.DayOfYear(events.occurred_at) % 7
                rows = events.get_row([events.id, same_weekday])
                # SELECT "events"."id",
                #        ((EXTRACT(DOY FROM "events"."occurred_at")::INTEGER) % %s)
                # FROM "events"
                # Parameters: [7]

            Filter for the first quarter (days 1 through 91)::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.Between(
                        Builtins.DayOfYear(events.occurred_at), 1, 91
                    ),
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE (((EXTRACT(DOY FROM "events"."occurred_at")::INTEGER))
                #        BETWEEN %s AND %s)
                # Parameters: [1, 91]

            Year-relative comparison — find all events on the same
            calendar day across different years::

                rows = events.get_row(
                    [events.occurred_at],
                    where=Builtins.DayOfYear(events.occurred_at) == 75,
                )
                # SELECT "events"."occurred_at" FROM "events"
                # WHERE ((EXTRACT(DOY FROM "events"."occurred_at")::INTEGER) = %s)
                # Parameters: [75]
                # -> every March 15 (or March 14 in leap years) regardless
                #    of the year, since DOY shifts by one after Feb 28 in
                #    leap years.

            Sort by position within the year — combines nicely with
            :meth:`Year`::

                rows = events.get_row(
                    [events.occurred_at],
                    order_by=Builtins.DayOfYear(events.occurred_at),
                )
                # SELECT "events"."occurred_at" FROM "events"
                # ORDER BY (EXTRACT(DOY FROM "events"."occurred_at")::INTEGER)

            Applied to a raw literal::

                rows = events.get_row([
                    Builtins.DayOfYear('2024-03-15'),  # -> 75
                    Builtins.DayOfYear('2024-12-31'),  # -> 366 (2024 is leap)
                ])
                # SELECT (EXTRACT(DOY FROM %s)::INTEGER),
                #        (EXTRACT(DOY FROM %s)::INTEGER)
                # Parameters: ['2024-03-15', '2024-12-31']
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(DOY FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def WeekOfYear(value):
        """Extract the ISO 8601 week number (1–53) from a date/time value.

        Generates an ``EXTRACT(WEEK FROM <expr>)::INTEGER`` expression.
        PostgreSQL's ``WEEK`` field returns the ISO 8601 week number —
        **not** the US-style ``%U`` or ``%W`` conventions that SQLite uses.
        The differences are important:

        - **Weeks start on Monday.**
        - **Week 1 is the week containing the first Thursday of the year**
          (equivalently, the week containing January 4th).
        - **The range is 1 through 53** — never 0.

        This means the first few days of January might belong to week 52
        or 53 of the *previous* year, and the last few days of December
        might belong to week 1 of the *next* year. If you need the year
        that a week belongs to, use ``EXTRACT(ISOYEAR FROM ...)`` — the
        ORM exposes this via :meth:`Builtins.Func`:
        ``Func('EXTRACT', col)`` combined with a manual expression, or by
        chaining the year separately.

        The result is always an ``INTEGER`` in the range 1 through 53.

        Args:
            value: The date/time expression whose week-of-year is
                extracted. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(WEEK FROM <expr>)::INTEGER)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Week number for every row::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "analytics")
                events = db.events

                rows = events.get_row([
                    events.id,
                    events.occurred_at,
                    Builtins.WeekOfYear(events.occurred_at),
                ])
                # SELECT "events"."id", "events"."occurred_at",
                #        (EXTRACT(WEEK FROM "events"."occurred_at")::INTEGER)
                # FROM "events"
                # -> [(1, date(2024, 3, 15), 11), ...]

            Weekly histogram — count events per week::

                rows = events.get_row([Builtins.WeekOfYear(events.occurred_at)])
                from collections import Counter
                by_week = Counter(w for (w,) in rows)
                # {1: 45, 2: 52, ..., 11: 22, ..., 53: 1}

            Filter to a specific week — e.g. week 11 of the year::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    where=Builtins.WeekOfYear(events.occurred_at) == 11,
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # WHERE ((EXTRACT(WEEK FROM "events"."occurred_at")::INTEGER) = %s)
                # Parameters: [11]

            Filter to the first quarter — weeks 1 through 13::

                rows = events.get_row(
                    [events.id],
                    where=Builtins.Between(
                        Builtins.WeekOfYear(events.occurred_at), 1, 13
                    ),
                )
                # SELECT "events"."id" FROM "events"
                # WHERE (((EXTRACT(WEEK FROM "events"."occurred_at")::INTEGER))
                #        BETWEEN %s AND %s)
                # Parameters: [1, 13]

            Build a year-week key for reporting — the ISO-correct year is
            ``ISOYEAR``, not the calendar year. Because ``ISOYEAR`` is not
            exposed by a dedicated helper, this example uses the calendar
            year, which is correct for most (but not all) dates::

                key = Builtins.Format(
                    '%s-W%s',
                    Builtins.Year(events.occurred_at),
                    Builtins.WeekOfYear(events.occurred_at),
                )
                rows = events.get_row([events.id, key])
                # SELECT "events"."id",
                #        (FORMAT(%s,
                #                (EXTRACT(YEAR FROM "events"."occurred_at")::INTEGER),
                #                (EXTRACT(WEEK FROM "events"."occurred_at")::INTEGER)))
                # FROM "events"
                # Parameters: ['%s-W%s']
                # -> [(1, '2024-W11'), (2, '2024-W11'), (3, '2024-W12'), ...]

            Order by week — chronological grouping within the year::

                rows = events.get_row(
                    [events.id, events.occurred_at],
                    order_by=Builtins.WeekOfYear(events.occurred_at),
                )
                # SELECT "events"."id", "events"."occurred_at"
                # FROM "events"
                # ORDER BY (EXTRACT(WEEK FROM "events"."occurred_at")::INTEGER)

            Applied to a raw literal — note that ISO semantics may put
            early January in the previous year's week::

                rows = events.get_row([
                    Builtins.WeekOfYear('2024-01-01'),  # -> 1 (Mon 2024-01-01 starts ISO week 1)
                    Builtins.WeekOfYear('2023-01-01'),  # -> 52 (Sun, belongs to 2022's last week)
                    Builtins.WeekOfYear('2024-03-15'),  # -> 11
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(
            f'(EXTRACT(WEEK FROM {sql})::INTEGER)', p, int, c)

    @staticmethod
    def Now(_=None):
        """Return the current moment as a ``TIMESTAMPTZ`` value.

        Generates a ``NOW()`` expression. PostgreSQL's ``NOW()`` returns
        the start time of the current transaction as a ``TIMESTAMP WITH
        TIME ZONE``. This means every call to ``Now()`` within a single
        transaction returns the same value — even if the wall-clock time
        has advanced during the transaction. This is the SQL standard
        behaviour and is usually what you want for consistency.

        To get the actual wall-clock time that changes within a
        transaction, use ``CLOCK_TIMESTAMP()`` via
        :meth:`Builtins.Func`: ``Func('CLOCK_TIMESTAMP', 'x')`` (the
        argument is ignored). The difference matters for long-running
        transactions and audit logs.

        The result is a real Python ``datetime.datetime`` with a
        ``tzinfo`` (usually UTC) after fetching.

        The optional positional argument ``_`` exists only to make the
        signature uniform with the other date/time helpers and to allow
        the callable to be passed to APIs that expect a one-argument
        function. It is ignored.

        Args:
            _: Ignored. Any value passed is discarded; the SQL emitted is
                always ``NOW()``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(NOW())`` and whose ``current_datatype`` is ``str``. The
            parameter list is always empty.

        Example:
            The current timestamp as a SELECT column::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([Builtins.Now()])
                # SELECT (NOW()) FROM "users"
                # -> [(datetime(2024, 3, 15, 14, 30, 42,
                #               tzinfo=datetime.timezone.utc),)]
                # Same value for every row, because PostgreSQL evaluates
                # NOW() once per transaction, not per row.

            Compute account age in days — combine with
            :meth:`DateDiffDays`::

                age = Builtins.DateDiffDays(Builtins.Now(), users.created_at)
                rows = users.get_row([users.username, age])
                # SELECT "users"."username",
                #        (CAST(EXTRACT(EPOCH FROM
                #                       (CAST((NOW()) AS TIMESTAMP)
                #                        - CAST("users"."created_at" AS TIMESTAMP)))
                #              / %s
                #              AS INTEGER))
                # FROM "users"
                # Parameters: [86400]

            Compute account age in a human-readable format — combine with
            :meth:`Timediff`::

                age_str = Builtins.Timediff(Builtins.Now(), users.created_at)
                rows = users.get_row([users.username, age_str])
                # -> [('Alice', '2 days 05:29:18'), ...]

            Filter rows updated in the last hour — combine with
            :meth:`DateDiffSeconds`::

                rows = users.get_row(
                    [users.username, users.updated_at],
                    where=Builtins.DateDiffSeconds(
                        Builtins.Now(), users.updated_at
                    ) < 3600,
                )
                # SELECT "users"."username", "users"."updated_at"
                # FROM "users"
                # WHERE ((CAST(EXTRACT(EPOCH FROM
                #                       (CAST((NOW()) AS TIMESTAMP)
                #                        - CAST("users"."updated_at" AS TIMESTAMP)))
                #              AS BIGINT)) < %s)
                # Parameters: [3600]

            Store the current timestamp during an UPDATE::

                users.update(
                    update={users.last_seen: Builtins.Now()},
                    where=users.id == 42,
                )
                # UPDATE "users" SET "last_seen" = (NOW())
                # WHERE ("users"."id" = %s);
                # Parameters: [42]

            Compare two ``Now()`` calls to see that both evaluate to the
            same moment within a single transaction — useful for
            validating cache keys::

                rows = users.get_row([
                    Builtins.Now(),
                    Builtins.Now(),
                ])
                # SELECT (NOW()), (NOW()) FROM "users"
                # Both columns are identical.

            Local-time variant — pass ``'localtime'`` at the SQL level
            using :meth:`Func` — PostgreSQL does not support the SQLite
            ``'localtime'`` modifier, so this must be done in Python::

                import datetime
                # Fetch as UTC, convert in Python:
                rows = users.get_row([Builtins.Now()])
                local = rows[0][0].astimezone()  # to the local timezone
        """
        return Builtins._make('(NOW())', [], str, Builtins._NullCol)
    
    @staticmethod
    def Today(_=None):
        """Return the current date as a ``DATE`` value (from ``CURRENT_DATE``).

        Generates a ``CURRENT_DATE`` expression, which PostgreSQL returns
        as a ``DATE`` value — the calendar date in the session's time
        zone (usually the server's, unless ``SET TIME ZONE`` was issued).
        This is the date-only counterpart of :meth:`Now`.

        Unlike SQLite, PostgreSQL does not require a string keyword like
        ``'now'`` — ``CURRENT_DATE`` is a true SQL keyword that yields a
        native ``DATE`` type. After fetching, psycopg delivers the value
        as a Python ``datetime.date`` object, ready for direct comparison
        with other ``date`` values or for arithmetic.

        The optional positional argument ``_`` exists only to make the
        signature uniform with the other date/time helpers and to allow
        the callable to be passed to APIs that expect a one-argument
        function. It is ignored.

        Args:
            _: Ignored. Any value passed is discarded; the SQL emitted is
                always ``CURRENT_DATE``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CURRENT_DATE)`` and whose ``current_datatype`` is ``str``.
            The parameter list is always empty.

        Example:
            The current date as a SELECT column::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([Builtins.Today()])
                # SELECT (CURRENT_DATE) FROM "users"
                # -> [(date(2024, 3, 15),)]

            Find users who signed up today — cast the timestamp column
            to DATE and compare to the current date::

                rows = users.get_row(
                    [users.username, users.created_at],
                    where=Builtins.Date(users.created_at) == Builtins.Today(),
                )
                # SELECT "users"."username", "users"."created_at"
                # FROM "users"
                # WHERE ((CAST("users"."created_at" AS DATE)) = (CURRENT_DATE))

            Same-day boolean flag via ``IIf``::

                is_today = Builtins.IIf(
                    Builtins.Date(users.created_at) == Builtins.Today(),
                    'new',
                    'old',
                )
                rows = users.get_row([users.username, is_today])
                # SELECT "users"."username",
                #        (CASE WHEN ((CAST("users"."created_at" AS DATE))
                #                    = (CURRENT_DATE))
                #              THEN %s ELSE %s END)
                # FROM "users"
                # Parameters: ['new', 'old']

            Store today's date in an UPDATE — most useful against a
            column declared as ``DATE``::

                users.update(
                    update={users.last_active_date: Builtins.Today()},
                    where=users.id == 42,
                )
                # UPDATE "users" SET "last_active_date" = (CURRENT_DATE)
                # WHERE ("users"."id" = %s);
                # Parameters: [42]

            Yesterday's date — chain with a modifier via
            :meth:`DateAdd`::

                yesterday = Builtins.DateAdd(Builtins.Today(), '-1 day')
                rows = users.get_row([yesterday])
                # SELECT (CAST((CAST((CURRENT_DATE) AS TIMESTAMP)
                #               + %s::interval) AS DATE))
                # FROM "users"
                # Parameters: ['-1 day']
                # -> [(date(2024, 3, 14),)]

            First day of the current month — useful for month-to-date
            reporting. PostgreSQL has no ``'start of month'`` modifier,
            so this uses ``DATE_TRUNC`` via :meth:`Func`::

                month_start = Builtins.Func('DATE_TRUNC', users.created_at)
                # NOTE: DATE_TRUNC requires a unit argument. Build it
                # manually via Column expressions or a raw query.
                # The idiomatic PostgreSQL form is:
                #   DATE_TRUNC('month', CURRENT_DATE)::DATE
                # which is not expressible through Func alone.

            Count users who signed up today — via a conditional SUM::

                today_count = Builtins.Sum(
                    Builtins.IIf(
                        Builtins.Date(users.created_at) == Builtins.Today(),
                        1,
                        0,
                    )
                )
                rows = users.get_row([today_count])
                # SELECT (SUM((CASE WHEN ((CAST("users"."created_at" AS DATE))
                #                        = (CURRENT_DATE))
                #                   THEN %s ELSE %s END)))
                # FROM "users"
                # Parameters: [1, 0]
                # -> [(8,)] if 8 users signed up today

            Compare against a fixed date literal::

                rows = users.get_row(
                    [users.username],
                    where=Builtins.Today() > Builtins.Date('2024-01-01'),
                )
                # SELECT "users"."username" FROM "users"
                # WHERE ((CURRENT_DATE) > (CAST(%s AS DATE)))
                # Parameters: ['2024-01-01']
        """
        return Builtins._make('(CURRENT_DATE)', [], str, Builtins._NullCol)

    @staticmethod
    def UnixNow(_=None):
        """Return the current moment as an integer Unix timestamp.

        Generates an ``EXTRACT(EPOCH FROM NOW())::BIGINT`` expression.
        The result is an INTEGER equal to the number of whole seconds
        since the Unix epoch (midnight UTC on 1 January 1970), matching
        Python's ``int(time.time())`` — except that PostgreSQL's ``NOW()``
        returns the *transaction start* time rather than the wall-clock
        moment, so the value is consistent across all calls within a
        single transaction.

        This is the numeric counterpart of :meth:`Now`. Because the
        result is a plain integer, it is the best choice for cache keys,
        request IDs, expiry timestamps, and any arithmetic that needs
        sub-day precision without floating-point round-off. It also
        compares directly with `int` values in Python without conversion
        after fetching.

        The optional positional argument ``_`` exists only to make the
        signature uniform with the other date/time helpers; it is
        ignored.

        Args:
            _: Ignored. Any value passed is discarded; the SQL emitted is
                always ``EXTRACT(EPOCH FROM NOW())::BIGINT``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(EPOCH FROM NOW())::BIGINT)`` and whose
            ``current_datatype`` is ``int``. The parameter list is always
            empty.

        Example:
            Current Unix timestamp as a SELECT column::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([Builtins.UnixNow()])
                # SELECT (EXTRACT(EPOCH FROM NOW())::BIGINT) FROM "users"
                # -> [(1710513042,)]

            Compute account age in seconds without any date parsing::

                age_secs = (Builtins.UnixNow()
                            - Builtins.UnixEpoch(users.created_at))
                rows = users.get_row([users.username, age_secs])
                # SELECT "users"."username",
                #        ((EXTRACT(EPOCH FROM NOW())::BIGINT)
                #         - (EXTRACT(EPOCH FROM "users"."created_at")::BIGINT))
                # FROM "users"
                # -> [('Alice', 6394842), ('Bob', 1036800), ...]

            Filter users who signed up in the last 24 hours::

                rows = users.get_row(
                    [users.username, users.created_at],
                    where=Builtins.UnixNow()
                        - Builtins.UnixEpoch(users.created_at) < 86400,
                )
                # SELECT "users"."username", "users"."created_at"
                # FROM "users"
                # WHERE (((EXTRACT(EPOCH FROM NOW())::BIGINT)
                #         - (EXTRACT(EPOCH FROM "users"."created_at")::BIGINT))
                #        < %s)
                # Parameters: [86400]

            Generate a per-request cache key — combine with a user id::

                cache_key = Builtins.Format(
                    'user:%s:%s',
                    users.id,
                    Builtins.UnixNow(),
                )
                rows = users.get_row([cache_key])
                # SELECT (FORMAT(%s, "users"."id",
                #                (EXTRACT(EPOCH FROM NOW())::BIGINT)))
                # FROM "users"
                # Parameters: ['user:%s:%s']
                # -> [('user:42:1710513042',), ...]

            Store the current epoch in an UPDATE — useful for
            portability-friendly integer columns::

                users.update(
                    update={users.last_login_epoch: Builtins.UnixNow()},
                    where=users.id == 42,
                )
                # UPDATE "users"
                # SET "last_login_epoch" = (EXTRACT(EPOCH FROM NOW())::BIGINT)
                # WHERE ("users"."id" = %s);
                # Parameters: [42]

            Compute a future expiry — a session token valid for 1 hour::

                expires_at = Builtins.UnixNow() + 3600
                rows = users.get_row([users.id, expires_at])
                # SELECT "users"."id",
                #        ((EXTRACT(EPOCH FROM NOW())::BIGINT) + %s)
                # FROM "users"
                # Parameters: [3600]
                # -> [(1, 1710516642), ...]

            Set a session expiry during an UPDATE, then check it with a
            plain integer comparison::

                users.update(
                    update={users.token_expires: Builtins.UnixNow() + 3600},
                    where=users.id == 42,
                )

                # Later, filter expired sessions:
                import time
                now_epoch = int(time.time())
                expired = users.get_row(
                    [users.id],
                    where=users.token_expires < now_epoch,
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ("users"."token_expires" < %s)
                # Parameters: [1710516642]
        """
        return Builtins._make(
            '(EXTRACT(EPOCH FROM NOW())::BIGINT)', [], int, Builtins._NullCol)

    @staticmethod
    def UnixEpoch(value):
        """Convert a date/time value to a Unix timestamp.

        Generates an ``EXTRACT(EPOCH FROM <expr>)::BIGINT`` expression.
        The result is an INTEGER equal to the number of whole seconds
        since the Unix epoch (midnight UTC on 1 January 1970). The
        explicit ``::BIGINT`` cast ensures the value arrives in Python as
        a plain ``int`` rather than a ``decimal.Decimal`` (which is what
        ``EXTRACT`` returns by default) and truncates any fractional
        seconds.

        This works against any input PostgreSQL can interpret as a
        timestamp — a ``TIMESTAMP`` column, a ``TIMESTAMPTZ`` column, an
        ISO 8601 text literal, or the result of another builtin like
        :meth:`Now`. For ``TIMESTAMPTZ``, the time zone is normalised to
        UTC automatically; for plain ``TIMESTAMP``, PostgreSQL treats the
        value as UTC, which is usually what you want for storage.

        Args:
            value: The date/time expression to convert. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string — bound as a ``%s`` placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(EPOCH FROM <expr>)::BIGINT)`` and whose
            ``current_datatype`` is always ``int``.

        Example:
            Display the raw epoch value alongside a timestamp::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    users.created_at,
                    Builtins.UnixEpoch(users.created_at),
                ])
                # SELECT "users"."created_at",
                #        (EXTRACT(EPOCH FROM "users"."created_at")::BIGINT)
                # FROM "users"
                # -> [(datetime(2024, 3, 15, 9, 30, 42), 1710495042), ...]

            Compute elapsed seconds since signup::

                elapsed = (Builtins.UnixNow()
                           - Builtins.UnixEpoch(users.created_at))
                rows = users.get_row([users.username, elapsed])
                # SELECT "users"."username",
                #        ((EXTRACT(EPOCH FROM NOW())::BIGINT)
                #         - (EXTRACT(EPOCH FROM "users"."created_at")::BIGINT))
                # FROM "users"

            Elapsed days — divide by 86400::

                days = ((Builtins.UnixNow()
                         - Builtins.UnixEpoch(users.created_at))
                        / 86400)
                rows = users.get_row([users.username, days])
                # SELECT "users"."username",
                #        (((EXTRACT(EPOCH FROM NOW())::BIGINT)
                #          - (EXTRACT(EPOCH FROM "users"."created_at")::BIGINT))
                #         / %s)
                # FROM "users"
                # Parameters: [86400]
                # Note: integer division in PostgreSQL truncates — for a
                # fractional day count use Builtins.Float on one operand.

            Filter by an absolute cutoff timestamp::

                cutoff = 1704067200   # 2024-01-01 00:00:00 UTC
                rows = users.get_row(
                    [users.username],
                    where=Builtins.UnixEpoch(users.created_at) > cutoff,
                )
                # SELECT "users"."username" FROM "users"
                # WHERE ((EXTRACT(EPOCH FROM "users"."created_at")::BIGINT) > %s)
                # Parameters: [1704067200]

            Store a timestamp as an integer during an UPDATE — useful
            when a column is declared INTEGER for portability::

                users.update(
                    update={users.last_seen_epoch: Builtins.UnixNow()},
                    where=users.id == 42,
                )

            Sort by absolute moment::

                rows = users.get_row(
                    [users.username],
                    order_by=Builtins.UnixEpoch(users.created_at),
                )
                # SELECT "users"."username" FROM "users"
                # ORDER BY (EXTRACT(EPOCH FROM "users"."created_at")::BIGINT)

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.UnixEpoch('2024-03-15 00:00:00'),  # -> 1710460800
                    Builtins.UnixEpoch('1970-01-01 00:00:00'),  # -> 0
                ])
                # SELECT (EXTRACT(EPOCH FROM %s)::BIGINT),
                #        (EXTRACT(EPOCH FROM %s)::BIGINT)
                # Parameters: ['2024-03-15 00:00:00', '1970-01-01 00:00:00']
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(EXTRACT(EPOCH FROM CAST({sql} AS TIMESTAMP))::BIGINT)',p, int, c,)
    
    @staticmethod
    def JulianDay(value):
        """Convert a date/time value to its Julian day number.

        Generates an ``EXTRACT(JULIAN FROM <expr>)`` expression.
        PostgreSQL returns the value as a ``NUMERIC`` representing days
        since noon UTC on 24 November 4714 BC in the proleptic Gregorian
        calendar — the same numbering astronomers use and the same
        convention as SQLite's ``julianday()``.

        Because the result is a plain number, it is ideal for arithmetic
        that spans days, months, or years. Subtracting two Julian day
        numbers yields the number of days between two moments, with a
        fractional part for the hours. The Julian day number changes at
        **noon** UTC, not midnight, so fractional values represent the
        fraction of the day since the last noon boundary.

        Args:
            value: The date/time expression to convert. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(EXTRACT(JULIAN FROM <expr>))`` and whose
            ``current_datatype`` is always ``float``. PostgreSQL returns
            a NUMERIC, but the ORM declares ``float`` so downstream
            arithmetic stays in the floating-point domain.

        Example:
            Inspect the underlying numeric value of a timestamp::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    users.created_at,
                    Builtins.JulianDay(users.created_at),
                ])
                # SELECT "users"."created_at",
                #        (EXTRACT(JULIAN FROM "users"."created_at"))
                # FROM "users"
                # -> [(datetime(2024, 3, 15, 9, 30, 42), 2460385.89...), ...]

            Day count between two dates — the classic
            ``julianday(b) - julianday(a)`` idiom::

                days_between = (
                    Builtins.JulianDay(users.created_at)
                    - Builtins.JulianDay('2024-01-01')
                )
                rows = users.get_row([
                    users.username,
                    days_between,
                ])
                # SELECT "users"."username",
                #        ((EXTRACT(JULIAN FROM "users"."created_at"))
                #         - (EXTRACT(JULIAN FROM %s)))
                # FROM "users"
                # Parameters: ['2024-01-01']
                # -> [('Alice', 74.396...), ('Bob', 12.184...), ...]

            Whole-day version — cast to INTEGER to drop the fractional
            part. See :meth:`DateDiffDays` for a ready-made helper::

                whole_days = Builtins.Int(
                    Builtins.JulianDay(users.created_at)
                    - Builtins.JulianDay('2024-01-01')
                )
                # -> [('Alice', 74), ('Bob', 12), ...]

            Fractional hours since an event::

                hours_since = (
                    Builtins.JulianDay(Builtins.Now())
                    - Builtins.JulianDay(users.last_seen)
                ) * 24
                rows = users.get_row([users.username, hours_since])
                # SELECT "users"."username",
                #        (((EXTRACT(JULIAN FROM (NOW())))
                #          - (EXTRACT(JULIAN FROM "users"."last_seen"))) * %s)
                # FROM "users"
                # Parameters: [24]

            Sort by actual moment in time — Julian day numbers sort
            identically to the underlying timestamps::

                rows = users.get_row(
                    [users.username, users.created_at],
                    order_by=Builtins.JulianDay(users.created_at),
                )
                # SELECT "users"."username", "users"."created_at"
                # FROM "users"
                # ORDER BY (EXTRACT(JULIAN FROM "users"."created_at"))

            Compare against a threshold expressed in days::

                rows = users.get_row(
                    [users.username],
                    where=Builtins.JulianDay(Builtins.Now())
                        - Builtins.JulianDay(users.created_at) < 7,
                )
                # -> users who signed up in the last week

            Applied to a raw literal::

                rows = users.get_row([
                    Builtins.JulianDay('2024-03-15'),   # -> 2460384.5
                    Builtins.JulianDay(Builtins.Now()),  # -> current Julian day
                ])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(EXTRACT(JULIAN FROM {sql}))', p, float, c)

    @staticmethod
    def Strftime(fmt, value):
        """Format a date/time value according to a template string.

        Generates a ``TO_CHAR(<expr>, <fmt>)`` expression. This is the SQL
        analogue of Python's ``datetime.strftime()`` method — but
        PostgreSQL uses its own template syntax, which is **not**
        interchangeable with Python's or SQLite's:

        ==================  =============  =================
        Meaning             Python/SQLite  PostgreSQL
        ==================  =============  =================
        Four-digit year     %Y             YYYY
        Two-digit year      %y             YY
        Month number        %m             MM
        Month name          %B             Month
        Abbreviated month   %b             Mon
        Day of month        %d             DD
        Day name            %A             Day
        Abbreviated day     %a             Dy
        Hour (24-hour)      %H             HH24
        Hour (12-hour)      %I             HH12
        Minute              %M             MI
        Second              %S             SS
        Millisecond         —              MS
        Microsecond         —              US
        AM/PM               %p             AM
        Literal percent     %%             (no escape)
        ==================  =============  =================

        The most common pitfall is that ``MI`` is minutes and ``MM`` is
        months — the reverse of Python where ``%m`` is month and ``%M`` is
        minute. Another pitfall is that literal text in the format string
        must be quoted with double quotes (e.g. ``'YYYY" Q"Q'`` for
        ``2024 Q1``), because unquoted letters are treated as templates.

        The result is always a ``TEXT`` string, so ``+`` chained after it
        produces concatenation (``||``) rather than arithmetic addition.

        Args:
            fmt: The format template. Bound as a ``%s`` parameter, so it
                can be user-supplied safely — though for full safety you
                should validate the template against an allowlist. See
                the table above for the token mapping.

            value: The date/time expression to format. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(TO_CHAR(<expr>, %s))`` and whose ``current_datatype`` is
            ``str``. Parameters are ``value's_parameters + [fmt]``.

        Example:
            Format a timestamp as ``YYYY-MM-DD``::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    users.id,
                    Builtins.Strftime('YYYY-MM-DD', users.created_at),
                ])
                # SELECT "users"."id",
                #        (TO_CHAR("users"."created_at", %s))
                # FROM "users"
                # Parameters: ['YYYY-MM-DD']
                # -> [(1, '2024-03-15'), ...]

            Format a human-readable datetime — hour is ``HH24``, minute
            is ``MI`` (not ``MM``)::

                pretty = Builtins.Strftime(
                    'YYYY-MM-DD HH24:MI:SS',
                    users.created_at,
                )
                rows = users.get_row([users.id, pretty])
                # -> [(1, '2024-03-15 09:30:42'), ...]

            Format only the time portion with AM/PM::

                rows = users.get_row([
                    Builtins.Strftime('HH12:MI AM', users.created_at),
                ])
                # -> [('09:30 AM',), ('02:45 PM',), ...]

            Month-year grouping — useful for reporting::

                month_key = Builtins.Strftime('YYYY-MM', users.created_at)
                rows = users.get_row([month_key])
                from collections import Counter
                monthly = Counter(m for (m,) in rows)
                # {'2024-01': 45, '2024-02': 38, '2024-03': 52, ...}

            Literal text in the format — quoted with double quotes::

                display = Builtins.Strftime(
                    'YYYY" Q"Q',        # -> '2024 Q1'
                    users.created_at,
                )
                rows = users.get_row([display])
                # -> [('2024 Q1',), ...]
                # Note: Q is a template token for the quarter — you must
                # quote it with double quotes when you want a literal.

            Day name and ordinal day — using PostgreSQL's tokens::

                display = Builtins.Strftime(
                    'Day DD of YYYY',    # -> 'Friday 15 of 2024'
                    users.created_at,
                )
                rows = users.get_row([display])

            Filter by formatted year string::

                rows = users.get_row(
                    [users.id],
                    where=Builtins.Strftime('YYYY', users.created_at) == '2024',
                )
                # SELECT "users"."id" FROM "users"
                # WHERE ((TO_CHAR("users"."created_at", %s)) = %s)
                # Parameters: ['YYYY', '2024']

            Chain with string methods — ``current_datatype`` is ``str``::

                expr = Builtins.Strftime('YYYY', users.created_at).add_end('-Q1')
                rows = users.get_row([expr])
                # -> '2024-Q1'

            Escape a literal double-quote in the format — a rare need::

                tricky = Builtins.Strftime('YYYY""YY', users.created_at)
                # Produces '2024"24'
                rows = users.get_row([tricky])
        """
        sql, p, _, c = Builtins._normalize(value)
        return Builtins._make(f'(TO_CHAR({sql}, %s))', p + [fmt], str, c)

    @staticmethod
    def Timediff(a, b):
        """Compute the difference between two timestamps as a text interval.

        Generates an expression of the form
        ``(CAST(<a> AS TIMESTAMP) - CAST(<b> AS TIMESTAMP))::text``.
        PostgreSQL's native interval type is fetched by psycopg as a
        ``datetime.timedelta``, which is fine for Python-side arithmetic
        but awkward for direct display. Casting to ``text`` gives a
        human-readable form like ``'2 days 05:29:18'``.

        The result is a ``TEXT`` string describing the elapsed time
        between ``a`` and ``b``. Sign convention: the result is
        ``a - b``. A positive interval means ``a`` occurs after ``b``;
        a negative interval means ``a`` occurs before ``b``. When ``a``
        and ``b`` are within the same day, the day part is omitted.

        PostgreSQL's interval text format:

        .. code-block:: text

            1 year 2 months 3 days 04:05:06
            2 days 05:29:18
            00:00:42
            -1 days -04:05:06

        For numeric differences in days or seconds, use
        :meth:`DateDiffDays` or :meth:`DateDiffSeconds` instead — those
        return plain integers ready for arithmetic.

        Args:
            a: The subtrahend — the "later" side of the difference.
                Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

            b: The minuend — the "earlier" side of the difference. Same
                accepted types as ``a``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``((CAST(<a> AS TIMESTAMP) - CAST(<b> AS TIMESTAMP))::text)``
            and whose ``current_datatype`` is always ``str``. Parameters
            are concatenated left-to-right: first ``a``'s, then ``b``'s.

        Example:
            Human-readable account age for every user::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                age = Builtins.Timediff(Builtins.Now(), users.created_at)
                rows = users.get_row([
                    users.username,
                    age,
                ])
                # SELECT "users"."username",
                #        ((CAST((NOW()) AS TIMESTAMP)
                #          - CAST("users"."created_at" AS TIMESTAMP))::text)
                # FROM "users"
                # -> [('Alice', '2 days 05:29:18'), ...]

            Time since last login::

                elapsed = Builtins.Timediff(
                    Builtins.Now(),
                    users.last_login,
                )
                rows = users.get_row([users.username, elapsed])
                # -> [('Alice', '00:15:42'), ('Bob', '3 days 12:04:11'), ...]

            Compare two timestamp columns — session duration::

                session_len = Builtins.Timediff(
                    users.session_ended,
                    users.session_started,
                )
                rows = users.get_row([users.id, session_len])
                # -> [(1, '01:23:45'), ...]

            Sign convention — the order of arguments matters::

                later_minus_earlier = Builtins.Timediff(
                    users.updated_at,      # a
                    users.created_at,      # b
                )
                rows = users.get_row([users.id, later_minus_earlier])
                # -> [(1, '00:30:00'), ...]   (updated after created)

                earlier_minus_later = Builtins.Timediff(
                    users.created_at,      # a
                    users.updated_at,      # b
                )
                rows = users.get_row([users.id, earlier_minus_later])
                # -> [(1, '-00:30:00'), ...]   (created before updated)

            Combine with :meth:`Format` for a prettier display::

                age_str = Builtins.Timediff(Builtins.Now(), users.created_at)
                display = Builtins.Format('Member for %s', age_str)
                rows = users.get_row([users.username, display])
                # -> [('Alice', 'Member for 2 days 05:29:18'), ...]

            Filter by elapsed time — use the numeric helpers instead,
            because interval text does not sort lexicographically in the
            same order as chronologically::

                # Correct:
                recently = (Builtins.DateDiffDays(
                    Builtins.Now(), users.created_at
                ) < 7)
                rows = users.get_row([users.username], where=recently)

            Applied to raw literals::

                rows = users.get_row([
                    Builtins.Timediff('2024-03-15', '2024-03-01'),
                    # -> '14 days'
                    Builtins.Timediff('2024-03-01', '2024-03-15'),
                    # -> '-14 days'
                ])
        """
        s1, p1, _, c = Builtins._normalize(a)
        s2, p2, _, _ = Builtins._normalize(b)
        return Builtins._make(
            f'((CAST({s1} AS TIMESTAMP) - CAST({s2} AS TIMESTAMP))::text)',
            p1 + p2, str, c)

    @staticmethod
    def DateDiffDays(a, b):
        """Compute the number of whole days between two date/time values.

        Generates a
        ``CAST(EXTRACT(EPOCH FROM (CAST(<a> AS TIMESTAMP) - CAST(<b> AS TIMESTAMP))) / 86400 AS INTEGER)``
        expression. The result is the signed day count from ``b`` to
        ``a``:

        - A positive result means ``a`` occurs *after* ``b``.
        - A negative result means ``a`` occurs *before* ``b``.
        - A zero result means the two timestamps fall within the same
          twenty-four-hour window.

        Because the intermediate epoch-seconds value is divided by 86400
        and then cast to integer, the truncation is toward zero — "one
        and a half days" becomes "1", not "2". The count is a *whole*
        day count, not a rounded one.

        This is the natural SQL analogue of Python's
        ``(date_a - date_b).days`` on two ``date`` objects, and matches
        the SQLite ORM's identical helper.

        Args:
            a: The later date/time expression. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

            b: The earlier date/time expression. Same accepted types as
                ``a``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(EXTRACT(EPOCH FROM (CAST(<a> AS TIMESTAMP)
            - CAST(<b> AS TIMESTAMP))) / 86400 AS INTEGER))`` and whose
            ``current_datatype`` is always ``int``. Parameters are
            concatenated left-to-right: first ``a``'s parameters, then
            ``b``'s.

        Example:
            Account age in days for every user::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                age_days = Builtins.DateDiffDays(Builtins.Now(), users.created_at)
                rows = users.get_row([
                    users.username,
                    age_days,
                ])
                # SELECT "users"."username",
                #        (CAST(EXTRACT(EPOCH FROM
                #                       (CAST((NOW()) AS TIMESTAMP)
                #                        - CAST("users"."created_at" AS TIMESTAMP)))
                #              / %s AS INTEGER))
                # FROM "users"
                # Parameters: [86400]
                # -> [('Alice', 74), ('Bob', 12), ...]

            Filter users who signed up in the last week::

                rows = users.get_row(
                    [users.username, users.created_at],
                    where=Builtins.DateDiffDays(
                        Builtins.Now(), users.created_at
                    ) < 7,
                )
                # SELECT "users"."username", "users"."created_at"
                # FROM "users"
                # WHERE ((CAST(EXTRACT(EPOCH FROM
                #                      (CAST((NOW()) AS TIMESTAMP)
                #                       - CAST("users"."created_at" AS TIMESTAMP)))
                #             / %s AS INTEGER)) < %s)
                # Parameters: [86400, 7]

            Filter users who signed up more than a year ago::

                rows = users.get_row(
                    [users.username],
                    where=Builtins.DateDiffDays(
                        Builtins.Now(), users.created_at
                    ) > 365,
                )

            Session duration in days — compare two columns::

                duration = Builtins.DateDiffDays(
                    users.session_ended,
                    users.session_started,
                )
                rows = users.get_row([users.id, duration])
                # -> [(1, 2), (2, 0), (3, 5), ...]

            Order users by account age — most recent first::

                rows = users.get_row(
                    [users.username, users.created_at],
                    order_by=Builtins.DateDiffDays(
                        Builtins.Now(), users.created_at
                    ),
                )
                # Smallest day-count (most recent) appears first.

            Detect users who signed up today::

                same_day = (Builtins.DateDiffDays(
                    Builtins.Now(), users.created_at
                ) == 0)
                rows = users.get_row([users.username], where=same_day)

            Sign convention — order of arguments matters::

                # Positive: a is later than b
                pos = Builtins.DateDiffDays('2024-03-15', '2024-03-01')
                # -> 14

                # Negative: a is earlier than b
                neg = Builtins.DateDiffDays('2024-03-01', '2024-03-15')
                # -> -14

            Compare with the fractional-day form — use :meth:`JulianDay`
            for a fractional day count::

                # Truncated:   14  (whole days)
                whole = Builtins.DateDiffDays(
                    '2024-03-15 12:00:00', '2024-03-01 06:00:00'
                )
                # Fractional:  14.25  (days with hours preserved)
                fraction = (
                    Builtins.JulianDay('2024-03-15 12:00:00')
                    - Builtins.JulianDay('2024-03-01 06:00:00')
                )
        """
        s1, p1, _, c = Builtins._normalize(a)
        s2, p2, _, _ = Builtins._normalize(b)
        return Builtins._make(
            f'(CAST(EXTRACT(EPOCH FROM (CAST({s1} AS TIMESTAMP) - CAST({s2} AS TIMESTAMP))) / 86400 AS INTEGER))',
            p1 + p2, int, c)

    @staticmethod
    def DateDiffSeconds(a, b):
        """Compute the number of whole seconds between two date/time values.

        Generates a
        ``CAST(EXTRACT(EPOCH FROM (CAST(<a> AS TIMESTAMP) - CAST(<b> AS TIMESTAMP))) AS BIGINT)``
        expression. The result is the signed second count from ``b`` to
        ``a``:

        - A positive result means ``a`` occurs *after* ``b``.
        - A negative result means ``a`` occurs *before* ``b``.
        - A zero result means the two moments are within the same second.

        The explicit ``::BIGINT`` cast ensures the value arrives in Python
        as a plain ``int`` (not a ``decimal.Decimal``) and truncates the
        fractional seconds toward zero.

        This is the natural SQL analogue of Python's
        ``(datetime_a - datetime_b).total_seconds()`` for whole-second
        precision.

        Args:
            a: The later date/time expression. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

            b: The earlier date/time expression. Same accepted types as
                ``a``.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(EXTRACT(EPOCH FROM (CAST(<a> AS TIMESTAMP)
            - CAST(<b> AS TIMESTAMP)) AS BIGINT))`` and whose
            ``current_datatype`` is always ``int``. Parameters are
            concatenated left-to-right: first ``a``'s parameters, then
            ``b``'s.

        Example:
            Seconds since last login for every user::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                secs = Builtins.DateDiffSeconds(Builtins.Now(), users.last_login)
                rows = users.get_row([
                    users.username,
                    secs,
                ])
                # SELECT "users"."username",
                #        (CAST(EXTRACT(EPOCH FROM
                #                       (CAST((NOW()) AS TIMESTAMP)
                #                        - CAST("users"."last_login" AS TIMESTAMP)))
                #              AS BIGINT))
                # FROM "users"
                # Parameters: []
                # -> [('Alice', 4809), ('Bob', 172800), ...]

            Filter to sessions active in the last hour::

                rows = users.get_row(
                    [users.username, users.last_seen],
                    where=Builtins.DateDiffSeconds(
                        Builtins.Now(), users.last_seen
                    ) < 3600,
                )
                # SELECT "users"."username", "users"."last_seen"
                # FROM "users"
                # WHERE ((CAST(EXTRACT(EPOCH FROM
                #                      (CAST((NOW()) AS TIMESTAMP)
                #                       - CAST("users"."last_seen" AS TIMESTAMP)))
                #             AS BIGINT)) < %s)
                # Parameters: [3600]

            Convert to minutes in Python after fetching::

                rows = users.get_row([
                    users.username,
                    Builtins.DateDiffSeconds(Builtins.Now(), users.last_seen),
                ])
                minutes = [(u, s // 60) for u, s in rows]

            Session duration in seconds — compare two columns::

                duration = Builtins.DateDiffSeconds(
                    users.session_ended,
                    users.session_started,
                )
                rows = users.get_row([users.id, duration])
                # -> [(1, 5025), (2, 12), (3, 432000), ...]

            Order by recency — most recently active first::

                rows = users.get_row(
                    [users.username],
                    order_by=Builtins.DateDiffSeconds(
                        Builtins.Now(), users.last_seen
                    ),
                )

            Detect users active within the same second::

                same_second = (Builtins.DateDiffSeconds(
                    Builtins.Now(), users.last_seen
                ) == 0)
                rows = users.get_row([users.username], where=same_second)

            Rate limiting pattern — count events in a rolling window::

                events = db.events
                recent_count = Builtins.Sum(
                    Builtins.IIf(
                        Builtins.DateDiffSeconds(
                            Builtins.Now(), events.occurred_at
                        ) < 60,
                        1,
                        0,
                    )
                )
                rows = events.get_row([recent_count])
                # -> [(7,)] if 7 events happened in the last minute

            Sign convention — order of arguments matters::

                # Positive: a is later than b
                pos = Builtins.DateDiffSeconds(
                    '2024-03-15 12:00:00', '2024-03-15 11:00:00'
                )
                # -> 3600

                # Negative: a is earlier than b
                neg = Builtins.DateDiffSeconds(
                    '2024-03-15 11:00:00', '2024-03-15 12:00:00'
                )
                # -> -3600
        """
        s1, p1, _, c = Builtins._normalize(a)
        s2, p2, _, _ = Builtins._normalize(b)
        return Builtins._make(
            f'(CAST(EXTRACT(EPOCH FROM (CAST({s1} AS TIMESTAMP) - CAST({s2} AS TIMESTAMP))) AS BIGINT))',
            p1 + p2, int, c)

    @staticmethod
    def DateAdd(value, *intervals):
        """Add one or more PostgreSQL INTERVAL strings to a date and cast to DATE.

        Generates a chain of ``(CAST(<expr> AS TIMESTAMP) + %s::interval)``
        additions, each wrapping the previous result, and finally casts
        the whole expression to ``DATE``:

        .. code-block:: sql

            CAST((CAST(<expr> AS TIMESTAMP)
                  + %s::interval
                  + %s::interval) AS DATE)

        Each interval is bound as a ``%s`` parameter, so user-supplied
        interval strings are safe from SQL injection — though for full
        safety you should validate the interval against an allowlist
        before passing it.

        PostgreSQL interval syntax differs from SQLite's modifier syntax.
        The following forms are accepted:

        - ``'1 day'``, ``'7 days'`` — add days
        - ``'-1 day'`` — subtract a day
        - ``'1 month'``, ``'3 months'`` — add months (PostgreSQL clamps
          end-of-month correctly — Jan 31 + 1 month → Feb 28/29)
        - ``'1 year'`` — add a year
        - ``'2 hours'``, ``'30 minutes'``, ``'15 seconds'`` — add time
        - ``'1 day 2 hours'`` — compound intervals
        - ``'1-2'`` — SQL standard year-month form
        - ``'3 04:05:06'`` — SQL standard day-time form

        SQLite-style tokens like ``'start of month'`` or
        ``'weekday 1'`` are **not** supported. Use ``DATE_TRUNC`` via
        :meth:`Func` for those cases.

        When called with no intervals, this helper degenerates to
        ``CAST(<expr> AS DATE)`` — the same behaviour as :meth:`Date`.
        The separate name exists for the modifier-aware form, but both
        are valid call patterns.

        Args:
            value: The starting date/time expression. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

            *intervals: Zero or more interval strings, each bound as a
                ``%s`` parameter. Applied left-to-right. See the list
                above for accepted forms.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> + ?::interval + ?::interval AS DATE))`` and
            whose ``current_datatype`` is always ``str``. Parameters are
            concatenated left-to-right: first ``value``'s parameters,
            then the intervals in order.

        Example:
            Tomorrow's date::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    Builtins.DateAdd(Builtins.Today(), '1 day'),
                ])
                # SELECT (CAST((CAST((CURRENT_DATE) AS TIMESTAMP)
                #               + %s::interval) AS DATE))
                # FROM "users"
                # Parameters: ['1 day']
                # -> [(date(2024, 3, 16),)]

            A week from now::

                rows = users.get_row([
                    Builtins.DateAdd(Builtins.Today(), '7 days'),
                ])
                # -> [(date(2024, 3, 22),)]

            First day of the current month — PostgreSQL has no
            ``'start of month'`` modifier, so this uses ``DATE_TRUNC``
            via a raw Func call combined with a Python f-string::

                # Not directly expressible with DateAdd — use Func:
                month_start = Builtins.Func('DATE_TRUNC', Builtins.Today())
                # Note: DATE_TRUNC requires a unit argument. This is a
                # limitation of the single-argument Func helper. The
                # idiomatic form is:
                #   DATE_TRUNC('month', CURRENT_DATE)::DATE

            Previous week relative to a stored timestamp::

                last_week = Builtins.DateAdd(users.created_at, '-7 days')
                rows = users.get_row([users.username, last_week])
                # SELECT "users"."username",
                #        (CAST((CAST("users"."created_at" AS TIMESTAMP)
                #               + %s::interval) AS DATE))
                # FROM "users"
                # Parameters: ['-7 days']

            Chained intervals — one month and one day::

                next_period = Builtins.DateAdd(
                    users.created_at,
                    '1 month',
                    '1 day',
                )
                rows = users.get_row([users.username, next_period])
                # SELECT "users"."username",
                #        (CAST((CAST("users"."created_at" AS TIMESTAMP)
                #               + %s::interval
                #               + %s::interval) AS DATE))
                # FROM "users"
                # Parameters: ['1 month', '1 day']

            Filter events that occurred in the current month::

                month_start = Builtins.DateAdd(Builtins.Today(), '-30 days')
                rows = users.get_row(
                    [users.id, users.created_at],
                    where=Builtins.Date(users.created_at) >= month_start,
                )
                # SELECT "users"."id", "users"."created_at" FROM "users"
                # WHERE ((CAST("users"."created_at" AS DATE))
                #        >= (CAST((CAST((CURRENT_DATE) AS TIMESTAMP)
                #                  + %s::interval) AS DATE)))
                # Parameters: ['-30 days']

            Birthday reminder — combine a stored date with an annual
            offset. Note that ``'1 year'`` on Feb 29 lands on Feb 28 in
            non-leap years — PostgreSQL handles this correctly::

                next_birthday = Builtins.DateAdd(users.date_of_birth, '1 year')
                rows = users.get_row([users.username, next_birthday])

            Degenerate form — no intervals, same as :meth:`Date`::

                rows = users.get_row([
                    Builtins.DateAdd(users.created_at),   # (CAST(<expr> AS DATE))
                    Builtins.Date(users.created_at),      # (CAST(<expr> AS DATE))
                ])
                # Both columns produce identical SQL and results.
        """
        sql, p, _, c = Builtins._normalize(value)
        if not intervals:
            return Builtins._make(f'(CAST({sql} AS DATE))', p, str, c)
        expr, params = f'CAST({sql} AS TIMESTAMP)', list(p)
        for iv in intervals:
            expr = f'({expr} + %s::interval)'
            params.append(iv)
        return Builtins._make(f'(CAST({expr} AS DATE))', params, str, c)

    @staticmethod
    def DateTimeAdd(value, *intervals):
        """Add one or more PostgreSQL INTERVAL strings to a datetime.

        Generates a chain of ``(CAST(<expr> AS TIMESTAMP) + %s::interval)``
        additions, each wrapping the previous result. Unlike
        :meth:`DateAdd`, this helper does **not** cast the final result
        to ``DATE`` — the result is a full ``TIMESTAMP`` that preserves
        the time-of-day component.

        This is the datetime-producing counterpart of :meth:`DateAdd`:
        same interval set, same chaining semantics, but the result keeps
        the time portion. Use this method whenever the time-of-day
        matters — for example, when computing "one hour from now", when
        the intervals include a time unit (``'2 hours'``, ``'30
        minutes'``, ``'15 seconds'``), or when the base value is a full
        timestamp and you want to preserve it.

        See :meth:`DateAdd` for the complete list of accepted interval
        strings. They are identical for both helpers.

        When called with no intervals, this helper degenerates to
        ``CAST(<expr> AS TIMESTAMP)`` — the same behaviour as
        :meth:`DateTime`.

        Args:
            value: The starting date/time expression. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

            *intervals: Zero or more interval strings, each bound as a
                ``%s`` parameter. Applied left-to-right, exactly as in
                :meth:`DateAdd`.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> AS TIMESTAMP) + ?::interval + ?::interval)``
            and whose ``current_datatype`` is always ``str``. Parameters
            are concatenated left-to-right: first ``value``'s parameters,
            then the intervals in order.

        Example:
            One hour from now — the time portion is preserved::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    Builtins.DateTimeAdd(Builtins.Now(), '1 hour'),
                ])
                # SELECT ((CAST((NOW()) AS TIMESTAMP) + %s::interval))
                # FROM "users"
                # Parameters: ['1 hour']
                # -> [(datetime(2024, 3, 15, 15, 30, 42),)]

            Fifteen minutes from now — good for short-lived tokens::

                expiry = Builtins.DateTimeAdd(Builtins.Now(), '15 minutes')
                rows = users.get_row([users.id, expiry])
                # -> [(1, datetime(2024, 3, 15, 14, 45, 42)), ...]

            Last week relative to a stored timestamp::

                last_week = Builtins.DateTimeAdd(users.created_at, '-7 days')
                rows = users.get_row([users.username, last_week])
                # SELECT "users"."username",
                #        ((CAST("users"."created_at" AS TIMESTAMP)
                #          + %s::interval))
                # FROM "users"
                # Parameters: ['-7 days']
                # -> [('Alice', datetime(2024, 3, 8, 9, 30, 42)), ...]

            Chained intervals — a day and a half::

                later = Builtins.DateTimeAdd(
                    users.created_at,
                    '1 day',
                    '12 hours',
                )
                rows = users.get_row([users.username, later])
                # SELECT "users"."username",
                #        ((CAST("users"."created_at" AS TIMESTAMP)
                #          + %s::interval
                #          + %s::interval))
                # FROM "users"
                # Parameters: ['1 day', '12 hours']

            Filter events in the last hour — compare against a computed
            threshold::

                cutoff = Builtins.DateTimeAdd(Builtins.Now(), '-1 hour')
                rows = users.get_row(
                    [users.id, users.created_at],
                    where=users.created_at >= cutoff,
                )
                # SELECT "users"."id", "users"."created_at" FROM "users"
                # WHERE ("users"."created_at"
                #        >= ((CAST((NOW()) AS TIMESTAMP) + %s::interval)))
                # Parameters: ['-1 hour']

            Rolling window of one day — a common "recent activity"
            filter::

                rows = users.get_row(
                    [users.username, users.last_seen],
                    where=Builtins.DateTime(users.last_seen)
                        >= Builtins.DateTimeAdd(Builtins.Now(), '-1 day'),
                )

            Add months to a stored subscription date::

                plus_three_months = Builtins.DateTimeAdd(
                    users.subscribed_at, '3 months'
                )
                rows = users.get_row([users.username, plus_three_months])

            Store a computed expiry during an UPDATE::

                users.update(
                    update={users.token_expires:
                            Builtins.DateTimeAdd(Builtins.Now(), '1 day')},
                    where=users.id == 42,
                )
                # UPDATE "users"
                # SET "token_expires" = ((CAST((NOW()) AS TIMESTAMP)
                #                         + %s::interval))
                # WHERE ("users"."id" = %s);
                # Parameters: ['1 day', 42]

            Degenerate form — no intervals, same as :meth:`DateTime`::

                rows = users.get_row([
                    Builtins.DateTimeAdd(users.created_at),
                    Builtins.DateTime(users.created_at),
                ])
                # Both columns produce identical SQL and results.
        """
        sql, p, _, c = Builtins._normalize(value)
        if not intervals:
            return Builtins._make(f'(CAST({sql} AS TIMESTAMP))', p, str, c)
        expr, params = f'CAST({sql} AS TIMESTAMP)', list(p)
        for iv in intervals:
            expr = f'({expr} + %s::interval)'
            params.append(iv)
        return Builtins._make(f'({expr})', params, str, c)

    @staticmethod
    def TimeAdd(value, *intervals):
        """Add INTERVAL strings to a time-of-day value and cast back to TIME.

        Generates a chain of ``(CAST(<expr> AS TIMESTAMP) + %s::interval)``
        additions and finally casts the whole expression to ``TIME``:

        .. code-block:: sql

            CAST((CAST(<expr> AS TIMESTAMP)
                  + %s::interval
                  + %s::interval) AS TIME)

        This is the time-only counterpart of :meth:`DateAdd` and
        :meth:`DateTimeAdd`: the interval set is identical, but the
        result is a ``TIME`` value, discarding the date component
        entirely.

        Use this method when you only care about the time of day — for
        example, when a schedule shifts by a fixed number of minutes
        regardless of which day it lands on, or when you want to compare
        two times-of-day without their dates confusing the result.

        Because PostgreSQL's date-time arithmetic always carries a full
        timestamp internally, ``TIME`` casts will silently wrap around
        midnight — ``TIME '23:30' + INTERVAL '1 hour'`` produces
        ``TIME '00:30'``, losing the day boundary information. If you
        need to preserve the day, use :meth:`DateTimeAdd` and extract
        the time in Python.

        See :meth:`DateAdd` for the complete list of accepted interval
        strings. They are identical for all three helpers.

        When called with no intervals, this helper degenerates to
        ``CAST(<expr> AS TIME)`` — the same behaviour as :meth:`Time`.

        Args:
            value: The starting date/time expression. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

            *intervals: Zero or more interval strings, each bound as a
                ``%s`` parameter. Applied left-to-right.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(CAST(<expr> + ?::interval + ?::interval AS TIME))`` and
            whose ``current_datatype`` is always ``str``. Parameters
            are concatenated left-to-right: first ``value``'s parameters,
            then the intervals in order.

        Example:
            The current time of day::

                from Ormophine.Postgresql import Driver, Builtins

                db     = Driver("localhost", 5432, "user", "pass", "app")
                events = db.events

                rows = events.get_row([
                    Builtins.TimeAdd(Builtins.Now()),
                ])
                # SELECT (CAST((NOW()) AS TIME)) FROM "events"
                # -> [(time(14, 30, 42),)]

            Shift a stored time by 30 minutes::

                rows = events.get_row([
                    events.id,
                    events.scheduled_at,
                    Builtins.TimeAdd(events.scheduled_at, '30 minutes'),
                ])
                # SELECT "events"."id", "events"."scheduled_at",
                #        (CAST((CAST("events"."scheduled_at" AS TIMESTAMP)
                #               + %s::interval) AS TIME))
                # FROM "events"
                # Parameters: ['30 minutes']
                # -> [(1, datetime(2024, 3, 15, 9, 0), time(9, 30)), ...]

            Compute the "next hour" marker by adding 60 minutes::

                next_hour = Builtins.TimeAdd(events.scheduled_at, '60 minutes')
                rows = events.get_row([events.id, next_hour])
                # -> [(1, time(10, 0)), ...]

            Compare a stored time against a threshold — filter events
            whose scheduled time plus 15 minutes is after 6 PM::

                rows = events.get_row(
                    [events.id, events.scheduled_at],
                    where=Builtins.TimeAdd(
                        events.scheduled_at, '15 minutes'
                    ) > '18:00:00',
                )
                # SELECT "events"."id", "events"."scheduled_at"
                # FROM "events"
                # WHERE ((CAST((CAST("events"."scheduled_at" AS TIMESTAMP)
                #               + %s::interval) AS TIME)) > %s)
                # Parameters: ['15 minutes', '18:00:00']

            Shift a time-of-day backwards by an hour::

                one_hour_earlier = Builtins.TimeAdd(
                    events.scheduled_at, '-1 hour'
                )
                rows = events.get_row([events.id, one_hour_earlier])
                # -> [(1, time(8, 0)), (2, time(7, 30)), ...]

            Cross-midnight wrap-around — the result wraps silently::

                rows = events.get_row([
                    Builtins.TimeAdd('23:30:00', '1 hour'),
                ])
                # -> [(time(0, 30),)] — the day boundary is lost.
                # Use DateTimeAdd if you need to preserve the day.

            Filter office-hours slots — combined with plain :meth:`Time`::

                rows = events.get_row(
                    [events.id, events.scheduled_at],
                    where=(Builtins.Time(events.scheduled_at) >= '09:00:00')
                        & (Builtins.Time(events.scheduled_at) <= '17:00:00'),
                )
                # SELECT "events"."id", "events"."scheduled_at"
                # FROM "events"
                # WHERE (((CAST("events"."scheduled_at" AS TIME)) >= %s)
                #        AND ((CAST("events"."scheduled_at" AS TIME)) <= %s))
                # Parameters: ['09:00:00', '17:00:00']

            Compose with :meth:`Format` for a display string::

                display = Builtins.Format(
                    'Next slot at %s',
                    Builtins.TimeAdd(events.scheduled_at, '45 minutes'),
                )
                rows = events.get_row([events.id, display])
                # -> [('Next slot at 09:45:00',), ...]

            Degenerate form — no intervals, same as :meth:`Time`::

                rows = events.get_row([
                    Builtins.TimeAdd(events.scheduled_at),
                    Builtins.Time(events.scheduled_at),
                ])
                # Both columns produce identical SQL and results.
        """
        sql, p, _, c = Builtins._normalize(value)
        if not intervals:
            return Builtins._make(f'(CAST({sql} AS TIME))', p, str, c)
        expr, params = f'CAST({sql} AS TIMESTAMP)', list(p)
        for iv in intervals:
            expr = f'({expr} + %s::interval)'
            params.append(iv)
        return Builtins._make(f'(CAST({expr} AS TIME))', params, str, c)

    @staticmethod
    def StrftimeMod(fmt, value, *intervals):
        """Format a date/time value with INTERVAL modifiers applied first.

        Generates a ``TO_CHAR(<expr>, <fmt>)`` expression where ``<expr>``
        is the base value with every interval added in sequence:

        .. code-block:: sql

            TO_CHAR((CAST(<value> AS TIMESTAMP)
                     + %s::interval
                     + %s::interval),
                    %s)

        This is the modifier-aware companion of :meth:`Strftime`: it
        applies every interval to ``value`` before feeding the result to
        the format string, which lets you do things like "format the
        timestamp as ``YYYY-MM`` *after* adding one month" in a single
        SQL expression.

        The full interval set documented for :meth:`DateAdd` is
        available. The format string supports the same PostgreSQL
        template tokens as :meth:`Strftime` — see that method for the
        complete mapping.

        When called with no intervals, this helper degenerates to
        ``TO_CHAR(<value>, <fmt>)`` — the same behaviour as
        :meth:`Strftime`. The separate name exists for the modifier-aware
        form, but both patterns are valid.

        The result is always a ``TEXT`` string.

        Args:
            fmt: The format template. Bound as a ``%s`` parameter, so it
                can be user-supplied safely.

            value: The starting date/time expression. Supported types:

                - :class:`ColumnsOperation` — its SQL fragment and parameters
                  are reused.
                - :class:`Column` — the fully qualified column name is used.
                - A raw Python string or number — bound as a ``%s``
                  placeholder.

            *intervals: Zero or more interval strings, each bound as a
                ``%s`` parameter. Applied left-to-right.

        Returns:
            ColumnsOperation: An expression whose ``_output[0]`` is
            ``(TO_CHAR(<expr> + ?::interval + ?, %s))`` and whose
            ``current_datatype`` is always ``str``. Parameters are
            ordered: first ``value``'s parameters, then the intervals,
            then ``fmt`` as the final parameter.

        Example:
            Format "one month from now" as ``YYYY-MM``::

                from Ormophine.Postgresql import Driver, Builtins

                db    = Driver("localhost", 5432, "user", "pass", "app")
                users = db.users

                rows = users.get_row([
                    Builtins.StrftimeMod(
                        'YYYY-MM',
                        Builtins.Now(),
                        '1 month',
                    ),
                ])
                # SELECT (TO_CHAR((CAST((NOW()) AS TIMESTAMP)
                #                  + %s::interval), %s))
                # FROM "users"
                # Parameters: ['1 month', 'YYYY-MM']
                # -> [('2024-04',)]

            Report period label from a stored subscription date::

                period = Builtins.StrftimeMod(
                    'YYYY-MM',
                    users.subscribed_at,
                    '3 months',
                )
                rows = users.get_row([users.username, period])
                # SELECT "users"."username",
                #        (TO_CHAR((CAST("users"."subscribed_at" AS TIMESTAMP)
                #                  + %s::interval), %s))
                # FROM "users"
                # Parameters: ['3 months', 'YYYY-MM']
                # -> [('Alice', '2024-06'), ('Bob', '2024-07'), ...]

            "Last seen today" — extract the time after shifting by
            nothing (uses the base datetime)::

                last_seen_local = Builtins.StrftimeMod(
                    'HH24:MI',
                    users.last_seen,
                    '0 seconds',        # no-op interval, cleaner than empty
                )
                rows = users.get_row([users.username, last_seen_local])
                # -> [('Alice', '15:30'), ('Bob', '09:12'), ...]

            "First of next month" as a display string — chain two
            intervals::

                first_next_month = Builtins.StrftimeMod(
                    'YYYY-MM-DD',
                    Builtins.Today(),
                    '1 month',
                )
                rows = users.get_row([first_next_month])
                # -> [('2024-04-15',)]
                # Note: this returns the same day next month, not the
                # first of the month. For "first of month" semantics,
                # use DATE_TRUNC via a raw expression.

            Filter by a formatted, modified value::

                rows = users.get_row(
                    [users.id, users.created_at],
                    where=Builtins.StrftimeMod(
                        'YYYY',
                        users.created_at,
                        '-6 months',
                    ) == '2023',
                )
                # SELECT "users"."id", "users"."created_at"
                # FROM "users"
                # WHERE ((TO_CHAR((CAST("users"."created_at" AS TIMESTAMP)
                #                  + %s::interval), %s)) = %s)
                # Parameters: ['-6 months', 'YYYY', '2023']
                # Users whose timestamp, shifted back six months, falls
                # in 2023.

            Quarter label via a chained interval::

                q_label = Builtins.StrftimeMod(
                    'YYYY"-Q"Q',
                    users.created_at,
                    '0 days',
                )
                rows = users.get_row([users.username, q_label])
                # -> [('Alice', '2024-Q1'), ('Bob', '2024-Q2'), ...]
                # Q is a PostgreSQL template token for the quarter.

            Degenerate form — no intervals, same as :meth:`Strftime`::

                rows = users.get_row([
                    Builtins.StrftimeMod('YYYY', users.created_at),
                    Builtins.Strftime('YYYY', users.created_at),
                ])
                # Both columns produce identical SQL and results.
        """
        sql, p, _, c = Builtins._normalize(value)
        if not intervals:
            return Builtins._make(
                f'(TO_CHAR({sql}, %s))', p + [fmt], str, c)
        expr, params = f'CAST({sql} AS TIMESTAMP)', list(p)
        for iv in intervals:
            expr = f'({expr} + %s::interval)'
            params.append(iv)
        return Builtins._make(
            f'(TO_CHAR({expr}, %s))', params + [fmt], str, c)
    

from __future__ import annotations

class _NullCol:
    """
    Fallback ``col_obj`` used when a ColumnsOperation-shaped object has no
    originating Column.

    Supplies the same attribute surface as a real :class:`Column`
    (``datatype``, ``name``, ``first_name``, ``table_obj``) so that every
    method inherited from :class:`ColumnsOperation` keeps working when the
    expression is built from a raw literal rather than a real column.
    """
    datatype   = None
    name       = ''
    first_name = ''
    class table_obj:
        _PlaceHolder = type(None)   # Nothing isinstance-matches this


class ColumnsOperation:
    """
    A chainable builder for SQL column expressions and operations.

    This class provides a fluent interface for constructing SQL expressions
    involving columns, literals, and operations like arithmetic, comparisons,
    string functions, and pattern matching. It is used internally by the
    :class:`Column` class and is returned by most column operators and methods.

    The core of the class is the `_output` attribute, which stores a tuple
    `(sql_expression, parameters_list)`. As operations are chained, the SQL
    string is gradually built and the parameter list is accumulated. This
    allows the final expression to be safely used in parameterized queries,
    preventing SQL injection.

    The class supports:
    - Arithmetic operations (+, -, *, /, %, **) with automatic detection of
      string concatenation vs. numeric addition based on the column's datatype.
    - Comparison operations (==, !=, <, <=, >, >=) via both operator overloading
      and explicit methods (eq, ne, lt, le, gt, ge).
    - Logical operations (AND, OR) for combining conditions.
    - String operations: LIKE, STARTSWITH, ENDSWITH, CONTAINS, UPPER, LOWER,
      REPLACE, TRIM (strip, lstrip, rstrip), and SUBSTRING via slice notation.
    - Collection operations: IN with lists, tuples, or subqueries.
    - Concatenation methods: add_end, add_first for string columns.

    All methods return the instance itself, enabling method chaining:

    Example:
        >>> from ormophine.Postgresql import Driver, Table
        >>> driver = Driver(...)
        >>> employees = driver.employees
        >>> # Build a complex condition
        >>> cond = (employees.salary >= 50000) & (employees.name.upper().contains('SMITH'))
        >>> # Use it in a query
        >>> results = employees.get_row([employees.name, employees.salary], where=cond)
        >>>
        >>> # String slicing (SUBSTRING)
        >>> first_three = employees.name[0:3]
        >>> # Arithmetic operations
        >>> bonus = employees.salary * 0.1
    """
    def __init__(self, col_obj):
        """Initialize a new ColumnsOperation instance.

        This class is a builder for SQL expressions involving column operations.
        It stores the column object and maintains an internal state ``_output``
        that accumulates the SQL fragment and parameter list as operations are
        chained. Typically, instances are created indirectly via :class:`Column`
        operators rather than directly.

        Args:
            col_obj (Column): The column object that this operation is associated
                with. The column's datatype determines whether string concatenation
                (``||``) or numeric addition (``+``) is used in arithmetic operations.

        Returns:
            None: This method only initializes the instance.

        Example:
            >>> # Usually created through Column operators:
            >>> from ormophine.Postgresql import Column, Table
            >>> table = driver.employees
            >>> col_op = table.salary + 1000  # Creates a ColumnsOperation
            >>> # Or explicitly:
            >>> from ormophine.Postgresql import ColumnsOperation
            >>> op = ColumnsOperation(table.salary)
            >>> op._output  # Initially empty, but will be set when operations are applied
            ('', [])
        """
        self._output = ('', []) # To apply operations in a chained manner
        self.col_obj = col_obj
        self.current_datatype = col_obj.datatype

    def __add__(self, other):
        """Add two column expressions or a column and a value.

        This method implements the `+` operator for :class:`ColumnsOperation`.
        It generates a SQL expression that represents either numeric addition
        (for numeric column types) or string concatenation (for string column
        types) using PostgreSQL's `||` operator. The result is stored in the
        internal `_output` tuple, allowing method chaining.

        Args:
            other (Union[ColumnsOperation, Column, int, float, str, _PlaceHolder]):
                The right-hand operand. Can be another :class:`ColumnsOperation`,
                a :class:`Column`, a literal value (int, float, or str), or a
                ``_PlaceHolder`` instance (used in :meth:`Table.bulk_update`).

        Returns:
            ColumnsOperation: The current instance, with `_output` updated to
                contain the new SQL expression and its parameters. This enables
                fluent chaining of operations.

        Example:
            Simple numeric addition:

            >>> employees = driver.employees
            >>> expr = employees.salary * 1.1 + 1000
            >>> # This generates: (("employees"."salary" * 1.1) + %s) with params [1000]

        Example:
            String concatenation with a column and a literal:

            >>> full_name = employees.first_name + " " + employees.last_name
            >>> # Generates: (("employees"."first_name" || %s) || "employees"."last_name")
            >>> # with params [' ']

        Note:
            The operator is chosen as ``||`` if either side has a string datatype;
            otherwise ``+`` is used. For ``_PlaceHolder`` operands, the value is
            treated as a numeric placeholder.
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} {'||' if (self.current_datatype == str) or (other.current_datatype == str) else '+'} {other._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({self._output[0]} {'||' if (self.current_datatype == str) or (other.datatype == str) else '+'} {other.name})', self._output[1]) if isinstance(other, Column) else (f'({self._output[0]} {'||' if (self.current_datatype == str) else '+'} %s)', self._output[1]+[other]) if isinstance(other, int) or isinstance(other , float) or isinstance(other, self.col_obj.table_obj._PlaceHolder) else (f'({self._output[0]} || %s)', self._output[1]+[other if isinstance(other, str) else str(other)])
        new_op.current_datatype = str if (isinstance(other, ColumnsOperation) and other.current_datatype == str) or (isinstance(other, Column) and other.datatype == str) or self.current_datatype == str or ( not isinstance(other, ColumnsOperation) and not isinstance(other,Column) and not isinstance(other, int) and not isinstance(other, float)) else self.current_datatype
        return new_op

    def __radd__(self, other):
        """Implement reflected addition (right-hand side addition) for column operations.

        This method is called when a :class:`ColumnsOperation` object appears on the
        right side of a `+` operator (e.g., `value + column_operation`). It generates
        the appropriate SQL expression fragment, handling different types of `other`:

        - If `other` is another :class:`ColumnsOperation`, it combines both SQL
        expressions with the appropriate operator (`||` for strings, `+` for numerics).
        - If `other` is a :class:`Column`, it uses the column's name.
        - If `other` is an integer or float, it uses a parameterized placeholder `%s`.
        - If `other` is a string, it uses `||` concatenation with a placeholder.
        - For other types, it converts to string and uses `||`.

        The method updates the internal `_output` tuple (SQL string and parameter list)
        and returns `self` to allow method chaining.

        Args:
            other (Any): The value to add to the left side of the operation. Can be
                a :class:`ColumnsOperation`, :class:`Column`, numeric type, string,
                or any other value.

        Returns:
            ColumnsOperation: The current instance with updated `_output`, allowing
                chaining of further operations.

        Example:
            >>> from ormophine.Postgresql import Column, ColumnsOperation, Table
            >>> employees = driver.employees
            >>> # Create a column operation: employees.first_name
            >>> op = employees.first_name
            >>> # Right addition: "Mr. " + first_name
            >>> new_op = "Mr. " + op
            >>> # new_op now represents SQL: ('Mr. ' || "first_name")
            >>> # For numeric columns:
            >>> salary_op = employees.salary
            >>> bonus_op = 1000 + salary_op  # SQL: (1000 + "salary")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({other._output[0]} {'||' if (self.current_datatype == str) or (other.current_datatype == str) else '+'} {self._output[0]})', other._output[1]+self._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} {'||' if (self.current_datatype == str) or (other.datatype == str) else '+'} {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(%s {'||' if (self.current_datatype == str) else '+'} {self._output[0]})', [other]+self._output[1]) if isinstance(other, int) or isinstance(other , float) or isinstance(other, self.col_obj.table_obj._PlaceHolder) else (f'(%s || {self._output[0]})', [other if isinstance(other, str) else str(other)]+self._output[1])
        new_op.current_datatype = str if (isinstance(other, ColumnsOperation) and other.current_datatype == str) or (isinstance(other, Column) and other.datatype == str) or self.current_datatype == str or ( not isinstance(other, ColumnsOperation) and not isinstance(other,Column) and not isinstance(other, int) and not isinstance(other, float)) else self.current_datatype
        return new_op

    def __sub__(self, other):
        """Implement subtraction operator for column expressions.

        This method overloads the `-` operator to generate SQL subtraction expressions
        between column values, column operations, or literal values. It handles
        different operand types:

        * If `other` is a `ColumnsOperation`, both sides are combined.
        * If `other` is a `Column`, it references the column name.
        * Otherwise, it treats `other` as a literal value and uses a parameter placeholder.

        The method mutates the current instance by updating its internal `_output` tuple
        (SQL string and parameter list) and returns `self` to allow method chaining.

        Args:
            other (ColumnsOperation | Column | int | float | Any): The right-hand side
                operand for subtraction.

        Returns:
            ColumnsOperation: The current instance with the updated SQL expression.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> employees = driver.employees
            >>> # Column - literal
            >>> expr = employees.salary - 1000
            >>> # Column - Column
            >>> expr2 = employees.salary - employees.bonus
            >>> # ColumnOperation - ColumnOperation
            >>> expr3 = (employees.salary * 2) - (employees.bonus + 500)
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} - {other._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({self._output[0]} - {other.name})', self._output[1]) if isinstance(other, Column) else (f'({self._output[0]} - %s)', self._output[1]+[other])
        return new_op

    def __rsub__(self, other):
        """Implement reflected subtraction (right-hand side subtraction) for SQL expressions.

        This method is called when a :class:`ColumnsOperation` appears on the right
        side of a subtraction operator, e.g., `5 - column_operation`. It constructs
        the SQL expression for subtracting the current operation from `other` and
        stores the result internally, allowing method chaining.

        The generated SQL expression will use the appropriate operator:
        - If `other` is a :class:`Column`, the expression uses the column name.
        - If `other` is a :class:`ColumnsOperation`, the expression combines both
        operations.
        - If `other` is a literal value, the expression uses a parameter placeholder
        (`%s`) and adds the value to the parameters list.

        Args:
            other (Any): The left-hand operand. Can be a :class:`Column`,
                :class:`ColumnsOperation`, or a literal value (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # This will generate SQL: (1000 - "salary")
            >>> op = 1000 - employees.salary
            >>> print(op._output[0])
            '(1000 - "employees"."salary")'
            >>> print(op._output[1])  # parameters list
            []
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({other._output[0]} - {self._output[0]})', other._output[1] + self._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} - {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(%s - {self._output[0]})', [other]+self._output[1])
        return new_op

    def __mul__(self, other):
        """Implement multiplication for SQL expressions.

        This method is called when the `*` operator is used between a
        :class:`ColumnsOperation` and another operand. It constructs the SQL
        expression for multiplying the current operation by `other` and stores
        the result internally, allowing method chaining.

        The generated SQL expression uses the `*` operator for numeric types.
        If `other` is a :class:`Column`, a :class:`ColumnsOperation`, or a literal
        value, the appropriate SQL representation is generated with parameter
        placeholders (`%s`) as needed.

        Args:
            other (Any): The right-hand operand. Can be a :class:`Column`,
                :class:`ColumnsOperation`, or a literal value (int, float, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Calculate bonus as salary * 1.1
            >>> bonus = employees.salary * 1.1
            >>> print(bonus._output[0])
            '("employees"."salary" * %s)'
            >>> print(bonus._output[1])  # parameters list
            [1.1]
            >>> # Multiply two columns: salary * hours
            >>> total = employees.salary * employees.hours
            >>> print(total._output[0])
            '("employees"."salary" * "employees"."hours")'
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} * {other._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({self._output[0]} * {other.name})', self._output[1]) if isinstance(other, Column) else (f'({self._output[0]} * %s)', self._output[1]+[other])
        return new_op

    def __rmul__(self, other):
        """Implement reflected multiplication (right-hand side multiplication) for SQL expressions.

        This method is invoked when a :class:`ColumnsOperation` appears on the right side of a
        multiplication operator, e.g., `5 * column_operation`. It constructs the SQL expression
        for multiplying `other` by the current operation and stores the result internally,
        enabling method chaining.

        The generated SQL uses the `*` operator. Depending on the type of `other`:
        - If `other` is a :class:`ColumnsOperation`, the expression combines both operations.
        - If `other` is a :class:`Column`, the expression uses the column name.
        - If `other` is a literal value, the expression uses a parameter placeholder (`%s`)
        and adds the value to the parameters list.

        Args:
            other (Any): The left-hand operand. Can be a :class:`Column`,
                :class:`ColumnsOperation`, or a literal value (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output` state,
            allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # This will generate SQL: (2.5 * "salary")
            >>> op = 2.5 * employees.salary
            >>> print(op._output[0])
            '(2.5 * "employees"."salary")'
            >>> print(op._output[1])  # parameters list
            []
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({other._output[0]} * {self._output[0]})', other._output[1] + self._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} * {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(%s * {self._output[0]})', [other]+self._output[1])
        return new_op

    def __pow__(self, other):
        """Implement the exponentiation (power) operator for SQL expressions.

        This method is called when the `**` operator is used with a
        :class:`ColumnsOperation` on the left side. It generates a SQL `POW()`
        function call with the current expression as the base and `other` as the
        exponent. The resulting SQL fragment and its parameters are stored internally,
        allowing method chaining.

        Args:
            other (Any): The exponent. Can be a :class:`ColumnsOperation`,
                :class:`Column`, or a literal value (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            Simple exponentiation with a literal:

            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: POW("employees"."salary", 2)
            >>> op = employees.salary ** 2
            >>> print(op._output[0])
            'POW("employees"."salary" , %s)'
            >>> print(op._output[1])  # parameters: [2]
            [2]

        Example:
            Exponentiation with another Column:

            >>> # Generate SQL: POW("employees"."salary", "employees"."years")
            >>> op = employees.salary ** employees.years

        Example:
            Chaining with other operations:

            >>> # Generate SQL: POW(("salary" + 1000), 2)
            >>> op = (employees.salary + 1000) ** 2
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'(POW({self._output[0]} , {other._output[0]}))', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'(POW({self._output[0]} , {other.name}))', self._output[1]) if isinstance(other, Column) else (f'(POW({self._output[0]} , %s))', self._output[1]+[other])
        return new_op

    def __rpow__(self, other):
        """Implement reflected exponentiation (right-hand side power) for SQL expressions.

        This method is called when a :class:`ColumnsOperation` appears on the right
        side of the exponentiation operator (`**`), e.g., `5 ** column_operation`.
        It constructs the SQL `POW()` function expression with the left operand as
        the base and the current operation as the exponent, and stores the result
        internally, allowing method chaining.

        The generated SQL expression depends on the type of `other`:
        - If `other` is a :class:`Column`, the expression uses the column name as base.
        - If `other` is a :class:`ColumnsOperation`, the expression combines both
        operations.
        - If `other` is a literal value, the expression uses a parameter placeholder
        (`%s`) and adds the value to the parameters list.

        Args:
            other (Any): The left-hand operand. Can be a :class:`Column`,
                :class:`ColumnsOperation`, or a literal value (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # This will generate SQL: POW(2, "salary")
            >>> op = 2 ** employees.salary
            >>> print(op._output[0])
            'POW(%s , "employees"."salary")'
            >>> print(op._output[1])  # parameters list
            [2]
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'(POW({other._output[0]} , {self._output[0]}))', other._output[1] + self._output[1]) if isinstance(other, ColumnsOperation) else (f'(POW({other.name} , {self._output[0]}))', self._output[1]) if isinstance(other, Column) else (f'(POW(%s , {self._output[0]}))', [other]+self._output[1])
        return new_op

    def __truediv__(self, other):
        """Implement division (/) for SQL expressions.

        This method constructs a SQL division expression where the current
        :class:`ColumnsOperation` is divided by `other`. It handles various operand
        types:
        - If `other` is a :class:`ColumnsOperation`, the expression combines both
        operations with `/`.
        - If `other` is a :class:`Column`, the expression uses the column name.
        - If `other` is a literal value, the expression uses a parameter placeholder
        (`%s`) and adds the value to the parameters list.

        The result is stored internally, allowing method chaining.

        Args:
            other (Any): The right-hand operand. Can be a :class:`Column`,
                :class:`ColumnsOperation`, or a literal value (int, float, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, enabling further chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # This will generate SQL: ("salary" / 1000)
            >>> op = employees.salary / 1000
            >>> print(op._output[0])
            '("employees"."salary" / %s)'
            >>> print(op._output[1])  # parameters list
            [1000]

            >>> # Combining two operations
            >>> total_hours = employees.hours_worked
            >>> avg_hours = total_hours / employees.employee_count
            >>> # SQL: ("hours_worked" / "employee_count")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} / {other._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({self._output[0]} / {other.name})', self._output[1]) if isinstance(other, Column) else (f'({self._output[0]} / %s)', self._output[1]+[other])
        return new_op

    def __rtruediv__(self, other):
        """Implement reflected division (right-hand side division) for SQL expressions.

        This method is called when a :class:`ColumnsOperation` appears on the right
        side of a division operator, e.g., `10 / column_operation`. It constructs
        the SQL expression for dividing `other` by the current operation and stores
        the result internally, allowing method chaining.

        The generated SQL expression will use the appropriate operator:
        - If `other` is a :class:`Column`, the expression uses the column name.
        - If `other` is a :class:`ColumnsOperation`, the expression combines both
        operations.
        - If `other` is a literal value, the expression uses a parameter placeholder
        (`%s`) and adds the value to the parameters list.

        Args:
            other (Any): The left-hand operand (the numerator). Can be a
                :class:`Column`, :class:`ColumnsOperation`, or a literal value
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: (1000 / "salary")
            >>> op = 1000 / employees.salary
            >>> print(op._output[0])
            '(1000 / "employees"."salary")'
            >>> print(op._output[1])  # parameters list
            []
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({other._output[0]} / {self._output[0]})', other._output[1] + self._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} / {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(%s / {self._output[0]})', [other]+self._output[1])
        return new_op

    def __mod__(self, other):
        """Implement the modulo (remainder) operation for SQL expressions.

        This method is called when the `%` operator is used with a
        :class:`ColumnsOperation` on the left side, e.g.,
        `column_operation % 10`. It constructs the SQL expression for taking the
        modulus of the current operation by `other` and stores the result
        internally, allowing method chaining.

        The generated SQL expression will use the appropriate representation:
        - If `other` is a :class:`Column`, the expression uses the column name.
        - If `other` is a :class:`ColumnsOperation`, the expression combines both
        operations.
        - If `other` is a literal value, the expression uses a parameter placeholder
        (`%s`) and adds the value to the parameters list.

        Args:
            other (Any): The right-hand operand (the divisor). Can be a
                :class:`Column`, :class:`ColumnsOperation`, or a literal value
                (int, float, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" % 1000)
            >>> op = employees.salary % 1000
            >>> print(op._output[0])
            '("employees"."salary" % %s)'
            >>> print(op._output[1])
            [1000]
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} % {other._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({self._output[0]} % {other.name})', self._output[1]) if isinstance(other, Column) else (f'({self._output[0]} % %s)', self._output[1]+[other])
        return new_op

    def __rmod__(self, other):
        """Implement reflected modulo (right-hand side modulo) for SQL expressions.

        This method is called when a :class:`ColumnsOperation` appears on the right
        side of a modulo operator, e.g., `10 % column_operation`. It constructs the
        SQL expression for computing the remainder when `other` is divided by the
        current operation and stores the result internally, allowing method chaining.

        The generated SQL expression will use the appropriate representation:
        - If `other` is a :class:`Column`, the expression uses the column name.
        - If `other` is a :class:`ColumnsOperation`, the expression combines both
        operations.
        - If `other` is a literal value, the expression uses a parameter placeholder
        (`%s`) and adds the value to the parameters list.

        Args:
            other (Any): The left-hand operand (the dividend). Can be a
                :class:`Column`, :class:`ColumnsOperation`, or a literal value
                (int, float, str, etc.). Note that modulo with strings is not
                typical; the operator is primarily intended for numeric types.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: (10 % "salary")
            >>> op = 10 % employees.salary
            >>> print(op._output[0])
            '(10 % "employees"."salary")'
            >>> print(op._output[1])  # parameters list
            []
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({other._output[0]} % {self._output[0]})', other._output[1] + self._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} % {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(%s % {self._output[0]})', [other]+self._output[1])
        return new_op


    def __getitem__(self, key: slice):
        """Generate a SQL SUBSTRING expression from a slice operation on a string column.

        This method implements Python's subscript syntax (square brackets) for
        :class:`ColumnsOperation` objects when the associated column is of a string
        type. It translates slice indices into a PostgreSQL `SUBSTRING` function
        that extracts a portion of the column value. The method handles various
        slice configurations including positive, negative, and `None` start/stop
        values, adapting the SQL parameters accordingly.

        The generated SQL and its parameter list are stored in `self._output`,
        allowing this operation to be chained with other column operations or used
        in `WHERE` clauses and `SELECT` expressions.

        Args:
            key (slice): A Python slice object specifying the start and stop
                positions for substring extraction. Both `start` and `stop` can
                be `None`, positive, or negative integers, following Python's
                indexing semantics (0-based). However, PostgreSQL's `SUBSTRING`
                uses 1-based indexing, so the method adjusts indices accordingly.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing method chaining.

        Raises:
            TypeError: If `key` is not a slice object.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Extract first 3 characters of the "name" column
            >>> op = employees.name[:3]
            >>> print(op._output[0])
            '(SUBSTRING("employees"."name" , 1 , %s))'
            >>> print(op._output[1])
            [3]

            >>> op = employees.name[2:]
            >>> print(op._output[0])
            '(SUBSTRING("employees"."name" , %s , LENGTH("employees"."name")))'
            >>> print(op._output[1])
            [3]  # because 2+1

            >>> op = employees.name[-5:]
            >>> print(op._output[0])
            '(SUBSTRING("employees"."name" , LENGTH("employees"."name") - %s , LENGTH("employees"."name")))'
            >>> print(op._output[1])
            [4]  # abs(-5) - 1 = 4

            >>> op = employees.name[1:5].upper()
            >>> print(op._output[0])
            '(UPPER((SUBSTRING("employees"."name" , %s , %s))))'
            >>> print(op._output[1])
            [2, 4]
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op.current_datatype = str
        if self._output:
            if key.start == None and key.stop ==  None:
                new_op._output = (f'(SUBSTRING({self._output[0]} , 1 , LENGTH({self._output[0]}) + 1))', self._output[1] + self._output[1])   #
            elif key.start == None and key.stop < 0:
                new_op._output = (f'(SUBSTRING({self._output[0]} , 1 , LENGTH({self._output[0]}) - %s))', self._output[1] + self._output[1] + [abs(key.stop)])  #
            elif key.start == None and key.stop >= 0:
                new_op._output = (f'(SUBSTRING({self._output[0]} , 1 , %s))', self._output[1] + [key.stop])  #  
            elif key.start >= 0 and key.stop ==  None:
                new_op._output = (f'(SUBSTRING({self._output[0]} , %s , LENGTH({self._output[0]})))', self._output[1] + [key.start + 1] + self._output[1])  #   
            elif key.start < 0 and key.stop == None:
                new_op._output = (f'(SUBSTRING({self._output[0]} , LENGTH({self._output[0]}) - %s , LENGTH({self._output[0]})))', self._output[1] + self._output[1] + [abs(key.start) - 1] + self._output[1])  #
            elif key.start >= 0 and key.stop < 0:
                new_op._output = (f'(SUBSTRING({self._output[0]} , %s , LENGTH({self._output[0]}) - %s))', self._output[1] +  [key.start + 1] + self._output[1] + [abs(key.stop - key.start)])  #  
            elif key.start >= 0 and key.stop > 0:
                new_op._output = (f'(SUBSTRING({self._output[0]} , %s , %s))', self._output[1] + [key.start + 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop < 0:
                new_op._output = (f'(SUBSTRING({self._output[0]} , LENGTH({self._output[0]}) - %s , %s))', self._output[1] + self._output[1] + [abs(key.start) - 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop > 0:
                new_op._output = (f'(SUBSTRING({self._output[0]} , LENGTH({self._output[0]}) - %s ,  %s - (LENGTH({self._output[0]}) - %s)))', self._output[1] + self._output[1] + [abs(key.start) - 1, key.stop] + self._output[1] + [abs(key.start)])
        else:
            if key.start == None and key.stop ==  None:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , 1 , LENGTH({self.col_obj.name}) + 1))', [])   #
            elif key.start == None and key.stop < 0:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , 1 , LENGTH({self.col_obj.name}) - %s))', [abs(key.stop)])  #
            elif key.start == None and key.stop >= 0:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , 1 , %s))', [key.stop])  #  
            elif key.start >= 0 and key.stop ==  None:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , %s , LENGTH({self.col_obj.name})))', [key.start + 1])  #   
            elif key.start < 0 and key.stop == None:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , LENGTH({self.col_obj.name}) - %s , LENGTH({self.col_obj.name})))', [abs(key.start) - 1])  #
            elif key.start >= 0 and key.stop < 0:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , %s , LENGTH({self.col_obj.name}) - %s))', [key.start + 1, abs(key.stop - key.start)])  #  
            elif key.start >= 0 and key.stop > 0:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , %s , %s))', [key.start + 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop < 0:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , LENGTH({self.col_obj.name}) - %s , %s))', [abs(key.start) - 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop > 0:
                new_op._output = (f'(SUBSTRING({self.col_obj.name} , LENGTH({self.col_obj.name}) - %s ,  %s - (LENGTH({self.col_obj.name}) - %s)))', [abs(key.start) - 1, key.stop, abs(key.start)])
        return new_op

    def eq(self, value):
        """Create an equality comparison SQL expression.

        This method generates a SQL equality condition between the current
        column/expression and the provided value. It is equivalent to using the
        `==` operator but provided as an explicit method for clarity in complex
        conditions. The operation mutates the internal `_output` state and returns
        `self` for method chaining.

        Args:
            value (Any): The right-hand side of the equality comparison. Can be a
                :class:`Column` object, a :class:`ColumnsOperation` (for comparing
                two expressions), or a literal value (str, int, float, etc.) or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NULL``.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Simple equality with literal value
            >>> condition = employees.department.eq("Engineering")
            >>> print(condition._output[0])
            '("employees"."department" = %s)'
            >>> print(condition._output[1])  # parameters
            ['Engineering']
            >>>
            >>> # Equality between two columns
            >>> condition = employees.manager_id.eq(employees.id)
            >>> print(condition._output[0])
            '("employees"."manager_id" = "employees"."id")'
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} = {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} = {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} IS NULL)', self._output[1]) if value is None else (f'({self._output[0]} = %s)', self._output[1] + [value])
        return new_op

    def __eq__(self, value):
        """Implement equality comparison for SQL expressions.

        This special method is called when a :class:`ColumnsOperation` instance is
        compared with another value using the `==` operator. It constructs the SQL
        expression for equality (`=`) and stores the result internally, allowing
        method chaining.

        The generated SQL expression will use the appropriate syntax:
        - If `value` is a :class:`Column`, the expression uses the column name.
        - If `value` is a :class:`ColumnsOperation`, the expression combines both
        operations.
        - If `value` is a literal, the expression uses a parameter placeholder
        (`%s`) and adds the value to the parameters list.

        Args:
            value (Any): The right-hand operand. Can be a :class:`Column`,
                :class:`ColumnsOperation`, or a literal value or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NULL``.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" = 50000)
            >>> condition = employees.salary == 50000
            >>> print(condition._output[0])
            '("employees"."salary" = %s)'
            >>> print(condition._output[1])  # parameters list
            [50000]
            >>>
            >>> # Combining with AND
            >>> condition2 = (employees.department == "Engineering") & (employees.salary > 60000)
            >>> print(condition2._output[0])
            '(("employees"."department" = %s) AND ("employees"."salary" > %s))'
            >>> print(condition2._output[1])
            ['Engineering', 60000]
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} = {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} = {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} IS NULL)', self._output[1]) if value is None else (f'({self._output[0]} = %s)', self._output[1] + [value])
        return new_op

    def ne(self, value):
        """Create a SQL inequality comparison (`!=`) for this column operation.

        This method generates a SQL `!=` expression comparing the current operation
        with the provided value. It is the explicit (non-operator) version of
        `__ne__`, useful when the inequality operator cannot be used directly (e.g.,
        in contexts where operator overloading is not supported). The result is
        stored internally, allowing method chaining.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right-hand side of the inequality. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.) or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NOT NULL``.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit inequality: salary != 50000
            >>> op = employees.salary.ne(50000)
            >>> print(op._output[0])
            '("employees"."salary" != %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Chaining with logical operators
            >>> cond = employees.salary.ne(0) & employees.department.ne("IT")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} != {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} != {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} IS NOT NULL)', self._output[1]) if value is None else (f'({self._output[0]} != %s)', self._output[1] + [value])
        return new_op

    def __ne__(self, value):
        """Implement the inequality operator (`!=`) for SQL expressions.

        This special method is called when the `!=` operator is used between a
        :class:`ColumnsOperation` and another operand. It constructs a SQL `!=`
        expression comparing the current operation with the provided value and
        stores the result internally, allowing method chaining.

        The generated SQL expression adapts to the type of `value`:
        - If `value` is a :class:`ColumnsOperation`, both operations are combined.
        - If `value` is a :class:`Column`, the column name is used directly.
        - If `value` is a literal, a parameter placeholder (`%s`) is used, and the
        value is appended to the parameters list.

        Args:
            value (Any): The right-hand side of the inequality. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.) or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NOT NULL``.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" != 50000)
            >>> cond = employees.salary != 50000
            >>> print(cond._output[0])
            '("employees"."salary" != %s)'
            >>> print(cond._output[1])
            [50000]
            >>> # Combine with another condition
            >>> cond2 = (employees.salary != 0) & (employees.department != "IT")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} != {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} != {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} IS NOT NULL)', self._output[1]) if value is None else (f'({self._output[0]} != %s)', self._output[1] + [value])
        return new_op

    def gt(self, value):
        """Create a SQL greater-than comparison (`>`) for this column operation.

        This method generates a SQL `>` expression comparing the current operation
        with the provided value. It is the explicit (non-operator) version of
        `__gt__`, useful when the greater-than operator cannot be used directly (e.g.,
        in contexts where operator overloading is not supported). The result is
        stored internally, allowing method chaining.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit greater-than: salary > 50000
            >>> op = employees.salary.gt(50000)
            >>> print(op._output[0])
            '("employees"."salary" > %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Chaining with logical operators
            >>> cond = employees.salary.gt(0) & employees.department.gt("IT")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} > {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} > {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} > %s)', self._output[1] + [value])
        return new_op

    def __gt__(self, value):
        """Create a SQL greater-than comparison (`>`) for this column operation.

        This method is called when the `>` operator is used with a
        :class:`ColumnsOperation` instance on the left-hand side. It generates the
        SQL expression `operation > other` and stores it internally, allowing
        method chaining. The comparison supports:

        - Another :class:`ColumnsOperation`: combines both SQL expressions.
        - A :class:`Column`: uses the column's fully qualified name.
        - A literal value: uses a parameter placeholder (`%s`) and appends the
        value to the parameter list.

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" > 50000)
            >>> cond = employees.salary > 50000
            >>> print(cond._output[0])
            '("employees"."salary" > %s)'
            >>> print(cond._output[1])
            [50000]
            >>> # Chaining with another column:
            >>> cond2 = employees.bonus > employees.salary * 0.1
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} > {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} > {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} > %s)', self._output[1] + [value])
        return new_op

    def lt(self, value):
        """Create a SQL less-than comparison (`<`) for this column operation.

        This method generates a SQL `<` expression comparing the current operation
        with the provided value. It is the explicit (non-operator) version of
        `__lt__`, useful when the comparison operator cannot be used directly (e.g.,
        in contexts where operator overloading is not supported). The result is
        stored internally, allowing method chaining.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit less-than: salary < 50000
            >>> op = employees.salary.lt(50000)
            >>> print(op._output[0])
            '("employees"."salary" < %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Chaining with logical operators
            >>> cond = employees.salary.lt(100000) & employees.department.lt("ZZZ")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} < {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} < {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} < %s)', self._output[1] + [value])
        return new_op

    def __lt__(self, value):
        """Implement the less-than comparison operator (`<`) for SQL expressions.

        This method is called when a :class:`ColumnsOperation` is compared with
        another value using the `<` operator, e.g., `column_operation < 100`.
        It generates the corresponding SQL `LESS THAN` expression and stores
        the result internally, enabling method chaining and composition with
        logical operators like `&` (AND) and `|` (OR).

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" < 50000)
            >>> cond = employees.salary < 50000
            >>> print(cond._output[0])
            '("employees"."salary" < %s)'
            >>> print(cond._output[1])
            [50000]
            >>> # Combined condition: salary < 50000 AND department != 'IT'
            >>> final_cond = (employees.salary < 50000) & (employees.department != 'IT')
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} < {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} < {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} < %s)', self._output[1] + [value])
        return new_op

    def ge(self, value):
        """Create a SQL 'greater than or equal to' comparison (`>=`) for this column operation.

        This method generates a SQL `>=` expression comparing the current operation
        with the provided value. It is the explicit (non-operator) version of
        `__ge__`, useful when the comparison operator cannot be used directly (e.g.,
        in contexts where operator overloading is not supported). The result is
        stored internally, allowing method chaining.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit greater-or-equal: salary >= 50000
            >>> op = employees.salary.ge(50000)
            >>> print(op._output[0])
            '("employees"."salary" >= %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Chaining with logical operators
            >>> cond = employees.salary.ge(30000) & employees.age.ge(25)
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} >= {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} >= {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} >= %s)', self._output[1] + [value])
        return new_op

    def __ge__(self, value):
        """Implement the greater-than-or-equal-to comparison operator (`>=`) for SQL expressions.

        This method is called when the `>=` operator is used between a
        :class:`ColumnsOperation` and another value (e.g., `op >= other`). It
        constructs a SQL `>=` expression and stores it internally, allowing
        method chaining.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameters list).

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" >= 50000)
            >>> condition = employees.salary >= 50000
            >>> print(condition._output[0])
            '("employees"."salary" >= %s)'
            >>> print(condition._output[1])
            [50000]
            >>> # Chaining with logical operators
            >>> cond = (employees.salary >= 30000) & (employees.age >= 25)
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} >= {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} >= {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} >= %s)', self._output[1] + [value])
        return new_op

    def le(self, value):
        """Create a SQL 'less than or equal to' comparison (`<=`) for this column operation.

        This method generates a SQL `<=` expression comparing the current operation
        with the provided value. It is the explicit (non-operator) version of
        `__le__`, useful when the comparison operator cannot be used directly (e.g.,
        in contexts where operator overloading is not supported). The result is
        stored internally, allowing method chaining.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit less-or-equal: salary <= 50000
            >>> op = employees.salary.le(50000)
            >>> print(op._output[0])
            '("employees"."salary" <= %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Chaining with logical operators
            >>> cond = employees.salary.le(100000) & employees.age.le(65)
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} <= {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} <= {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} <= %s)', self._output[1] + [value])
        return new_op

    def __le__(self, value):
        """Implement the 'less than or equal to' comparison (`<=`) for SQL expressions.

        This special method is called when the `<=` operator is used between a
        :class:`ColumnsOperation` and another value. It generates a SQL `<=`
        expression comparing the current operation with the provided value and
        stores the result internally, allowing method chaining.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right-hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: salary <= 50000
            >>> op = employees.salary <= 50000
            >>> print(op._output[0])
            '("employees"."salary" <= %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Compound condition using logical AND
            >>> cond = (employees.salary <= 50000) & (employees.age <= 30)
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} <= {value._output[0]})', self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} <= {value.name})', self._output[1] if isinstance(self._output[1], list) else [self._output[1]]) if isinstance(value, Column) else (f'({self._output[0]} <= %s)', self._output[1] + [value])
        return new_op

    def __and__(self, value):
        """Combine two conditions with a SQL `AND` operator.

        This method implements the bitwise AND operator (`&`) for
        :class:`ColumnsOperation` objects. When used with another
        :class:`ColumnsOperation`, it generates a SQL expression that combines
        both conditions with `AND`. The resulting expression can be used as a
        `WHERE` clause in queries.

        The operation is performed on the internal `_output` state, which is
        updated to contain the new SQL fragment and its parameters.

        Args:
            value (ColumnsOperation): The right-hand side condition to combine
                with the current condition.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations (e.g., `(col1 == 1) & (col2 == 2)`).

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Build a compound condition: salary >= 50000 AND department = 'Engineering'
            >>> cond = (employees.salary >= 50000) & (employees.department == "Engineering")
            >>> # Use the condition in a query
            >>> employees.get_row([employees.name], where=cond)
            # This generates SQL: ... WHERE (("salary" >= %s) AND ("department" = %s))
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} AND {value._output[0]})', self._output[1] + value._output[1])
        return new_op

    def __or__(self, value):
        """Implement logical OR for SQL conditions.

        This method is called when the `|` operator is used between two
        :class:`ColumnsOperation` instances. It generates a SQL expression
        combining the left and right conditions with an `OR` operator, allowing
        complex boolean logic in WHERE clauses.

        The result is stored internally as a tuple `(sql_string, parameters_list)`,
        enabling method chaining for further logical combinations or comparisons.

        Args:
            value (ColumnsOperation): The right-hand side operation to combine
                with the current operation using logical OR.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
                state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees who are either managers or have salary > 100000
            >>> cond = (employees.title == "Manager") | (employees.salary > 100000)
            >>> print(cond._output[0])
            '(("employees"."title" = %s) OR ("employees"."salary" > %s))'
            >>> print(cond._output[1])  # parameters: ["Manager", 100000]
            ['Manager', 100000]

        Note:
            The method assumes `value` is another `ColumnsOperation`. Combining
            with other types is not supported for `__or__`; use explicit method
            calls or wrap literals appropriately.
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} OR {value._output[0]})', self._output[1] + value._output[1])
        return new_op

    def like(self, value):
        """Create a SQL `LIKE` pattern matching expression for this column operation.

        This method generates a SQL `LIKE` expression that compares the current
        column operation against a pattern. It supports patterns from:
            - Another :class:`ColumnsOperation` (e.g., concatenated strings).
            - A :class:`Column` (using the column's name).
            - A literal string value (using a parameter placeholder `%s`).

        The result is stored internally, allowing the expression to be used in
        `WHERE` clauses or combined with other conditions. The method is chainable.

        Args:
            value (Any): The pattern to match against. Can be a :class:`ColumnsOperation`,
                :class:`Column`, or a literal string.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose names start with 'A'
            >>> cond = employees.name.like('A%')
            >>> # Using a ColumnsOperation for more complex patterns
            >>> prefix = employees.name.upper() + '%'
            >>> cond = employees.name.like(prefix)
            >>> # Combining with other conditions
            >>> final_cond = cond & (employees.salary > 50000)
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f"({self._output[0]} like {value._output[0]})", (self._output[1] + value._output[1]) if self._output[0] else value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self._output[0]} like {value.name})', self._output[1]) if isinstance(value , Column) else (f'({self._output[0]} like %s)', self._output[1] + [f'{value}'])
        return new_op

    def startswith(self, prefix):
        """Just like python startswith(), create a SQL `LIKE` expression that checks if the column starts with a given prefix.

        This method generates a `LIKE` pattern that matches strings beginning with the
        specified prefix. It appends `'%'` to the prefix to match any trailing characters.
        The result is stored internally, allowing the expression to be used in `WHERE`
        clauses or combined with other conditions.

        The prefix can be:
            - Another :class:`ColumnsOperation` (e.g., concatenated expressions).
            - A :class:`Column` (using the column's value).
            - A literal string (using a parameter placeholder `%s`).

        Args:
            prefix (Any): The prefix to match. Can be a :class:`ColumnsOperation`,
                :class:`Column`, or a literal string.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose names start with 'A'
            >>> cond = employees.name.startswith('A')
            >>> # Using a ColumnsOperation for a dynamic prefix
            >>> prefix_col = employees.name.upper()
            >>> cond = employees.name.startswith(prefix_col)
            >>> # Combine with other conditions
            >>> final = cond & (employees.salary > 50000)
            >>> # The generated SQL will be like: ("employees"."name" LIKE 'A%')
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f"({self._output[0]} like {prefix._output[0]} || '%%')", (self._output[1] + prefix._output[1]) if self._output[0] else prefix._output[1]) if isinstance(prefix, ColumnsOperation) else (f"({self._output[0]} like {prefix.name} || '%%')", self._output[1]) if isinstance(prefix , Column) else (f"({self._output[0]} like %s || '%%')", self._output[1] + [f'{prefix}'])
        return new_op

    def endswith(self, suffix):
        """Just like python endswith(), create a SQL LIKE pattern matching expression for strings ending with a given suffix.

        This method generates a SQL `LIKE` expression that checks whether the current
        column operation's value ends with the specified suffix. The generated SQL
        uses `LIKE '%%' || suffix` to match strings that end with the suffix. The
        suffix can be a literal string, a :class:`Column`, or a :class:`ColumnsOperation`
        for dynamic values.

        The result is stored internally and can be chained with other operations.

        Args:
            suffix (Any): The suffix to match. Can be a :class:`ColumnsOperation`,
                :class:`Column`, or a literal string value.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose names end with 'son'
            >>> cond = employees.name.endswith('son')
            >>> # Using a column as suffix
            >>> cond = employees.name.endswith(employees.suffix_column)
            >>> # Combine with other conditions
            >>> final = cond & (employees.salary > 50000)
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f"({self._output[0]} like '%%' || {suffix._output[0]})", (self._output[1] + suffix._output[1]) if self._output[0] else suffix._output[1]) if isinstance(suffix, ColumnsOperation) else (f"({self._output[0]} like '%%' || {suffix.name})", self._output[1]) if isinstance(suffix , Column) else (f"({self._output[0]} like '%%' || %s)", self._output[1] + [f'{suffix}'])
        return new_op

    def contains(self, value):
        """Create a SQL `LIKE` pattern matching expression that checks if the current
        column operation contains the given value as a substring.

        This method generates a SQL `LIKE` expression with wildcards on both sides of
        the value: `'%%' || value || '%%'`. This is equivalent to checking if the
        column value contains the specified substring anywhere within it.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal string value (using a parameter placeholder `%s`).

        The result is stored internally, allowing method chaining.

        Args:
            value (Any): The substring to search for. Can be a :class:`ColumnsOperation`,
                :class:`Column`, or a literal string.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose names contain 'Smith'
            >>> cond = employees.name.contains("Smith")
            >>> # Using a column for the pattern (case-insensitive)
            >>> pattern = employees.last_name.lower()
            >>> cond = employees.first_name.contains(pattern)
            >>> # Combine with other conditions
            >>> final_cond = cond & (employees.department == "Sales")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f"({self._output[0]} like '%%' || {value._output[0]} || '%%')", (self._output[1] + value._output[1]) if self._output[0] else value._output[1]) if isinstance(value, ColumnsOperation) else (f"({self._output[0]} like '%%' || {value.name} || '%%')", self._output[1]) if isinstance(value , Column) else (f"({self._output[0]} like '%%' || %s || '%%')", self._output[1] + [f'{value}'])
        return new_op

    def add_end(self, content):
        """Concatenate additional content to the end of the current SQL expression.

        This method generates a SQL concatenation expression using the `||` operator
        (string concatenation). It appends the provided `content` to the end of the
        current column operation. This is useful for building dynamic SQL strings
        such as constructing full names, adding suffixes, or assembling text values.

        The `content` can be:
            - Another :class:`ColumnsOperation` (the two expressions are concatenated).
            - A :class:`Column` (the column's name is used as the right operand).
            - A literal value (inserted as a parameter placeholder `%s`).

        The method modifies the internal `_output` state and returns `self` for
        method chaining.

        Args:
            content (Any): The content to append to the current expression.
                Can be a :class:`ColumnsOperation`, :class:`Column`, or a literal
                (str, int, etc.). For non-string literals, the value is converted
                to a string for concatenation.

        Returns:
            ColumnsOperation: The current instance with updated SQL expression and
            parameters, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Add a suffix to names
            >>> op = employees.name.add_end(" (Retired)")
            >>> print(op._output[0])
            '("employees"."name" || %s)'
            >>> print(op._output[1])
            [' (Retired)']
            >>> # Chain with another column
            >>> full_name = employees.first_name.add_end(" ").add_end(employees.last_name)
            >>> # Generates: (("first_name" || ' ') || "last_name")
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} || {content._output[0]})', self._output[1]+content._output[1] if self._output[0] else content._output[1]) if isinstance(content, ColumnsOperation) else (f'({self._output[0]} || {content.name})', self._output[1] if self._output[0] else []) if isinstance(content, Column) else (f'({self._output[0]} || %s)', self._output[1]+[content] if self._output[0] else [content])
        new_op.current_datatype = str
        return new_op

    def add_first(self, content):
        """Prepend content to the current string expression (SQL concatenation).

        This method generates a SQL string concatenation expression where the
        provided `content` is placed before the current column operation. The
        result is stored internally and the instance is returned for chaining.

        The `content` can be:
            - Another :class:`ColumnsOperation` (the expression is concatenated).
            - A :class:`Column` (the column name is used).
            - A literal value (a parameter placeholder `%s` is used and the value
            is added to the parameter list).

        The SQL operator used is `||`, which is the standard string concatenation
        operator in PostgreSQL.

        Args:
            content (Any): The content to prepend. Can be a :class:`ColumnsOperation`,
                :class:`Column`, or a literal value (str, int, etc.).

        Returns:
            ColumnsOperation: The current instance with updated `_output`,
            allowing method chaining.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Prepend a prefix to the name column: 'Mr. ' || name
            >>> op = employees.name.add_first("Mr. ")
            >>> print(op._output[0])
            '(%s || "employees"."name")'
            >>> print(op._output[1])
            ['Mr. ']
            >>> # Chain with other operations
            >>> op = employees.name.lower().add_first("Prefix: ")
            >>> # Generates SQL: (%s || LOWER("employees"."name"))
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({content._output[0]} || {self._output[0]})', content._output[1]+self._output[1] if self._output[0] else content._output[1]) if isinstance(content, ColumnsOperation) else (f'({content.name} || {self._output[0]})', self._output[1] if self._output[0] else []) if isinstance(content, Column) else (f'(%s || {self._output[0]})', [content]+self._output[1] if self._output[0] else [content])
        new_op.current_datatype = str
        return new_op

    def replace(self, old: str, new: str):
        """Just like python replace(), generate a SQL `REPLACE` function call for string substitution.

        This method constructs a SQL `REPLACE` expression that substitutes all
        occurrences of `old` with `new` in the current column or operation.
        The result is stored internally, allowing chained operations.

        If the current `_output` is already set (i.e., this is a chained operation),
        the REPLACE is applied to the existing expression. If not, it is applied
        directly to the underlying column.

        Args:
            old (str): The substring to be replaced.
            new (str): The substring to replace with.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Replace 'old' with 'new' in the name column
            >>> op = employees.name.replace('old', 'new')
            >>> print(op._output[0])
            '(REPLACE("employees"."name" , %s , %s))'
            >>> print(op._output[1])
            ['old', 'new']
            >>> # Chain with other operations
            >>> op2 = employees.name.upper().replace('A', 'B')
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'(REPLACE({self._output[0]} , %s , %s))', self._output[1] + [old, new]) if self._output[0] else (f'(REPLACE({self.col_obj.name} , %s , %s))', [old, new])
        new_op.current_datatype = str
        return new_op

    def upper(self):
        """Generate a SQL `UPPER` function call to convert the expression to uppercase.

        This method constructs a SQL `UPPER` expression that converts the current
        column or operation to uppercase. The result is stored internally, allowing
        chained operations. If the current `_output` is already set (i.e., this is a
        chained operation), the UPPER is applied to the existing expression.
        Otherwise, it is applied directly to the underlying column.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Convert name to uppercase for case-insensitive comparison
            >>> op = employees.name.upper() == 'JOHN'
            >>> print(op._output[0])
            '(UPPER("employees"."name") = %s)'
            >>> print(op._output[1])
            ['JOHN']
            >>> # Chain with other string operations
            >>> op2 = employees.name.strip().upper()
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'(UPPER({self._output[0]}))', self._output[1]) if self._output[0] else (f'(UPPER({self.col_obj.name}))', [])
        new_op.current_datatype = str
        return new_op

    def lower(self):
        """just like python lower(), generate a SQL `LOWER` function call to convert text to lowercase.

        This method constructs a SQL `LOWER` expression that transforms the current
        column or operation result to lowercase. The result is stored internally,
        allowing chained operations.

        If the current `_output` is already set (i.e., this is a chained operation),
        the `LOWER` is applied to the existing expression. If not, it is applied
        directly to the underlying column.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Convert name to lowercase
            >>> op = employees.name.lower()
            >>> print(op._output[0])
            '(LOWER("employees"."name"))'
            >>> # Chain with other operations
            >>> op2 = employees.name.upper().lower()  # upper then lower
            >>> print(op2._output[0])
            'LOWER(UPPER("employees"."name"))'
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'(LOWER({self._output[0]}))', self._output[1]) if self._output[0] else (f'(LOWER({self.col_obj.name}))', [])
        new_op.current_datatype = str
        return new_op

    def strip(self, chars: str = ' '):
        """just like python strip(), generate a SQL `TRIM` function call to remove characters from both ends.

        This method creates a SQL `TRIM(BOTH ... FROM ...)` expression that strips
        the specified characters from the start and end of the current column or
        operation. If the `_output` is already set (chained operation), the TRIM is
        applied to that expression; otherwise, it is applied to the underlying column.
        The result is stored internally, allowing further method chaining.

        Args:
            chars (str, optional): The characters to remove. Defaults to a single space.

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Remove leading/trailing spaces from the name column
            >>> op = employees.name.strip()
            >>> print(op._output[0])
            "(TRIM(BOTH ' ' FROM \"employees\".\"name\"))"
            >>> # Remove specific characters after an upper() operation
            >>> op = employees.name.upper().strip('_')
            >>> print(op._output[0])
            "(TRIM(BOTH '_' FROM UPPER(\"employees\".\"name\")))"
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f"(TRIM(BOTH '{chars}' FROM {self._output[0]}))", self._output[1]) if self._output[0] else (f"(TRIM(BOTH '{chars}' FROM {self.col_obj.name}))", [])
        new_op.current_datatype = str
        return new_op

    def lstrip(self, chars: str = ' '):
        """just like python's lstrip method, generate a SQL `TRIM(LEADING ...)` expression to remove leading characters.

        This method constructs a SQL `TRIM` function call that strips the specified
        leading characters from the current column or operation. The result is stored
        internally, allowing chained operations.

        If the current `_output` is already set (i.e., this is a chained operation),
        the trimming is applied to the existing expression. Otherwise, it is applied
        directly to the underlying column. The default character to strip is a space.

        Args:
            chars (str, optional): The character(s) to strip from the left side of
                the string. Defaults to a single space (`' '`).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Remove leading spaces from the name column
            >>> op = employees.name.lstrip()
            >>> print(op._output[0])
            "(TRIM(LEADING ' ' FROM \"employees\".\"name\"))"
            >>> # Remove leading '#' characters from a computed expression
            >>> op = (employees.code + employees.suffix).lstrip('#')
            >>> print(op._output[0])
            "(TRIM(LEADING '#' FROM (\"employees\".\"code\" || \"employees\".\"suffix\")))"
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f"(TRIM(LEADING '{chars}' FROM {self._output[0]}))", self._output[1]) if self._output[0] else (f"(TRIM(LEADING '{chars}' FROM {self.col_obj.name}))", [])
        new_op.current_datatype = str
        return new_op

    def rstrip(self, chars: str = ' '):
        """just like python's rstrip method, generate a SQL `TRIM` expression to remove trailing characters from a string.

        This method constructs a SQL `TRIM(TRAILING ... FROM ...)` expression that
        removes all occurrences of the specified characters from the end (right side)
        of the current column or operation. The result is stored internally, allowing
        chained operations.

        If the current `_output` is already set (i.e., this is a chained operation),
        the `TRIM` is applied to the existing expression. If not, it is applied
        directly to the underlying column.

        Args:
            chars (str, optional): The characters to remove from the trailing end.
                Defaults to a single space (`' '`).

        Returns:
            ColumnsOperation: The current instance with updated internal `_output`
            state, allowing chained operations.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Remove trailing spaces from the name column
            >>> op = employees.name.rstrip()
            >>> print(op._output[0])
            "(TRIM(TRAILING ' ' FROM \"employees\".\"name\"))"
            >>> print(op._output[1])
            []
            >>> # Remove trailing underscores and chain with upper()
            >>> op2 = employees.name.rstrip('_').upper()
            >>> print(op2._output[0])
            "(UPPER(TRIM(TRAILING '_' FROM \"employees\".\"name\")))"
            >>> print(op2._output[1])
            []
        """
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f"(TRIM(TRAILING '{chars}' FROM {self._output[0]}))", self._output[1]) if self._output[0] else (f"(TRIM(TRAILING '{chars}' FROM {self.col_obj.name}))", [])
        new_op.current_datatype = str
        return new_op

    def In(self, column: 'Ormophine.Postgresql.Column|Ormophine.Postgresql.ColumnsOperation' = None, where: 'Ormophine.Postgresql.ColumnsOperation' = None, data_list: list = None):
        """Build an SQL ``IN`` clause for the current column expression.

        This method supports two distinct modes for generating an ``IN`` clause:

        * **Literal list mode**: When ``data_list`` is provided, generates a
        parameterised ``IN (%s, %s, ...)`` clause using the literal values.
        For backward compatibility, if a list of plain values is passed as
        the first positional argument (``column``), it is automatically
        treated as ``data_list``.
        * **Subquery mode**: When ``column`` is provided as a single
        :class:`Column` or :class:`ColumnsOperation`, builds an
        ``IN (SELECT ...)`` subquery. The table name is extracted from the
        provided column object, and an optional ``where`` condition can be
        applied inside the subquery — handled identically to
        :meth:`Table.get_row`.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            column: A single :class:`Column` or :class:`ColumnsOperation` to
                use in the ``SELECT`` clause of the subquery. The table name
                is determined from this object. Do not pass a list of columns;
                if you need multiple conditions, chain them using ``&`` or
                ``|``. If a list of literals is passed, it is treated as
                ``data_list``.
            where: An optional :class:`ColumnsOperation` (or :class:`Column`
                for boolean columns) representing the ``WHERE`` condition for
                the subquery. Defaults to ``None``.
            data_list: A list of literal values for a direct ``IN`` clause.
                When provided, ``column`` and ``where`` are ignored.

        Returns:
            ColumnsOperation: The current instance with its ``_output``
            updated to represent the ``IN`` clause. This allows method chaining.

        Raises:
            Exception: If neither ``data_list`` nor a valid ``column``
                is provided.

        Example:
            Assuming ``users`` and ``admins`` tables::

                from ormophine.Postgresql import Driver

                db = Driver("localhost", 5432, "user", "pass", "mydb")
                users = db.users
                admins = db.admins

                # Literal list mode (backward compatible)
                expr1 = users.name.In(['Alice', 'Bob'])
                # expr1._output[0] -> '("users"."name" IN (%s, %s))'
                # expr1._output[1] -> ['Alice', 'Bob']

                # Literal list mode (using keyword)
                expr2 = users.name.In(data_list=['Alice', 'Bob'])

                # Subquery mode with WHERE
                expr3 = users.name.In(
                    column=admins.username,
                    where=admins.active == True
                )
                # expr3._output[0] ->
                #   '("users"."name" IN (SELECT "admins"."username" FROM "admins" WHERE ("admins"."active" = %s)))'
                # expr3._output[1] -> [True]

                # Subquery mode without WHERE
                expr4 = users.name.In(column=admins.username)
                # expr4._output[0] ->
                #   '("users"."name" IN (SELECT "admins"."username" FROM "admins"))'

                # Using the result in a query
                rows = users.get_row([users.name], where=expr1)
        """
        if isinstance(column, list):
            data_list, column = column, None #So user can simply In(['Alice', 'Bob']) with out passing arguments
        if not column and not data_list:
            raise Exception("In() requires either data_list or column")
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} IN ({", ".join(["%s" for _ in data_list])}))', self._output[1] + data_list) if data_list is not None else (f'({self._output[0]} IN (SELECT {column.name if isinstance(column, Column) else column._output[0]} FROM {(column.name if isinstance(column, Column) else column.col_obj.name).split(".")[0]}{f" WHERE {where._output[0]}" if isinstance(where, ColumnsOperation) else f" WHERE {where.name}" if isinstance(where, Column) else ""}))', self._output[1] + ([] if isinstance(column, Column) else column._output[1]) + (where._output[1] if isinstance(where, ColumnsOperation) else [])) if isinstance(column, (Column, ColumnsOperation)) else None
        
        return new_op

    def not_In(self, column: Column|ColumnsOperation = None, where: ColumnsOperation = None, data_list: list = None):
        """Build an SQL ``NOT IN`` clause for the current column expression.

        This method generates a condition that checks whether the column's value
        is **not** present in a given list of literals or in the result of a
        subquery. The behaviour mirrors :meth:`In` but with the negation operator
        ``NOT IN``.

        Two modes are supported:

        * **Literal list mode**: When ``data_list`` is provided, generates a
        parameterised ``NOT IN (%s, %s, ...)`` clause using the literal values.
        For backward compatibility, if a list of plain values is passed as the
        first positional argument (``column``), it is automatically treated as
        ``data_list``.
        * **Subquery mode**: When ``column`` is provided as a single
        :class:`Column` or :class:`ColumnsOperation`, builds a
        ``NOT IN (SELECT ...)`` subquery. The table name is extracted from the
        provided column object, and an optional ``where`` condition can be
        applied inside the subquery — handled identically to
        :meth:`Table.get_row`.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            column: A single :class:`Column` or :class:`ColumnsOperation`
                to use in the ``SELECT`` clause of the subquery. The table name
                is determined from this object. Do not pass a list of columns;
                if you need multiple conditions, chain them using ``&`` or ``|``.
                If a list of literals is passed, it is treated as ``data_list``.
            where: An optional :class:`ColumnsOperation` (or :class:`Column`
                for boolean columns) representing the ``WHERE`` condition for
                the subquery. Defaults to ``None``.
            data_list: A list of literal values for a direct ``NOT IN`` clause.
                When provided, ``column`` and ``where`` are ignored.

        Returns:
            ColumnsOperation: The current instance with its ``_output``
            updated to represent the ``NOT IN`` clause. This allows method
            chaining.

        Raises:
            Exception: If neither ``data_list`` nor a valid ``column``
                is provided.

        Example:
            Assuming ``users`` and ``admins`` tables::

                from ormophine.Postgresql import Driver

                db = Driver("localhost", 5432, "user", "pass", "mydb")
                users = db.users
                admins = db.admins

                # Literal list mode (backward compatible)
                expr1 = users.name.not_In(['Alice', 'Bob'])
                # expr1._output[0] -> '("users"."name" NOT IN (%s, %s))'
                # expr1._output[1] -> ['Alice', 'Bob']

                # Literal list mode (using keyword)
                expr2 = users.name.not_In(data_list=['Alice', 'Bob'])

                # Subquery mode with WHERE
                expr3 = users.name.not_In(
                    column=admins.username,
                    where=admins.active == True
                )
                # expr3._output[0] ->
                #   '("users"."name" NOT IN (SELECT "admins"."username" FROM "admins" WHERE ("admins"."active" = %s)))'

                # Subquery mode without WHERE
                expr4 = users.name.not_In(column=admins.username)
                # expr4._output[0] ->
                #   '("users"."name" NOT IN (SELECT "admins"."username" FROM "admins"))'
        """
        if isinstance(column, list):
            data_list, column = column, None #So user can simply In(['Alice', 'Bob']) with out passing arguments
        if not column and not data_list:
            raise Exception("In() requires either data_list or column")
        new_op = ColumnsOperation(self.col_obj)
        new_op._output = (f'({self._output[0]} NOT IN ({", ".join(["%s" for _ in data_list])}))', self._output[1] + data_list) if data_list is not None else (f'({self._output[0]} NOT IN (SELECT {column.name if isinstance(column, Column) else column._output[0]} FROM {(column.name if isinstance(column, Column) else column.col_obj.name).split(".")[0]}{f" WHERE {where._output[0]}" if isinstance(where, ColumnsOperation) else f" WHERE {where.name}" if isinstance(where, Column) else ""}))', self._output[1] + ([] if isinstance(column, Column) else column._output[1]) + (where._output[1] if isinstance(where, ColumnsOperation) else [])) if isinstance(column, (Column, ColumnsOperation)) else None
        
        return new_op

    def If(self, condition):
        """
        Start a one-line conditional expression: ``then if cond else other``.

        Returns a :class:`_IfThenBuilder`. Chain ``.Else(value)`` to obtain
        a usable :class:`ColumnsOperation`. Any attempt to read ``_output``
        on the builder without chaining ``.Else()`` raises a clear
        :class:`RuntimeError`.

        Args:
            condition: A :class:`ColumnsOperation`, a :class:`Column`, or a
                raw Python value (``True``, ``1``, ``'x'``, ...). Raw values
                are wrapped in :class:`LiteralValue` automatically.

        Returns:
            _IfThenBuilder: Intermediate builder; call ``.Else(value)`` next.

        Example:
            >>> users.name.If(users.is_active == True).Else('inactive')
            # SQL: (CASE WHEN ("users"."is_active" = %s) THEN "users"."name" ELSE %s END)
        """
        return _IfThenBuilder(
            self,
            condition if isinstance(condition, (ColumnsOperation, Column)) else LiteralValue(condition)
        )


class LiteralValue(ColumnsOperation):
    """
    Wrap a raw Python value inside a ColumnsOperation-shaped object.

    Used when a plain literal must be the left-hand side of a conditional
    (``LiteralValue('n/a').If(cond).Else(col)``) or any other expression
    chain. Subclasses :class:`ColumnsOperation` but deliberately does
    **not** call ``super().__init__()`` — the parent constructor would
    reset ``_output`` to ``('', [])`` and erase the wrapped value.

    Attributes:
        _output (tuple[str, list]): Always ``('%s', [wrapped_value])``.
        col_obj (_NullCol): Stub in place of a real parent Column.
        current_datatype (type): ``type(wrapped_value)``.
    """

    def __init__(self, value):
        """
        Wrap a Python value in a ColumnsOperation-compatible shell.

        Args:
            value: Any value that psycopg can bind later.

        Example:
            >>> LiteralValue('Alice')._output
            ('%s', ['Alice'])
            >>> LiteralValue(42)._output
            ('%s', [42])
        """
        self._output          = ('%s', [value])
        self.col_obj          = _NullCol
        self.current_datatype = type(value)


class _IfThenBuilder:
    """
    Intermediate builder returned by :meth:`ColumnsOperation.If`.

    The ``_output`` attribute is a read-only property that always raises
    :class:`RuntimeError` — this is what guarantees that a forgotten
    ``.Else()`` is caught before any SQL reaches the database.

    SQL syntax note:
        PostgreSQL has no ``IIF`` function, so the ternary is rendered as
        ``CASE WHEN cond THEN then ELSE else END``.

    Attributes:
        _then (ColumnsOperation): The "then" branch expression.
        _cond (ColumnsOperation | Column): The condition.
    """

    def __init__(self, then_branch, condition):
        self._then = then_branch
        self._cond = condition

    @property
    def _output(self):
        """Always raise — forces the user to chain ``.Else()``."""
        raise RuntimeError(
            "You called `.If(condition)` but never chained `.Else(value)`. "
            "Complete the conditional, e.g. `col.If(cond).Else(other)`."
        )

    def Else(self, value):
        """
        Complete the conditional with the "else" branch.

        Produces ``(CASE WHEN cond THEN then ELSE else END)`` with all
        parameters concatenated in the order: cond → then → else.

        Args:
            value: A :class:`ColumnsOperation`, :class:`Column`, or a raw
                Python value.

        Returns:
            ColumnsOperation: The fully-formed expression.
        """
        new_op = ColumnsOperation(self._then.col_obj)
        new_op._output = (
            f'(CASE WHEN {self._cond._output[0]} THEN {self._then._output[0]} ELSE {value._output[0]} END)',
            self._cond._output[1] + self._then._output[1] + value._output[1]
        ) if isinstance(value, ColumnsOperation) else (
            f'(CASE WHEN {self._cond._output[0]} THEN {self._then._output[0]} ELSE {value.name} END)',
            self._cond._output[1] + self._then._output[1]
        ) if isinstance(value, Column) else (
            f'(CASE WHEN {self._cond._output[0]} THEN {self._then._output[0]} ELSE %s END)',
            self._cond._output[1] + self._then._output[1] + [value]
        )
        new_op.current_datatype = None
        return new_op

    
class Column:
    """
    A database column representation with expression-building capabilities.

    This class represents a column in a database table. It stores the column's
    fully qualified name, its Python datatype, and a reference to its parent
    :class:`Table`. The primary purpose of a `Column` object is to serve as a
    starting point for building SQL expressions using operator overloading and
    method chaining. When you perform operations like `employees.salary + 100`,
    the `Column` object delegates to a :class:`ColumnsOperation` to build the
    corresponding SQL fragment, which can then be used in `WHERE` clauses,
    `SELECT` lists, `UPDATE` assignments, and more.

    The class supports:
    - Arithmetic operations (+, -, *, /, %, **) with automatic selection of
      string concatenation (`||`) vs. numeric addition (`+`) based on datatype.
    - Comparison operators (==, !=, <, <=, >, >=) via operator overloading.
    - String methods: `like()`, `startswith()`, `endswith()`, `contains()`,
      `upper()`, `lower()`, `replace()`, `strip()`, `lstrip()`, `rstrip()`,
      and slice notation for `SUBSTRING`.
    - Collection methods: `In()` for `IN` clauses.
    - Concatenation helpers: `add_end()`, `add_first()`.
    - DDL operations: `rename()` and `delete_column()` (with safety flags).

    Instances of `Column` are automatically created by the :class:`Table` class
    when it initializes, and are attached as attributes to the `Table` object
    (e.g., `employees.name`). You typically do not instantiate `Column` directly.

    Attributes:
        name (str): The fully qualified column name, including the table name
            and quoted identifier (e.g., `"employees"."salary"`). Used in SQL
            generation.
        first_name (str): The quoted column name without the table prefix
            (e.g., `"salary"`). Used in DDL statements and in contexts where the
            table is already specified.
        table_obj (Table): The parent :class:`Table` object that this column
            belongs to.
        datatype (type): The Python type that corresponds to the column's SQL
            data type (e.g., `int`, `str`, `float`, `bool`, `bytes`). Used to
            choose the correct SQL operator for addition (`+` for numeric,
            `||` for string concatenation).

    Example:
        >>> from ormophine.Postgresql import Driver, Table
        >>> driver = Driver(...)
        >>> employees = driver.employees
        >>> # Access a column (automatically created)
        >>> salary_col = employees.salary
        >>> print(salary_col.name)
        '"employees"."salary"'
        >>>
        >>> # Build an expression
        >>> cond = employees.salary > 50000
        >>> print(cond._output[0])
        '("employees"."salary" > %s)'
        >>> # Use in a query
        >>> results = employees.get_row([employees.name], where=cond)
        >>>
        >>> # String operations
        >>> upper_name = employees.name.upper()
        >>> starts_with_a = employees.name.startswith('A')
        >>>
        >>> # DDL: rename a column (requires confirmation flags on Table)
        >>> # employees.rename_column(employees.salary, "base_salary")
    """
    def __init__(self, table_obj: Table, column_name: str, datatype: type):
        """Initialize a Column instance representing a database column.

        This constructor creates a column object that references a specific table
        and column in the database. It stores the column's fully qualified name
        (including the table name), a simplified quoted name for use in SQL
        statements, and its Python datatype. Column objects are typically created
        automatically when a :class:`Table` is instantiated and are accessible as
        attributes of the table object.

        The `name` attribute is used in generated SQL to qualify the column with
        its table, ensuring unambiguous references in JOINs and complex queries.
        The `first_name` attribute provides the column name alone, quoted, which is
        used in contexts where the table is already specified (e.g., SET clauses).

        Args:
            table_obj (Table): The Table object that this column belongs to.
            column_name (str): The name of the column in the database.
            datatype (type): The Python type corresponding to the column's SQL data
                type (e.g., int, str, float, bool, bytes).

        Returns:
            None: This method initializes the instance and does not return a value.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Columns are automatically created as attributes:
            >>> print(employees.name)  # Column object
            >>> # Manual creation (typically not needed):
            >>> from ormophine.Postgresql import Column
            >>> col = Column(employees, "salary", int)
            >>> print(col.name)
            '"employees"."salary"'
            >>> print(col.first_name)
            '"salary"'
            >>> print(col.datatype)
            <class 'int'>
        """
        self.name = table_obj.name_ + '."' + column_name + '"'
        self.first_name = f'"{column_name}"'
        self.table_obj = table_obj
        self.datatype = datatype

    def __hash__(self):
        """Compute the hash value for this column object.

        This method enables :class:`Column` objects to be used as keys in
        dictionaries and sets. The hash is based on the column's fully qualified
        name (including the table name), which uniquely identifies a column
        within a database session.

        Returns:
            int: The hash value of the column's full name.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> col = employees.id
            >>> hash(col)  # Returns a hash based on '"employees"."id"'
            >>> # Columns can be used in sets:
            >>> {employees.id, employees.name}
        """
        return hash(self.name)

    def __add__(self, value):
        """Implement column addition or string concatenation.

        This method overloads the `+` operator for :class:`Column` objects.
        It creates a :class:`ColumnsOperation` instance initialized with this column,
        then delegates to the operation's `__add__` method to combine it with `value`.
        The resulting expression will use `+` for numeric columns or `||` for
        string/text columns (based on the column's datatype) when generating SQL.

        Args:
            value (Any): The right-hand operand. Can be a :class:`Column`,
                a :class:`ColumnsOperation`, a numeric value, a string, etc.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` representing the
            SQL expression for the addition or concatenation.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Numeric addition: salary + bonus
            >>> expr = employees.salary + employees.bonus
            >>> # String concatenation: first_name + ' ' + last_name
            >>> full_name = employees.first_name + ' ' + employees.last_name
            >>> # Use the expression in a query
            >>> results = employees.get_row([expr], where=employees.id == 1)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob + value

    def __radd__(self, value):
        """Implement reflected addition (right-hand side addition) for a column.

        This method is called when a :class:`Column` appears on the right side of an
        addition operator, e.g., `100 + employees.salary` or `'prefix ' + employees.name`.
        It creates a :class:`ColumnsOperation` object for the column and then performs
        the addition with the given value.

        The operator used depends on the column's datatype:
        - If the column is a string (`str`), the SQL `||` concatenation operator is used.
        - Otherwise, the SQL `+` addition operator is used.

        The result is a :class:`ColumnsOperation` that can be used in queries or
        further chained operations.

        Args:
            value (Any): The left-hand operand of the addition. Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A new :class:`ColumnsOperation` representing the
            addition expression.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Numeric addition
            >>> op = 1000 + employees.salary
            >>> print(op._output[0])  # SQL expression
            '(1000 + "employees"."salary")'
            >>> print(op._output[1])  # parameters
            []
            >>>
            >>> # String concatenation
            >>> op = 'Name: ' + employees.name
            >>> print(op._output[0])
            '(%s || "employees"."name")'
            >>> print(op._output[1])
            ['Name: ']
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value + temp_ob

    def __sub__(self, value):
        """Implement subtraction of a value from this column.

        This method overloads the `-` operator for :class:`Column` objects.
        It creates a :class:`ColumnsOperation` that represents the SQL expression
        `column - value`. The operation is chainable and can be used in `WHERE`
        clauses, `SET` expressions, or as part of larger computations.

        The subtraction is always numeric (using the `-` operator in SQL), regardless
        of the column's datatype. If the column is of a string type and you intend
        to remove a suffix, consider using string functions instead.

        Args:
            value (Any): The right-hand side of the subtraction. Can be a
                :class:`Column`, :class:`ColumnsOperation`, or a literal value
                (int, float, etc.).

        Returns:
            ColumnsOperation: A new :class:`ColumnsOperation` instance representing
            the subtraction expression, with the SQL fragment and parameters
            stored internally.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Subtract a constant from a column
            >>> expr = employees.salary - 5000
            >>> # Use in update: decrease salary by 5000 for all employees
            >>> employees.update({employees.salary: employees.salary - 5000}, where=...)
            >>>
            >>> # Subtract one column from another
            >>> expr = employees.max_salary - employees.min_salary
            >>> # Use in SELECT: get salary range
            >>> rows = employees.get_row([expr], where=...)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob - value

    def __rsub__(self, value):
        """Implement reflected subtraction (right-hand side subtraction) for a column.

        This method is called when a :class:`Column` appears on the right side of a
        subtraction operator, e.g., `5 - employees.salary`. It creates a
        :class:`ColumnsOperation` that represents the subtraction expression and
        delegates the actual operation to the `__sub__` method of the operation builder.

        The generated SQL expression will have the form `(value - column)` where
        `value` can be a literal, another :class:`Column`, or a
        :class:`ColumnsOperation`.

        Args:
            value (Any): The left-hand operand (the subtrahend). Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            subtraction expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: (1000 - "salary")
            >>> op = 1000 - employees.salary
            >>> print(op._output[0])
            '(1000 - "employees"."salary")'
            >>> print(op._output[1])
            []
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value - temp_ob

    def __mul__(self, value):
        """Implement multiplication (`*`) of a column by a value.

        This method is called when a :class:`Column` is multiplied, e.g.,
        `employees.salary * 1.1`. It creates a :class:`ColumnsOperation` that
        represents the multiplication expression and delegates the actual operation
        to the `__mul__` method of the operation builder.

        The generated SQL expression will have the form `(column * value)` where
        `value` can be a literal, another :class:`Column`, or a
        :class:`ColumnsOperation`. For string columns, multiplication is not
        typically used; this is intended for numeric operations.

        Args:
            value (Any): The right-hand operand (the multiplier). Can be a literal
                (int, float, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            multiplication expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: (salary * 1.1) to calculate a 10% raise
            >>> op = employees.salary * 1.1
            >>> print(op._output[0])
            '("employees"."salary" * %s)'
            >>> print(op._output[1])
            [1.1]
            >>> # Chain with other operations
            >>> bonus = employees.salary * 0.05 + employees.bonus
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob * value

    def __rmul__(self, value):
        """Implement reflected multiplication (right-hand side multiplication) for a column.

        This method is called when a :class:`Column` appears on the right side of a
        multiplication operator, e.g., `5 * employees.salary`. It creates a
        :class:`ColumnsOperation` that represents the multiplication expression and
        delegates the actual operation to the appropriate operator.

        The generated SQL expression will have the form `(value * column)` where
        `value` can be a literal, another :class:`Column`, or a
        :class:`ColumnsOperation`.

        Args:
            value (Any): The left-hand operand (the multiplier). Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            multiplication expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: (1000 * "salary")
            >>> op = 1000 * employees.salary
            >>> print(op._output[0])
            '(1000 * "employees"."salary")'
            >>> print(op._output[1])
            []
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value * temp_ob

    def __pow__(self, value):
        """Implement the power/exponentiation operator (`**`) for a column.

        This method is called when a :class:`Column` is raised to a power, e.g.,
        `employees.salary ** 2`. It creates a :class:`ColumnsOperation` that
        represents the exponentiation expression and delegates the actual operation
        to the `__pow__` method of the operation builder, which generates a SQL
        `POW(column, value)` expression.

        The resulting SQL expression will be parameterized appropriately:
        - If `value` is a literal, it will be parameterized as `%s`.
        - If `value` is another :class:`Column` or :class:`ColumnsOperation`,
        the expression will combine them.

        Args:
            value (Any): The exponent. Can be a literal (int, float, etc.),
                a :class:`Column`, or a :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            exponentiation expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: POW("salary", 2)
            >>> op = employees.salary ** 2
            >>> print(op._output[0])
            'POW("employees"."salary" , %s)'
            >>> print(op._output[1])
            [2]
            >>> # Chain with other operations
            >>> op2 = (employees.salary ** 2) + employees.bonus
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob ** value

    def __rpow__(self, value):
        """Implement reflected exponentiation (right-hand side power) for a column.

        This method is called when a :class:`Column` appears on the right side of the
        exponentiation operator, e.g., `2 ** employees.salary`. It creates a
        :class:`ColumnsOperation` that represents the exponentiation expression and
        delegates the actual operation to the `__pow__` method of the operation builder.

        The generated SQL expression will use the `POW` function with the form
        `POW(value, column)` where `value` can be a literal, another :class:`Column`,
        or a :class:`ColumnsOperation`.

        Args:
            value (Any): The left-hand operand (the base). Can be a literal
                (int, float, etc.), a :class:`Column`, or a :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            exponentiation expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: POW(2, "salary")
            >>> op = 2 ** employees.salary
            >>> print(op._output[0])
            'POW(%s , "employees"."salary")'
            >>> print(op._output[1])
            [2]
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value ** temp_ob

    def __truediv__(self, value):
        """Implement division for a column.

        This method is called when a :class:`Column` is divided by a value using the
        `/` operator. It creates a :class:`ColumnsOperation` that represents the
        division expression and delegates the actual operation to the operation
        builder.

        Args:
            value (Any): The right-hand operand (the divisor). Can be a literal
                (int, float, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            division expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" / 2)
            >>> op = employees.salary / 2
            >>> print(op._output[0])
            '("employees"."salary" / %s)'
            >>> print(op._output[1])
            [2]
            >>> # Division by another column
            >>> op2 = employees.salary / employees.bonus
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob / value

    def __rtruediv__(self, value):
        """Implement reflected division (right‑hand side division) for a column.

        This method is called when a :class:`Column` appears on the right side of a
        division operator, e.g., `100 / employees.salary`. It creates a
        :class:`ColumnsOperation` that represents the division expression and
        delegates the actual operation to the `__truediv__` method of the operation
        builder, with the column as the right operand.

        The generated SQL expression will have the form `(value / column)` where
        `value` can be a literal, another :class:`Column`, or a
        :class:`ColumnsOperation`.

        Args:
            value (Any): The left‑hand operand (the numerator). Can be a literal
                (int, float, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            division expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: (1000 / "salary")
            >>> op = 1000 / employees.salary
            >>> print(op._output[0])
            '(1000 / "employees"."salary")'
            >>> print(op._output[1])
            []
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value / temp_ob

    def __mod__(self, value):
        """Implement the modulo (`%`) operator for a column expression.

        This method is called when the modulo operator is used with a :class:`Column`
        on the left side, e.g., `employees.salary % 10`. It creates a
        :class:`ColumnsOperation` that represents the modulo expression and
        delegates the actual operation to the `__mod__` method of the operation
        builder.

        The generated SQL expression will have the form `(column % value)` where
        `value` can be a literal, another :class:`Column`, or a
        :class:`ColumnsOperation`. The modulo operator is typically used with
        numeric columns.

        Args:
            value (Any): The right‑hand operand. Can be a literal (int, float, etc.),
                a :class:`Column`, or a :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            modulo expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: ("salary" % 10)
            >>> op = employees.salary % 10
            >>> print(op._output[0])
            '("employees"."salary" % %s)'
            >>> print(op._output[1])
            [10]
            >>> # Chaining with other operations
            >>> op2 = (employees.salary % 5) == 0
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob % value

    def __rmod__(self, value):
        """Implement reflected modulo (right-hand side modulo) for a column.

        This method is called when a :class:`Column` appears on the right side of a
        modulo operator, e.g., `10 % employees.salary`. It creates a
        :class:`ColumnsOperation` that represents the modulo expression and delegates
        the actual operation to the `__mod__` method of the operation builder.

        The generated SQL expression will have the form `(value % column)` where
        `value` can be a literal, another :class:`Column`, or a
        :class:`ColumnsOperation`.

        Args:
            value (Any): The left-hand operand (the dividend). Can be a literal
                (int, float, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            modulo expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: (10 % "salary")
            >>> op = 10 % employees.salary
            >>> print(op._output[0])
            '(10 % "employees"."salary")'
            >>> print(op._output[1])
            []
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value % temp_ob

    def eq(self, value):
        """Create a SQL equality comparison (`=`) for this column.

        This method generates a SQL `=` expression with the column on the left
        and the provided value on the right. It is the explicit (non-operator)
        version of `__eq__`, useful when the equality operator cannot be used
        directly (e.g., in contexts where operator overloading is not supported).
        The result is returned as a :class:`ColumnsOperation`, allowing further
        chaining or combination with other conditions.

        The right-hand side can be:
            - Another :class:`ColumnsOperation` (e.g., a computed expression).
            - A :class:`Column` (using its fully qualified name).
            - A literal value (int, float, str, etc.), which will be added to
            the parameters list as a placeholder.

        Args:
            value (Any): The right‑hand side of the equality. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or any literal
                value (str, int, float, etc.) or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NULL``.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing
            the equality condition, ready for use in WHERE clauses or
            further chaining.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>>
            >>> # Equality with a literal
            >>> cond = employees.id.eq(100)
            >>> print(cond._output[0])
            '("employees"."id" = %s)'
            >>> print(cond._output[1])
            [100]
            >>>
            >>> # Equality with another column
            >>> cond2 = employees.manager_id.eq(employees.id)
            >>> print(cond2._output[0])
            '("employees"."manager_id" = "employees"."id")'
            >>>
            >>> # Equality with a ColumnsOperation (e.g., computed)
            >>> from ormophine.Postgresql import ColumnsOperation
            >>> bonus = employees.salary * 0.1
            >>> cond3 = employees.bonus.eq(bonus)
            >>> # This generates: ("employees"."bonus" = ("salary" * 0.1))
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} = {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} = {value.name})', []) if isinstance(value, Column) else (f'({self.name} IS NULL)', []) if value is None else (f'({self.name} = %s)', [value])
        return temp_ob

    def __eq__(self, value):
        """Create a SQL equality comparison (`=`) for this column.

        This method is called when the `==` operator is used between a
        :class:`Column` and another value. It creates a :class:`ColumnsOperation`
        that represents the equality expression `column = value`, where `value` can
        be a literal, another :class:`Column`, or a :class:`ColumnsOperation`.

        The generated SQL expression will be parameterized when `value` is a literal,
        using a placeholder (`%s`) to prevent SQL injection.

        Args:
            value (Any): The right-hand side of the equality comparison. Can be a
                literal (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation` or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NULL``.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            equality expression, ready for use in `WHERE` clauses or chaining with
            logical operators.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Equality with literal
            >>> cond = employees.name == "Alice"
            >>> print(cond._output[0])
            '("employees"."name" = %s)'
            >>> print(cond._output[1])
            ['Alice']
            >>>
            >>> # Equality with another column
            >>> cond2 = employees.manager_id == employees.id
            >>> print(cond2._output[0])
            '("employees"."manager_id" = "employees"."id")'
            >>>
            >>> # Chaining with AND
            >>> final_cond = (employees.salary == 50000) & (employees.department == "Engineering")
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} = {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} = {value.name})', []) if isinstance(value, Column) else (f'({self.name} IS NULL)', []) if value is None else (f'({self.name} = %s)', [value])
        return temp_ob

    def ne(self, value):
        """Create a SQL inequality comparison (`!=`) for this column.

        This method generates a `!=` expression comparing the column to a value,
        subquery, or another column. It is the explicit (non-operator) version of
        `__ne__`, useful when the inequality operator cannot be used directly
        (e.g., in contexts where operator overloading is not supported).

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (embedding its SQL and params).
            - A :class:`Column` (using the column's fully qualified name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right‑hand side of the inequality. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.) or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NOT NULL``.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            inequality expression, ready for use in WHERE clauses or further chaining.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit inequality: salary != 50000
            >>> cond = employees.salary.ne(50000)
            >>> print(cond._output[0])
            '("employees"."salary" != %s)'
            >>> print(cond._output[1])
            [50000]
            >>>
            >>> # Chain with logical operations
            >>> final = employees.salary.ne(0) & employees.department.ne('IT')
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} != {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} != {value.name})', []) if isinstance(value, Column) else (f'({self.name} IS NOT NULL)', []) if value is None else (f'({self.name} != %s)', [value])
        return temp_ob

    def __ne__(self, value):
        """Implement the inequality operator (`!=`) for a column.

        This method is called when a :class:`Column` is compared for inequality with
        another value using the `!=` operator. It creates a :class:`ColumnsOperation`
        that represents the SQL expression `column != value`, where `value` can be
        a literal, another :class:`Column`, or a :class:`ColumnsOperation`.

        The generated SQL expression will be parameterized appropriately to prevent
        SQL injection:
        - If `value` is a `ColumnsOperation`, the expression combines both.
        - If `value` is a `Column`, the expression uses the column name.
        - If `value` is a literal, a placeholder `%s` is used and the value is
            added to the parameters list.

        Args:
            value (Any): The right‑hand side of the inequality. Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation` or ``None``. When ``None`` is passed, the generated SQL
                becomes ``<expression> IS NOT NULL``.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            inequality expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: "salary" != 50000
            >>> op = employees.salary != 50000
            >>> print(op._output[0])
            '("employees"."salary" != %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Compare with another column: "salary" != "bonus"
            >>> op2 = employees.salary != employees.bonus
            >>> print(op2._output[0])
            '("employees"."salary" != "employees"."bonus")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} != {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} != {value.name})', []) if isinstance(value, Column) else (f'({self.name} IS NOT NULL)', []) if value is None else (f'({self.name} != %s)', [value])
        return temp_ob

    def gt(self, value):
        """Create a SQL 'greater than' comparison (`>`) for this column.

        This method generates a SQL `>` expression comparing the column with the
        provided value. It is the explicit (non-operator) version of `__gt__`,
        useful when the comparison operator cannot be used directly (e.g., in
        contexts where operator overloading is not supported or when building
        dynamic queries). The result is a :class:`ColumnsOperation` instance that
        can be chained or used in `WHERE` clauses.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - Another :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right‑hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `>` comparison expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees with salary greater than 50000
            >>> cond = employees.salary.gt(50000)
            >>> print(cond._output[0])
            '("employees"."salary" > %s)'
            >>> print(cond._output[1])
            [50000]
            >>>
            >>> # Compare two columns: salary > bonus
            >>> cond2 = employees.salary.gt(employees.bonus)
            >>> print(cond2._output[0])
            '("employees"."salary" > "employees"."bonus")'
            >>>
            >>> # Using with a ColumnsOperation (e.g., salary > (bonus + 1000))
            >>> bonus_plus = employees.bonus + 1000
            >>> cond3 = employees.salary.gt(bonus_plus)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} > {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} > {value.name})', []) if isinstance(value, Column) else (f'({self.name} > %s)', [value])
        return temp_ob

    def __gt__(self, value):
        """Implement the greater-than operator (`>`) for a column.

        This method is called when a :class:`Column` is compared with another value
        using the `>` operator. It creates a :class:`ColumnsOperation` that
        represents the SQL expression `column > value`, where `value` can be a
        literal, another :class:`Column`, or a :class:`ColumnsOperation`.

        The generated SQL expression will be parameterized appropriately to prevent
        SQL injection:
        - If `value` is a `ColumnsOperation`, the expression combines both.
        - If `value` is a `Column`, the expression uses the column name.
        - If `value` is a literal, a placeholder `%s` is used and the value is
            added to the parameters list.

        Args:
            value (Any): The right‑hand side of the comparison. Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            greater‑than expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: "salary" > 50000
            >>> op = employees.salary > 50000
            >>> print(op._output[0])
            '("employees"."salary" > %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Compare with another column: "salary" > "bonus"
            >>> op2 = employees.salary > employees.bonus
            >>> print(op2._output[0])
            '("employees"."salary" > "employees"."bonus")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} > {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} > {value.name})', []) if isinstance(value, Column) else (f'({self.name} > %s)', [value])
        return temp_ob

    def lt(self, value):
        """Create a SQL 'less than' comparison (`<`) for this column.

        This method generates a SQL `<` expression comparing the column with the
        provided value. It is the explicit (non‑operator) version of `__lt__`,
        useful when the comparison operator cannot be used directly (e.g., in
        contexts where operator overloading is not supported). The result is a
        :class:`ColumnsOperation` that can be used in `WHERE` clauses or combined
        with other conditions.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right‑hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            less‑than expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit less-than: salary < 50000
            >>> cond = employees.salary.lt(50000)
            >>> print(cond._output[0])
            '("employees"."salary" < %s)'
            >>> print(cond._output[1])
            [50000]
            >>> # Compare with another column: salary < bonus
            >>> cond2 = employees.salary.lt(employees.bonus)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} < {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} < {value.name})', []) if isinstance(value, Column) else (f'({self.name} < %s)', [value])
        return temp_ob

    def __lt__(self, value):
        """Implement the less‑than operator (`<`) for a column.

        This method is called when a :class:`Column` is compared with another value
        using the `<` operator. It creates a :class:`ColumnsOperation` that represents
        the SQL expression `column < value`, where `value` can be a literal, another
        :class:`Column`, or a :class:`ColumnsOperation`.

        The generated SQL expression will be parameterized appropriately to prevent
        SQL injection:
        - If `value` is a `ColumnsOperation`, the expression combines both.
        - If `value` is a `Column`, the expression uses the column name.
        - If `value` is a literal, a placeholder `%s` is used and the value is
            added to the parameters list.

        Args:
            value (Any): The right‑hand side of the comparison. Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            less‑than expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: "salary" < 50000
            >>> op = employees.salary < 50000
            >>> print(op._output[0])
            '("employees"."salary" < %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Compare with another column: "salary" < "bonus"
            >>> op2 = employees.salary < employees.bonus
            >>> print(op2._output[0])
            '("employees"."salary" < "employees"."bonus")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} < {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} < {value.name})', []) if isinstance(value, Column) else (f'({self.name} < %s)', [value])
        return temp_ob

    def ge(self, value):
        """Create a SQL 'greater than or equal to' comparison (`>=`) for this column.

        This method is called when a :class:`Column` is compared using the `ge()`
        method (explicit comparison) or via the `>=` operator (delegated to `__ge__`).
        It creates a :class:`ColumnsOperation` that represents the SQL expression
        `column >= value`, where `value` can be a literal, another :class:`Column`,
        or a :class:`ColumnsOperation`.

        The generated SQL expression will be parameterized appropriately:
        - If `value` is a `ColumnsOperation`, the expression combines both operations.
        - If `value` is a `Column`, the expression uses the column name.
        - If `value` is a literal, a placeholder `%s` is used and the value is
        added to the parameters list.

        Args:
            value (Any): The right‑hand side of the comparison. Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `>=` comparison expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: "salary" >= 50000
            >>> op = employees.salary.ge(50000)
            >>> print(op._output[0])
            '("employees"."salary" >= %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Compare with another column: "salary" >= "bonus"
            >>> op2 = employees.salary.ge(employees.bonus)
            >>> print(op2._output[0])
            '("employees"."salary" >= "employees"."bonus")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} >= {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} >= {value.name})', []) if isinstance(value, Column) else (f'({self.name} >= %s)', [value])
        return temp_ob

    def __ge__(self, value):
        """Implement the greater‑than‑or‑equal comparison operator (`>=`) for a column.

        This method is called when a :class:`Column` is compared with another value
        using the `>=` operator. It creates a :class:`ColumnsOperation` that
        represents the SQL expression `column >= value`, where `value` can be a
        literal, another :class:`Column`, or a :class:`ColumnsOperation`.

        The generated SQL expression is parameterized to prevent SQL injection:
        - If `value` is a `ColumnsOperation`, the expression combines both.
        - If `value` is a `Column`, the expression uses the column name.
        - If `value` is a literal, a placeholder `%s` is used and the value is
            added to the parameters list.

        Args:
            value (Any): The right‑hand side of the comparison. Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            comparison expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: "salary" >= 50000
            >>> op = employees.salary >= 50000
            >>> print(op._output[0])
            '("employees"."salary" >= %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Compare with another column: "salary" >= "bonus"
            >>> op2 = employees.salary >= employees.bonus
            >>> print(op2._output[0])
            '("employees"."salary" >= "employees"."bonus")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} >= {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} >= {value.name})', []) if isinstance(value, Column) else (f'({self.name} >= %s)', [value])
        return temp_ob

    def le(self, value):
        """Create a SQL 'less than or equal to' comparison (`<=`) for this column.

        This method generates a SQL `<=` expression comparing the column with the
        provided value. It is the explicit (non-operator) version of `__le__`,
        useful when the comparison operator cannot be used directly (e.g., in
        contexts where operator overloading is not supported). The result is a
        :class:`ColumnsOperation` that can be used in `WHERE` clauses or combined
        with other conditions.

        The comparison can be made against:
            - Another :class:`ColumnsOperation` (combining both expressions).
            - A :class:`Column` (using the column's name).
            - A literal value (using a parameter placeholder `%s` and adding the
            value to the parameter list).

        Args:
            value (Any): The right‑hand side of the comparison. Can be a
                :class:`ColumnsOperation`, :class:`Column`, or a literal
                (int, float, str, etc.).

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            comparison expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Explicit less‑or‑equal: salary <= 50000
            >>> op = employees.salary.le(50000)
            >>> print(op._output[0])
            '("employees"."salary" <= %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Chaining with another condition
            >>> cond = employees.salary.le(70000) & employees.name.startswith('A')
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} <= {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} <= {value.name})', []) if isinstance(value, Column) else (f'({self.name} <= %s)', [value])
        return temp_ob

    def __le__(self, value):
        """Implement the less‑than‑or‑equal comparison operator (`<=`) for a column.

        This method is called when a :class:`Column` is compared with another value
        using the `<=` operator. It creates a :class:`ColumnsOperation` that
        represents the SQL expression `column <= value`, where `value` can be a
        literal, another :class:`Column`, or a :class:`ColumnsOperation`.

        The generated SQL expression is parameterized to prevent SQL injection:
        - If `value` is a `ColumnsOperation`, the expression combines both.
        - If `value` is a `Column`, the expression uses the column name.
        - If `value` is a literal, a placeholder `%s` is used and the value is
            added to the parameters list.

        Args:
            value (Any): The right‑hand side of the comparison. Can be a literal
                (int, float, str, etc.), a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            comparison expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Generate SQL: "salary" <= 50000
            >>> op = employees.salary <= 50000
            >>> print(op._output[0])
            '("employees"."salary" <= %s)'
            >>> print(op._output[1])
            [50000]
            >>> # Compare with another column: "salary" <= "bonus"
            >>> op2 = employees.salary <= employees.bonus
            >>> print(op2._output[0])
            '("employees"."salary" <= "employees"."bonus")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} <= {value._output[0]})', value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} <= {value.name})', []) if isinstance(value, Column) else (f'({self.name} <= %s)', [value])
        return temp_ob

    def __getitem__(self, key: slice):
        """Implement substring extraction using slice notation.

        This method enables Python's slicing syntax (e.g., `column[start:stop]`)
        on a :class:`Column` object. It generates a SQL `SUBSTRING` expression
        that extracts a portion of the column's string value.

        The behavior mimics Python string slicing with support for positive and
        negative indices, as well as `None` for start or stop. The generated SQL
        uses the PostgreSQL `SUBSTRING` function with `LENGTH` for negative
        indexing.

        The method is chainable: it returns a :class:`ColumnsOperation` that can
        be further combined with other operations.

        Args:
            key (slice): A slice object specifying the start and stop positions.
                - `start` (int or None): The starting position (0‑based, inclusive).
                If `None`, the extraction begins at position 1 (SQL 1‑based).
                - `stop` (int or None): The ending position (0‑based, exclusive).
                If `None`, the extraction continues to the end of the string.
                Both `start` and `stop` can be negative, indicating positions
                counted from the end of the string.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance whose `_output`
            contains the SQL `SUBSTRING` expression and associated parameters.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Extract first three characters of the name
            >>> op = employees.name[0:3]
            >>> print(op._output[0])
            '(SUBSTRING("employees"."name" , %s , %s))'
            >>> print(op._output[1])
            [1, 3]  # note SQL uses 1-based indexing
            >>>
            >>> # Extract from position 2 to the end
            >>> op2 = employees.name[1:]
            >>> print(op2._output[0])
            '(SUBSTRING("employees"."name" , %s , LENGTH("employees"."name")))'
            >>> print(op2._output[1])
            [2]
            >>>
            >>> # Negative indices (last 3 characters)
            >>> op3 = employees.name[-3:]
            >>> print(op3._output[0])
            '(SUBSTRING("employees"."name" , LENGTH("employees"."name") - %s , LENGTH("employees"."name")))'
            >>> print(op3._output[1])
            [2]  # LENGTH - 2 gives the start position for last 3 chars
        """
        temp_ob = ColumnsOperation(self)
        if key.start == None and key.stop ==  None:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , 1 , LENGTH({temp_ob.col_obj.name}) + 1))', [])   #
        elif key.start == None and key.stop < 0:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , 1 , LENGTH({temp_ob.col_obj.name}) - %s))', [abs(key.stop)])  #
        elif key.start == None and key.stop >= 0:
             temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , 1 , %s))', [key.stop])  #  
        elif key.start >= 0 and key.stop ==  None:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , %s , LENGTH({temp_ob.col_obj.name})))', [key.start + 1])  #   
        elif key.start < 0 and key.stop == None:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , LENGTH({temp_ob.col_obj.name}) - %s , LENGTH({temp_ob.col_obj.name})))', [abs(key.start) - 1])  #
        elif key.start >= 0 and key.stop < 0:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , %s , LENGTH({temp_ob.col_obj.name}) - %s))', [key.start + 1, abs(key.stop - key.start)])  #  
        elif key.start >= 0 and key.stop > 0:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , %s , %s))', [key.start + 1, key.stop - key.start])  #
        elif key.start < 0 and key.stop < 0:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , LENGTH({temp_ob.col_obj.name}) - %s , %s))', [abs(key.start) - 1, key.stop - key.start])  #
        elif key.start < 0 and key.stop > 0:
            temp_ob._output = (f'(SUBSTRING({temp_ob.col_obj.name} , LENGTH({temp_ob.col_obj.name}) - %s ,  %s - (LENGTH({temp_ob.col_obj.name}) - %s)))', [abs(key.start) - 1, key.stop, abs(key.start)])
        return temp_ob

    def strip(self, chars: str = ' '):
        """just like python strip(), create a SQL `TRIM` expression to strip leading and trailing characters.

        This method generates a PostgreSQL `TRIM` function call that removes the
        specified characters (default space) from both ends of the column's value.
        It returns a :class:`ColumnsOperation` that can be used in queries or
        chained with other operations.

        Args:
            chars (str, optional): The characters to remove. Defaults to a space.
                The characters are treated as a set; any occurrence at the beginning
                or end of the string is removed.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `TRIM` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Trim spaces from the 'name' column
            >>> op = employees.name.strip()
            >>> print(op._output[0])
            "(TRIM(BOTH ' ' FROM \"employees\".\"name\"))"
            >>> # Trim underscores from both ends
            >>> op2 = employees.code.strip('_')
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"(TRIM(BOTH '{chars}' FROM {temp_ob._output[0]}))", temp_ob._output[1]) if temp_ob._output[0] else (f"(TRIM(BOTH '{chars}' FROM {temp_ob.col_obj.name}))", [])
        return temp_ob

    def lstrip(self, chars: str = ' '):
        """Just like python lstrip(), remove leading characters from a string column or expression.

        This method generates a SQL `TRIM(LEADING ... FROM ...)` expression that
        strips the specified characters from the start of the column value or
        existing operation. If no `chars` are provided, leading spaces are removed.

        The operation is chainable and returns a :class:`ColumnsOperation` that
        can be used in queries, updates, or combined with other expressions.

        Args:
            chars (str, optional): The characters to remove from the left side.
                Defaults to a single space.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `TRIM(LEADING ...)` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Remove leading spaces from the 'name' column
            >>> trimmed = employees.name.lstrip()
            >>> # Generate SQL: (TRIM(LEADING ' ' FROM "employees"."name"))
            >>> # Remove leading dashes from the 'code' column
            >>> trimmed2 = employees.code.lstrip('-')
            >>> # Chain with other operations
            >>> cond = employees.name.lstrip().upper().contains('SMITH')
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"(TRIM(LEADING '{chars}' FROM {temp_ob._output[0]}))", temp_ob._output[1]) if temp_ob._output[0] else (f"(TRIM(LEADING '{chars}' FROM {temp_ob.col_obj.name}))", [])
        return temp_ob

    def rstrip(self, chars: str = ' '):
        """Just like python rstrip(), generate a SQL `TRIM` expression that removes trailing characters from the column.

        This method creates a :class:`ColumnsOperation` that, when used in a query,
        strips the specified characters from the end (right side) of the column's
        string value. The default is to strip spaces. The result is a SQL
        `TRIM(TRAILING ... FROM ...)` expression.

        Args:
            chars (str, optional): The characters to remove from the right end of
                the string. Defaults to a single space (`' '`).

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `TRIM` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Strip trailing spaces from the name column
            >>> op = employees.name.rstrip()
            >>> print(op._output[0])
            "(TRIM(TRAILING ' ' FROM \"employees\".\"name\"))"
            >>> # Strip trailing 'x' characters
            >>> op2 = employees.name.rstrip('x')
            >>> print(op2._output[0])
            "(TRIM(TRAILING 'x' FROM \"employees\".\"name\"))"
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"(TRIM(TRAILING '{chars}' FROM {temp_ob._output[0]}))", temp_ob._output[1]) if temp_ob._output[0] else (f"(TRIM(TRAILING '{chars}' FROM {temp_ob.col_obj.name}))", [])
        return temp_ob

    def add_end(self, content):
        """Concatenate content to the end of the column's string value.

        This method creates a :class:`ColumnsOperation` that represents the SQL
        expression `column || content`, which appends the given content to the
        end of the column's string value. The content can be a literal, another
        :class:`Column`, or a :class:`ColumnsOperation` (e.g., an expression).

        The result is a :class:`ColumnsOperation` instance that can be used in
        queries, updates, or further chained operations.

        Args:
            content (Any): The value or expression to append. Can be:
                - A :class:`ColumnsOperation` (e.g., an existing expression).
                - A :class:`Column` (another column).
                - A literal (str, int, etc.) that will be converted to a string
                parameter.

        Returns:
            ColumnsOperation: A new :class:`ColumnsOperation` representing the
            concatenation expression, ready for chaining.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Append " (Inc.)" to the company name
            >>> op = employees.company.add_end(" (Inc.)")
            >>> print(op._output[0])
            '("employees"."company" || %s)'
            >>> print(op._output[1])
            [' (Inc.)']
            >>>
            >>> # Append another column (e.g., suffix column)
            >>> op2 = employees.first_name.add_end(employees.last_name)
            >>> print(op2._output[0])
            '("employees"."first_name" || "employees"."last_name")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({self.name} || {content._output[0]})', content._output[1]) if isinstance(content, ColumnsOperation) else (f'({self.name} || {content.name})', []) if isinstance(content, Column) else (f'({self.name} || %s)', [content])
        return temp_ob

    def add_first(self, content):
        """Generate a SQL expression that prepends content to the column's value.

        This method creates a :class:`ColumnsOperation` that represents the SQL
        concatenation of `content` before the column's current value. For string
        columns, this is equivalent to `content || column` in PostgreSQL. The
        result can be used in SELECT, UPDATE, or WHERE clauses.

        The `content` parameter can be:
        - Another :class:`ColumnsOperation` (e.g., a concatenated expression).
        - A :class:`Column` from the same or another table.
        - A literal string value (which will be parameterized).

        Args:
            content (Any): The value to prepend. Can be a :class:`ColumnsOperation`,
                :class:`Column`, or a literal string.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            concatenation expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Prepend 'EMP-' to the employee code
            >>> op = employees.code.add_first('EMP-')
            >>> print(op._output[0])
            '(%s || "employees"."code")'
            >>> print(op._output[1])
            ['EMP-']
            >>> # Prepend the value of another column
            >>> op2 = employees.code.add_first(employees.department_code)
            >>> print(op2._output[0])
            '("employees"."department_code" || "employees"."code")'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'({content._output[0]} || {self.name})', content._output[1]) if isinstance(content, ColumnsOperation) else (f'({content.name} || {self.name})', []) if isinstance(content, Column) else (f'(%s || {self.name})', [content])
        return temp_ob
    
    def lower(self):
        """Just like python lower(), generate a SQL `LOWER` expression to convert the column value to lowercase.

        This method creates a :class:`ColumnsOperation` that, when used in a query,
        applies the PostgreSQL `LOWER` function to the column's string value,
        converting all characters to lowercase. The result is a SQL expression
        that can be used in `SELECT`, `WHERE`, or other clauses.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `LOWER` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Compare names case-insensitively
            >>> cond = employees.name.lower() == 'john'
            >>> print(cond._output[0])
            '((LOWER("employees"."name")) = %s)'
            >>> print(cond._output[1])
            ['john']
            >>> # Use in a query
            >>> results = employees.get_row([employees.name], where=cond)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'(LOWER({temp_ob._output[0]}))', temp_ob._output[1]) if temp_ob._output[0] else (f'(LOWER({temp_ob.col_obj.name}))', [])
        return temp_ob

    def upper(self):
        """Just like python upper(), generate a SQL `UPPER` expression that converts the column value to uppercase.

        This method creates a :class:`ColumnsOperation` that, when used in a query,
        applies the SQL `UPPER()` function to the column, transforming all characters
        to uppercase. The result can be used in `SELECT`, `WHERE`, or other clauses.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `UPPER` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Convert names to uppercase for case‑insensitive comparison
            >>> op = employees.name.upper()
            >>> print(op._output[0])
            '(UPPER("employees"."name"))'
            >>> # Use in a WHERE clause
            >>> cond = employees.name.upper() == 'JOHN DOE'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'(UPPER({temp_ob._output[0]}))', temp_ob._output[1]) if temp_ob._output[0] else (f'(UPPER({temp_ob.col_obj.name}))', [])
        return temp_ob

    def replace(self, old, new):
        """Just like python replace(), generate a SQL `REPLACE` expression to substitute substrings in the column.

        This method creates a :class:`ColumnsOperation` that, when used in a query,
        replaces all occurrences of a specified substring (`old`) with another
        substring (`new`) in the column's string value. The result is a SQL
        `REPLACE(column, old, new)` expression with parameterized placeholders to
        prevent SQL injection.

        Args:
            old (str): The substring to be replaced.
            new (str): The substring to replace with.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `REPLACE` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Replace 'old' with 'new' in the name column
            >>> op = employees.name.replace('old', 'new')
            >>> print(op._output[0])
            '(REPLACE("employees"."name" , %s , %s))'
            >>> print(op._output[1])
            ['old', 'new']
            >>> # Chain with other string functions
            >>> op2 = employees.name.upper().replace('A', 'X')
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'(REPLACE({temp_ob._output[0]} , %s , %s))', temp_ob._output[1] + [old, new]) if temp_ob._output[0] else (f'(REPLACE({temp_ob.col_obj.name} , %s , %s))', [old, new])
        return temp_ob

    def like(self, value):
        """Generate a SQL `LIKE` pattern matching expression for this column.

        This method creates a :class:`ColumnsOperation` that represents a SQL `LIKE`
        comparison between the column and the provided pattern. The pattern can be
        a literal string, another :class:`Column`, or a :class:`ColumnsOperation`
        (e.g., for concatenated patterns). The result can be used directly in
        `WHERE` clauses or combined with other conditions using logical operators.

        The generated SQL expression is parameterized to prevent injection:
        - If `value` is a `ColumnsOperation`, the expression uses the operation's
            SQL and parameter list.
        - If `value` is a `Column`, the expression uses the column name.
        - If `value` is a literal string, a placeholder `%s` is used and the
            string is added to the parameters list.

        Args:
            value (Any): The pattern to match against. Can be a literal string,
                a :class:`Column`, or a :class:`ColumnsOperation`. For literal
                strings, use `%` as a wildcard (e.g., `'A%'` for values starting
                with 'A').

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `LIKE` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose names start with 'A'
            >>> cond = employees.name.like('A%')
            >>> # Combine with another condition
            >>> final_cond = cond & (employees.salary > 50000)
            >>> # Use a ColumnsOperation for a more complex pattern
            >>> pattern = employees.name.upper() + '%'
            >>> cond2 = employees.name.like(pattern)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"({self.name} like {value._output[0]})", (temp_ob._output[1] + value._output[1]) if temp_ob._output[0] else value._output[1]) if isinstance(value, ColumnsOperation) else (f'({self.name} like {value.name})', temp_ob._output[1] if temp_ob._output[0] else []) if isinstance(value , Column) else (f'({self.name} like %s)', (temp_ob._output[1] + [f'{value}']) if temp_ob._output[0] else [f'{value}'])
        return temp_ob

    def startswith(self, value):
        """Just like python startswith(), generate a SQL `LIKE` expression that checks if the column starts with a given prefix.

        This method creates a :class:`ColumnsOperation` representing a condition
        that is true when the column's value begins with the specified prefix.
        The generated SQL uses the `LIKE` operator with the prefix followed by a
        wildcard (`%`), e.g., `column LIKE 'prefix%'`. The prefix can be provided
        as a literal string, another :class:`Column`, or a :class:`ColumnsOperation`
        (e.g., for a computed prefix).

        The result is a parameterized SQL expression to prevent injection. When used
        in a query, this condition can be combined with other conditions using
        logical operators (`&`, `|`).

        Args:
            value (Any): The prefix to match at the start of the column's value.
                Can be a literal string, a :class:`Column`, or a
                :class:`ColumnsOperation`. If a literal is provided, it will be
                treated as a string and escaped appropriately.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `LIKE` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose names start with 'A'
            >>> cond = employees.name.startswith('A')
            >>> print(cond._output[0])
            '("employees"."name" like %s || \'%%\')'
            >>> print(cond._output[1])
            ['A']
            >>> # Using another column as prefix
            >>> cond2 = employees.name.startswith(employees.prefix_column)
            >>> # Using a ColumnsOperation (e.g., upper-cased prefix)
            >>> prefix_op = employees.name.upper()
            >>> cond3 = employees.name.startswith(prefix_op)
            >>> # Combine with other conditions
            >>> final_cond = cond & (employees.salary > 50000)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"({self.name} like {value._output[0]} || '%%')", (temp_ob._output[1] + value._output[1]) if temp_ob._output[0] else value._output[1]) if isinstance(value, ColumnsOperation) else (f"({self.name} like {value.name} || '%%')", temp_ob._output[1] if temp_ob._output[0] else []) if isinstance(value , Column) else (f"({self.name} like %s || '%%')", (temp_ob._output[1] + [f'{value}']) if temp_ob._output[0] else [f'{value}'])
        return temp_ob
    
    def endswith(self, value):
        """Generate a SQL `LIKE` expression that matches strings ending with a suffix.

        This method creates a :class:`ColumnsOperation` that, when used in a query,
        filters rows where the column's string value ends with the specified suffix.
        The generated SQL uses `LIKE '%%' || value` (with the wildcard before the
        value) to perform the pattern match.

        The suffix can be provided as:
            - A literal string (e.g., `'son'`).
            - Another :class:`Column` (e.g., `employees.suffix_column`).
            - A :class:`ColumnsOperation` (e.g., for computed suffixes).

        The result is parameterized to prevent SQL injection; literal values are
        added to the parameters list and bound safely.

        Args:
            value (Any): The suffix to match at the end of the column's string.
                Can be a literal string, a :class:`Column`, or a
                :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `LIKE` expression, ready for chaining or use in `WHERE` clauses.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose last names end with 'son'
            >>> cond = employees.last_name.endswith('son')
            >>> # Use in a query
            >>> results = employees.get_row([employees.last_name], where=cond)
            >>>
            >>> # Using a Column as the suffix
            >>> suffix_col = Table(driver, "suffixes").suffix
            >>> cond2 = employees.last_name.endswith(suffix_col)
            >>>
            >>> # Using a ColumnsOperation (e.g., uppercase suffix)
            >>> op = employees.suffix_column.upper()
            >>> cond3 = employees.last_name.endswith(op)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"({self.name} like '%%' || {value._output[0]})", (temp_ob._output[1] + value._output[1]) if temp_ob._output[0] else value._output[1]) if isinstance(value, ColumnsOperation) else (f"({self.name} like '%%' || {value.name})", temp_ob._output[1] if temp_ob._output[0] else []) if isinstance(value , Column) else (f"({self.name} like '%%' || %s)", (temp_ob._output[1] + [f'{value}']) if temp_ob._output[0] else [f'{value}'])
        return temp_ob

    def contains(self, value):
        """Generate a SQL `LIKE` expression to check if the column contains a substring.

        This method creates a :class:`ColumnsOperation` that represents a SQL
        `LIKE` condition with wildcards on both sides: `column LIKE '%' || value || '%'`.
        The result can be used in `WHERE` clauses to filter rows where the column's
        string value contains the specified substring.

        The behavior depends on the type of `value`:
        - If `value` is a :class:`ColumnsOperation`, its SQL expression and
        parameters are used, and the `LIKE` pattern becomes `'%' || expr || '%'`.
        - If `value` is a :class:`Column`, its name is used directly.
        - If `value` is a literal string, a parameter placeholder `%s` is used,
        and the value is added to the parameter list with `%` wildcards appended.

        Args:
            value (Any): The substring to search for. Can be a literal string,
                a :class:`Column`, or a :class:`ColumnsOperation`.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `LIKE` expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Find employees whose name contains 'Smith'
            >>> cond = employees.name.contains('Smith')
            >>> # Equivalent SQL: "name" LIKE '%' || %s || '%'
            >>> # With parameter: 'Smith'
            >>>
            >>> # Using a ColumnsOperation (e.g., concatenated columns)
            >>> full_name = employees.first_name + ' ' + employees.last_name
            >>> cond2 = full_name.contains('John')
            >>> # Generated SQL: ((("first_name" || ' ') || "last_name") LIKE '%' || %s || '%')
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"({self.name} like '%%' || {value._output[0]} || '%%')", (temp_ob._output[1] + value._output[1]) if temp_ob._output[0] else value._output[1]) if isinstance(value, ColumnsOperation) else (f"({self.name} like '%%' || {value.name} || '%%')", temp_ob._output[1] if temp_ob._output[0] else []) if isinstance(value , Column) else (f"({self.name} like '%%' || %s || '%%')", (temp_ob._output[1] + [f'{value}']) if temp_ob._output[0] else [f'{value}'])
        return temp_ob

    def rename(self, column: 'Column', new_name: str) -> None:
        """Rename an existing column in the table.

        This method executes an `ALTER TABLE ... RENAME COLUMN` SQL statement to
        change the name of the specified column. After the database operation, it
        updates the corresponding :class:`Table` object by removing the attribute
        with the old column name and adding a new attribute with the new name,
        preserving the column's datatype.

        Args:
            column (Column): The :class:`Column` object representing the column to
                rename. This is typically a reference to a column attribute of the
                table.
            new_name (str): The new name for the column. This will be quoted
                appropriately.

        Returns:
            None: This method performs an in-place modification and does not
            return a value.

        Raises:
            Exception: Propagates any database errors from the `ALTER TABLE`
                statement, such as permission issues or if the column does not
                exist.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Assume the table has a column named 'last_name'
            >>> employees.last_name.rename(employees.last_name, "surname")
            >>> # After this, the column is renamed to 'surname', and the
            >>> # employees object now has an attribute 'surname'.
            >>> print(employees.surname)  # Works
        """
        query = f'ALTER TABLE {self.table_obj.name_} RENAME COLUMN {column.first_name} TO "{new_name}";'
        self.table_obj._exc(query)
        self.table_obj.__delattr__(column.first_name.strip('"'))
        self.table_obj.__setattr__(new_name, Column(self.table_obj, new_name, column.datatype))

    def delete_column(self, are_you_sure: bool, are_you_really_sure: bool, for_sure: bool) -> None:
        """Permanently remove this column from its table.

        This method executes a SQL `ALTER TABLE ... DROP COLUMN` statement to
        delete the column from the database schema. It also removes the column
        attribute from the parent :class:`Table` object to keep the ORM in sync.
        To prevent accidental data loss, three separate confirmation flags must
        all be `True`.

        Args:
            are_you_sure (bool): First confirmation flag.
            are_you_really_sure (bool): Second confirmation flag.
            for_sure (bool): Third confirmation flag.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: Propagates any database errors raised during the execution
                of the DROP COLUMN statement (e.g., permission issues or if the
                column does not exist).

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Assume there is a column 'temp_column'
            >>> # Permanently delete it from the table
            >>> employees.temp_column.delete_column(True, True, True)
            >>> # The attribute is no longer available on the table object
        """
        if are_you_sure and are_you_really_sure and for_sure:
            query = f'ALTER TABLE {self.table_obj.name_} DROP COLUMN {self.first_name};'
            self.table_obj._exc(query)
            self.table_obj.__delattr__(self.first_name[1:-1])

    def In(self, column: 'Ormophine.Postgresql.Column|Ormophine.Postgresql.ColumnsOperation' = None, where: 'Ormophine.Postgresql.ColumnsOperation' = None, data_list: list = None):
        """Build an SQL ``IN`` clause for the current column.

        This method serves as an entry point for the :class:`ColumnsOperation`
        ``In`` method. It initialises a :class:`ColumnsOperation` with the
        current column's name and delegates the execution to it, enabling
        seamless method chaining.

        Supports two modes:
        * Passing a list of literal values to ``data_list`` (or as the first
        positional argument for backward compatibility).
        * Passing a single :class:`Column`/:class:`ColumnsOperation` to
        ``column`` to build a ``SELECT`` subquery, with an optional
        ``where`` condition.

        Args:
            column: A single :class:`Column` or :class:`ColumnsOperation` to
                use in the ``SELECT`` clause of the subquery. If a list of
                literals is passed, it is treated as ``data_list``.
            where: An optional :class:`ColumnsOperation` (or :class:`Column`)
                representing the ``WHERE`` condition for the subquery.
            data_list: A list of literal values for a direct ``IN`` clause.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing
            the ``IN`` clause, allowing further chaining.

        Raises:
            Exception: If neither ``data_list`` nor a valid ``column``
                is provided to the underlying :class:`ColumnsOperation` method.

        Example:
            Assuming ``users`` and ``admins`` tables::

                from ormophine.Postgresql import Driver

                db = Driver("localhost", 5432, "user", "pass", "mydb")
                users = db.users
                admins = db.admins

                # Literal list
                cond1 = users.age.In([25, 30, 35])

                # Subquery
                cond2 = users.name.In(
                    column=admins.username,
                    where=admins.active == True
                )

                # Use in a query
                result = users.get_row([users.name], where=cond2)
        """
        temp_obj = ColumnsOperation(self)
        temp_obj._output = (self.name, [])
        return temp_obj.In(column=column, where=where, data_list=data_list)

    def not_In(self, column: Column|ColumnsOperation = None, where: ColumnsOperation = None, data_list: list = None):
        """Build an SQL ``NOT IN`` clause for the current column.

        This method serves as an entry point for the :class:`ColumnsOperation`
        ``not_In`` method. It initialises a :class:`ColumnsOperation` with the
        current column's name and delegates the execution to it, enabling
        seamless method chaining.

        Supports two modes:
        * Passing a list of literal values to ``data_list`` (or as the first
        positional argument for backward compatibility).
        * Passing a single :class:`Column`/:class:`ColumnsOperation` to
        ``column`` to build a ``SELECT`` subquery, with an optional
        ``where`` condition.

        Args:
            column: A single :class:`Column` or :class:`ColumnsOperation` to
                use in the ``SELECT`` clause of the subquery. If a list of
                literals is passed, it is treated as ``data_list``.
            where: An optional :class:`ColumnsOperation` (or :class:`Column`)
                representing the ``WHERE`` condition for the subquery.
            data_list: A list of literal values for a direct ``NOT IN`` clause.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing
            the ``NOT IN`` clause, allowing further chaining.

        Raises:
            Exception: If neither ``data_list`` nor a valid ``column``
                is provided to the underlying :class:`ColumnsOperation` method.

        Example:
            Assuming ``users`` and ``admins`` tables::

                from ormophine.Postgresql import Driver

                db = Driver("localhost", 5432, "user", "pass", "mydb")
                users = db.users
                admins = db.admins

                # Literal list
                cond1 = users.age.not_In([25, 30, 35])

                # Subquery
                cond2 = users.name.not_In(
                    column=admins.username,
                    where=admins.active == True
                )

                # Use in a query
                result = users.get_row([users.name], where=cond2)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob.not_In(column=column, where=where, data_list=data_list)

    def If(self, condition):
        """
        Start a one-line conditional with this column as the "then" branch.

        Same behavior as :meth:`ColumnsOperation.If` but starts from the
        column itself.

        Args:
            condition: A :class:`ColumnsOperation`, :class:`Column`, or raw
                value (auto-wrapped in :class:`LiteralValue`).

        Returns:
            _IfThenBuilder: Chain ``.Else(value)`` to complete.

        Example:
            >>> users.name.If(users.is_active == True).Else('inactive')
        """
        op = ColumnsOperation(self)
        op._output = (self.name, [])
        return op.If(condition)



class BatchOperation:
    """A builder for batch executing multiple SQL operations in a single transaction.

    This class provides a fluent interface for accumulating INSERT and UPDATE
    statements and then executing them together as a single atomic transaction.
    It is useful for bulk data modifications where you want to ensure that all
    operations succeed or fail together, and to reduce network round‑trips by
    sending multiple statements at once.

    Operations are added via the :meth:`insert` and :meth:`update` methods, each
    of which returns the instance itself to allow method chaining. The actual
    execution is triggered by calling :meth:`run`.

    The internal script stores each operation as a list of `[sql_string, params_list]`
    (or `[sql_string]` for no parameters). When `run()` is called, the underlying
    :class:`Table` executes the script using its `_excs` method, which commits
    all changes in one transaction.

    Attributes:
        script (list): A list where each element is either `[sql_string]` or
            `[sql_string, params_list]`, representing the operations to be executed.
        table_obj (Table): The table object that this batch is associated with;
            used to execute the script.

    Example:
        >>> employees = driver.employees
        >>> batch = BatchOperation(employees)
        >>> batch.insert({employees.name: "Alice", employees.salary: 60000})
        >>> batch.insert({employees.name: "Bob", employees.salary: 70000})
        >>> batch.update(
        ...     {employees.salary: employees.salary * 1.05},
        ...     employees.department == "Engineering"
        ... )
        >>> batch.run()
        # All three operations execute in a single transaction.

    Note:
        After `run()`, the script is not cleared automatically. To reuse the
        batch object, you would need to manually clear `script`, but it is
        recommended to create a new `BatchOperation` instance for each batch.
    """
    def __init__(self, table_object: Table):
        """Initialize a new batch operation builder for a specific table.

        A `BatchOperation` instance allows you to collect multiple SQL statements
        (INSERT and UPDATE) and execute them together in a single transaction,
        which improves performance for bulk operations. This constructor is
        typically not called directly; instead, use :meth:`Table.batch` to obtain
        a batch builder for a table.

        Args:
            table_object (Table): The :class:`Table` object on which the batched
                operations will be performed. All operations added to this batch
                will target this table unless overridden in individual operation calls.

        Returns:
            None: This method initializes the instance and does not return a value.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> # Using Table.batch() is the recommended way:
            >>> batch = employees.batch()
            >>> batch.insert({employees.name: "Alice", employees.salary: 60000})
            >>> batch.insert({employees.name: "Bob", employees.salary: 65000})
            >>> batch.update({employees.salary: employees.salary * 1.05}, employees.department == "Engineering")
            >>> batch.run()
            # All statements are executed in a single transaction.
        """
        self.script = []
        self.table_obj = table_object

    def update(self, update: dict[Column, Any], where: ColumnsOperation, table: Table = None) -> 'BatchOperation':
        """Add an UPDATE statement to the batch operation script.

        This method appends an UPDATE SQL statement to the batch script, which will
        be executed when :meth:`run` is called. The update modifies rows in the
        specified table (or the batch's table if no `table` is provided) that match
        the given `where` condition. The method handles various types of values in
        the `update` dictionary, including literals, :class:`Column` objects (for
        column-to-column assignments), and :class:`ColumnsOperation` objects (for
        computed expressions). All literal values are parameterized to prevent SQL
        injection.

        The method is chainable, returning the `BatchOperation` instance.

        Args:
            update (dict[Column, Any]): A dictionary mapping :class:`Column` objects
                to new values. Values can be:
                - Literals (int, str, float, etc.): will be parameterized as `%s`.
                - :class:`Column` objects: for setting one column to another's value.
                - :class:`ColumnsOperation` objects: for computed expressions
                (e.g., `employees.salary + 1000`).
            where (ColumnsOperation): A :class:`ColumnsOperation` representing the
                condition that determines which rows to update.
            table (Table, optional): An optional :class:`Table` object specifying
                which table to update. If `None`, the batch's original table is used.

        Returns:
            BatchOperation: The current instance, allowing method chaining.

        Raises:
            Exception: This method does not immediately raise exceptions, but errors
                may be raised when :meth:`run` is called if the SQL is malformed or
                parameters are invalid.

        Example:
            Simple batch update with literal values:

            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> batch = employees.batch()
            >>> # Update all employees in 'Engineering' to have salary 60000
            >>> batch.update({employees.salary: 60000}, employees.department == 'Engineering')
            >>> batch.run()

        Example:
            Complex update using ColumnsOperation for computed values and
            a compound condition with a different table:

            >>> from ormophine.Postgresql import ColumnsOperation
            >>> # Increase salary by 10% for managers with >5 years experience,
            >>> # and update the title.
            >>> batch = employees.batch()
            >>> batch.update(
            ...     {
            ...         employees.salary: employees.salary * 1.10,
            ...         employees.title: employees.title + ' (Senior)'
            ...     },
            ...     (employees.title == 'Manager') & (employees.years > 5),
            ...     table=employees  # table parameter is optional
            ... )
            >>> batch.run()
        """
        if not update:
            return self
        temp_list= []
        [None if isinstance(value , Column) else temp_list.append(value) if not isinstance(value, ColumnsOperation) else temp_list.extend(value._output[1]) for key, value in update.items()]
        self.script.append([f'UPDATE {table.name_ if table else self.table_obj.name_} SET {', '.join(f'{key.first_name} = {value.first_name}' if isinstance(value , Column) else f'{key.first_name}=%s' if not isinstance(value , ColumnsOperation) else f'{key.first_name}={value._output[0]}' for key , value in list(update.items()))} WHERE {where._output[0]};', temp_list+where._output[1]])
        return self

    def insert(self, insert: dict[Column, Any], table: Table = None) -> 'BatchOperation':
        """Add an INSERT operation to the batch script.

        This method appends an INSERT statement to the internal batch script list.
        The statement will insert a new row with the given column-value pairs into
        the specified table (or the batch's default table if none is provided).
        When :meth:`run` is called, all batch operations are executed in order
        within a single transaction.

        Args:
            insert (dict[Column, Any]): A dictionary mapping :class:`Column` objects
                to the values to insert. The values can be Python literals (e.g.,
                `str`, `int`, `float`, etc.) that will be passed as parameters to
                the query.
            table (Table, optional): The table to insert into. If not provided,
                the batch's default table (the one used when creating the
                `BatchOperation` instance) will be used. Defaults to `None`.

        Returns:
            BatchOperation: The current instance, allowing method chaining.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> batch = employees.batch()
            >>> # Insert a single employee
            >>> batch.insert({employees.name: "Alice", employees.salary: 60000})
            >>> # Insert another employee into a different table
            >>> departments = driver.departments
            >>> batch.insert({departments.name: "Engineering"}, table=departments)
            >>> # Execute all inserts
            >>> batch.run()
        """
        if not insert:
            self.script.append([f'INSERT INTO {self.table_obj.name_ if not table else table.name_} DEFAULT VALUES;', []])
            return self
        self.script.append([f'INSERT INTO {table.name_ if table else self.table_obj.name_} ({', '.join(i.first_name for i in list(insert.keys()))}) VALUES ({', '.join(f'%s' for k in insert)})' , [v for v in list(insert.values())]])
        return self

    def delete_row(self, where: ColumnsOperation, table: Table = None) -> 'BatchOperation':
        """Add a DELETE statement to the batch operation script.

        This method appends a DELETE SQL statement to the batch script, which will
        be executed when :meth:`run` is called. The deletion removes rows from the
        specified table (or the batch's default table if no `table` is provided)
        that satisfy the given `where` condition. Parameter values from the
        condition are safely parameterized to prevent SQL injection.

        The method is chainable, returning the `BatchOperation` instance itself.

        Args:
            where (ColumnsOperation): A :class:`ColumnsOperation` representing the
                condition that selects which rows to delete.
            table (Table, optional): An optional :class:`Table` object specifying
                from which table to delete. If `None`, the batch's original table
                is used. Defaults to `None`.

        Returns:
            BatchOperation: The current instance, allowing method chaining.

        Raises:
            Exception: This method does not immediately raise exceptions, but errors
                may be raised when :meth:`run` is called if the SQL is malformed or
                parameters are invalid.

        Example:
            Simple batch deletion of all employees in a specific department:

            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> batch = employees.batch()
            >>> batch.delete_row(employees.department == 'Temp')
            >>> batch.run()

        Example:
            Deleting from a different table with a complex condition:

            >>> departments = driver.departments
            >>> batch = employees.batch()
            >>> batch.delete_row(
            ...     (departments.budget < 10000) & (departments.name != 'Core'),
            ...     table=departments
            ... )
            >>> batch.insert(...)  # can chain with other operations
            >>> batch.run()
        """
        self.script.append([f'DELETE FROM {table.name_ if table else self.table_obj.name_} WHERE {where._output[0]};', where._output[1]])
        return self

    def run(self):
        """Execute all batched operations as a single transaction.

        This method sends all accumulated SQL statements (from previous `update()` and
        `insert()` calls) to the database for execution. The operations are performed
        in the order they were added, and the entire batch is executed as a single
        transaction: if any statement fails, all changes are rolled back.

        After execution, the internal script list is not automatically cleared, so
        subsequent calls to `run()` would re‑execute the same statements. Typically,
        a new :class:`BatchOperation` instance should be created for each batch.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) raised during execution of the batch.
                If an error occurs, the transaction is rolled back.

        Example:
            >>> employees = driver.employees
            >>> batch = employees.batch()
            >>> batch.insert({employees.name: "Alice", employees.salary: 60000})
            >>> batch.update({employees.salary: 55000}, employees.department == "Marketing")
            >>> batch.run()
            # Both operations are executed in a single transaction.

        Note:
            The `BatchOperation` instance retains the script after execution. To
            avoid re‑executing the same operations, create a new batch instance
            for each set of operations.
        """
        self.table_obj._excs(self.script)



from __future__ import annotations
from .. import Column, ColumnsOperation


class JoinQuery:
    """
    Fluent builder for PostgreSQL JOIN queries with automatic table aliasing.

    ``JoinQuery`` is returned by :meth:`Table.inner_join`,
    :meth:`Table.left_join`, and :meth:`Table.right_join`. It accumulates
    join clauses lazily (without executing SQL) and lets the caller chain
    additional joins before finally materializing the result set through
    :meth:`get_row`.

    The builder is **immutable**: every call to :meth:`inner_join`,
    :meth:`left_join`, or :meth:`right_join` returns a *new*
    ``JoinQuery`` instance, leaving the original object unchanged. This
    makes it safe to reuse a partially-built query as a starting point for
    multiple downstream queries.

    Automatic table aliasing
    ------------------------
    If the same :class:`Table` object participates in a join more than
    once, a unique alias (e.g. ``orders_0``, ``orders_1``) is generated
    for each additional reference. All column references in subsequent
    ``ON``, ``WHERE``, ``SELECT`` and ``ORDER BY`` clauses are rewritten
    to use these aliases, so ambiguous column references are handled
    transparently.

    Internal state
    --------------
    table_obj (Table): The base table (left side of the first join).
    joins (list[dict]): Accumulated join descriptors. Each dict contains
        the keys ``type`` (e.g. ``"INNER JOIN"``), ``table`` (a
        :class:`Table`), ``condition`` (a :class:`ColumnsOperation`), and
        ``alias`` (a string or ``None``).
    params (list): Bind parameters accumulated from all join conditions,
        kept in the order their placeholders appear in the generated SQL.
    _output (tuple[str, list]): A ``(sql_fragment, parameter_list)`` pair
        mirroring the shape of :class:`ColumnsOperation._output`, so a
        ``JoinQuery`` can be embedded in other expressions if needed.

    Example:
        Chain multiple joins and execute with ``get_row``::

            >>> users = driver.users_j
            >>> orders = driver.orders_j
            >>> products = driver.products_j
            >>> rows = (users.inner_join(orders,   orders.user_id == users.id)
            ...             .inner_join(products, products.id == orders.product_id)
            ...             .get_row([users.username, products.name],
            ...                      where=products.price < 100,
            ...                      order_by=products.price * -1,
            ...                      limit=10))

        Reuse a partial query for two different downstream queries::

            >>> base = users.inner_join(orders, orders.user_id == users.id)
            >>> q1 = base.get_row([users.username])
            >>> q2 = base.get_row([orders.amount],
            ...                   where=orders.amount > 100)
            >>> # `base` is not mutated by either call.

    Note:
        - The condition passed to any ``*_join`` method must be a
          :class:`ColumnsOperation` (e.g. ``orders.user_id == users.id``).
          Passing a string or any other type raises an ``Exception``
          immediately, before any SQL is built.
        - Unlike :class:`Table.get_row`, ``JoinQuery.get_row`` always
          returns a list of tuples — even when only one column is
          selected — because the JOIN may involve multiple tables.
    """

    def __init__(self, table_obj, joins=None, params=None):
        """Initialize a new :class:`JoinQuery` builder.

        This constructor is normally called indirectly through
        :meth:`Table.inner_join`, :meth:`Table.left_join`, or
        :meth:`Table.right_join`. It is also used internally by
        :meth:`_add_join` to produce a new immutable instance when a join is
        appended.

        Args:
            table_obj (Table): The base table on the left side of the first
                join. This table is used as the ``FROM`` clause of the final
                ``SELECT`` statement.
            joins (list[dict], optional): A list of previously accumulated
                join descriptors. Each item must be a dict with keys
                ``type``, ``table``, ``condition``, and ``alias``.
                Defaults to ``None`` (empty join list).
            params (list, optional): Bind parameters accumulated from
                previously added join conditions. Defaults to ``None``
                (empty parameter list).

        Returns:
            None: This method initializes the instance.

        Note:
            The ``_output`` attribute is computed immediately in the
            constructor. When ``joins`` is empty, ``_output`` is set to
            ``('', [])``. Otherwise it is set to ``(self._join_sql(),
            list(self.params))`` so that the resulting fragment is always
            consistent with the current accumulated state.
        """
        self.table_obj = table_obj
        self.joins = joins if joins is not None else []
        self.params = params if params is not None else []
        # _output must be computed after joins/params are stored
        self._output = (self._join_sql(), list(self.params)) if self.joins else ('', [])

    def inner_join(self, table, condition, alias=None) -> 'JoinQuery':
        """Add an ``INNER JOIN`` clause and return a new :class:`JoinQuery`.

        An ``INNER JOIN`` only keeps rows where the ``ON`` condition is true
        on both sides. If the same ``table`` object has already been joined
        earlier in the chain, a unique alias is generated automatically and
        all column references inside ``condition`` (and any subsequent
        ``WHERE`` / ``ORDER BY`` / ``SELECT`` expressions) are rewritten to
        use that alias.

        Args:
            table (Table): The right-hand table to join.
            condition (ColumnsOperation): The ``ON`` condition, expressed as
                a :class:`ColumnsOperation` comparison (e.g.
                ``orders.user_id == users.id``). Passing a non-
                :class:`ColumnsOperation` value raises an exception
                immediately.
            alias (str, optional): An explicit alias for ``table``. When
                ``None`` (default), an alias is generated automatically only
                if the table has already been joined; otherwise the table is
                referenced by its own name.

        Returns:
            JoinQuery: A new :class:`JoinQuery` instance with the
            ``INNER JOIN`` clause appended. The original instance is not
            modified.

        Raises:
            Exception: If ``condition`` is not a :class:`ColumnsOperation`.
                The error message suggests building the condition with
                column comparisons such as ``table1.col == table2.col``.

        Example:
            Single inner join::

                >>> users  = driver.users_j
                >>> orders = driver.orders_j
                >>> rows = (users.inner_join(orders, orders.user_id == users.id)
                ...             .get_row([users.username, orders.amount]))

            Chained inner joins::

                >>> products = driver.products_j
                >>> rows = (users.inner_join(orders,   orders.user_id == users.id)
                ...             .inner_join(products, products.id == orders.product_id)
                ...             .get_row([users.username, products.name]))
        """
        return self._add_join('INNER JOIN', table, condition, alias)

    def left_join(self, table, condition, alias=None) -> 'JoinQuery':
        """Add a ``LEFT JOIN`` clause and return a new :class:`JoinQuery`.

        A ``LEFT JOIN`` keeps all rows from the base (left) table and fills
        columns of the joined table with ``NULL`` when no matching row
        exists. Automatic aliasing and column rewriting behave exactly like
        :meth:`inner_join`.

        Args:
            table (Table): The right-hand table to join.
            condition (ColumnsOperation): The ``ON`` condition (e.g.
                ``orders.user_id == users.id``).
            alias (str, optional): An explicit alias for ``table``. When
                ``None`` (default), an alias is generated automatically if
                the table has already been joined.

        Returns:
            JoinQuery: A new :class:`JoinQuery` instance with the ``LEFT
            JOIN`` clause appended.

        Raises:
            Exception: If ``condition`` is not a :class:`ColumnsOperation`.

        Example:
            Keep unmatched users (with ``NULL`` amounts)::

                >>> rows = (users.left_join(orders, orders.user_id == users.id)
                ...             .get_row([users.username, orders.amount]))
                >>> # Users without orders appear with `None` amount.

            Filter only the unmatched rows::

                >>> rows = (users.left_join(orders, orders.user_id == users.id)
                ...             .get_row([users.username, orders.amount],
                ...                      where=orders.amount == None))
        """
        return self._add_join('LEFT JOIN', table, condition, alias)

    def right_join(self, table, condition, alias=None) -> 'JoinQuery':
        """Add a ``RIGHT JOIN`` clause and return a new :class:`JoinQuery`.

        A ``RIGHT JOIN`` keeps all rows from the joined (right) table and
        fills columns of the base table with ``NULL`` when no matching row
        exists. Automatic aliasing and column rewriting behave exactly like
        :meth:`inner_join`.

        Args:
            table (Table): The right-hand table to join.
            condition (ColumnsOperation): The ``ON`` condition (e.g.
                ``orders.user_id == users.id``).
            alias (str, optional): An explicit alias for ``table``. When
                ``None`` (default), an alias is generated automatically if
                the table has already been joined.

        Returns:
            JoinQuery: A new :class:`JoinQuery` instance with the ``RIGHT
            JOIN`` clause appended.

        Raises:
            Exception: If ``condition`` is not a :class:`ColumnsOperation`.

        Example:
            Keep unmatched orders (with ``NULL`` usernames)::

                >>> rows = (users.right_join(orders, orders.user_id == users.id)
                ...             .get_row([users.username, orders.amount]))

        Note:
            In practice ``RIGHT JOIN`` is less commonly used than
            ``LEFT JOIN``; swapping the two tables and using ``left_join``
            produces the same result and is often clearer.
        """
        return self._add_join('RIGHT JOIN', table, condition, alias)

    def _make_unique_alias(self, table) -> str:
        """Generate a unique alias for a table within this join chain.

        Called internally when the same :class:`Table` object is joined more
        than once. The generated alias follows the pattern
        ``<table_name>_<counter>`` (e.g. ``orders_0``, ``orders_1``), where
        ``counter`` starts at 0 and increases until an unused alias is found.

        Args:
            table (Table): The table for which an alias is needed.

        Returns:
            str: A unique alias string that does not collide with any alias
            already used by the joins accumulated on this instance.

        Example:
            Internal usage::

                >>> # First join of `orders` uses no alias,
                >>> # second join gets 'orders_0', third gets 'orders_1', ...
                >>> alias = self._make_unique_alias(orders)

        Note:
            The surrounding double quotes of the table name are stripped
            before the counter suffix is appended; the alias is later
            re-quoted when it is emitted in the SQL.
        """
        base = table.name_[1:-1]  
        used = {j['alias'] for j in self.joins if j['alias']}
        i = 0
        while f'{base}_{i}' in used:
            i += 1
        return f'{base}_{i}'

    def _add_join(self, join_type, table, condition, alias):
        """Append a join descriptor to the chain and return a new instance.

        This is the shared implementation used by :meth:`inner_join`,
        :meth:`left_join`, and :meth:`right_join`. It validates the
        condition type, auto-generates an alias if the table is already
        joined, and produces a new immutable :class:`JoinQuery` containing
        the extended join list and parameter list.

        Args:
            join_type (str): The SQL join keyword, e.g. ``"INNER JOIN"``,
                ``"LEFT JOIN"``, or ``"RIGHT JOIN"``.
            table (Table): The right-hand table being joined.
            condition (ColumnsOperation): The ``ON`` condition.
            alias (str or None): An explicit alias for ``table``, or ``None``
                to let the method decide (auto-alias only if the table is
                already present in ``self.joins``).

        Returns:
            JoinQuery: A new :class:`JoinQuery` instance whose ``joins``
            list is ``self.joins + [new_descriptor]`` and whose ``params``
            list is extended with the parameters of ``condition``.

        Raises:
            Exception: If ``condition`` is not a
                :class:`ColumnsOperation`.

        Note:
            This method does **not** mutate the current instance. Each call
            returns a brand-new ``JoinQuery``, which is what makes the
            builder safe to reuse.
        """
        if not isinstance(condition, ColumnsOperation):
            raise Exception(
                f"Join condition must be a ColumnsOperation, got "
                f"{type(condition).__name__}. Build it with column comparisons "
                f"like `table1.col == table2.col`."
            )

        already_joined = any(j['table'] is table for j in self.joins)
        if alias is None and already_joined:
            alias = self._make_unique_alias(table)

        new_joins = self.joins + [{
            'type': join_type,
            'table': table,
            'condition': condition,
            'alias': alias,
        }]
        new_params = self.params + list(condition._output[1])
        return JoinQuery(self.table_obj, new_joins, new_params)

    def _join_sql(self) -> str:
        """Build the SQL fragment for all accumulated join clauses.

        Iterates over ``self.joins`` in insertion order and produces a single
        string of the form::

            INNER JOIN "orders" ON (...)
            LEFT  JOIN "products" AS "products_0" ON (...)
            ...

        When a join descriptor carries an alias, all occurrences of the
        table's fully qualified prefix (e.g. ``"orders".``) inside its
        condition are rewritten to the alias prefix (e.g. ``"orders_0".``)
        before the fragment is emitted.

        Returns:
            str: The concatenated join SQL fragment, ready to be inserted
            after the ``FROM <base_table>`` clause. Returns an empty string
            when no joins have been added.

        Example:
            Internal usage::

                >>> jq = users.inner_join(orders, orders.user_id == users.id)
                >>> jq._join_sql()
                'INNER JOIN "orders" ON ("orders"."user_id" = "users"."id")'
        """
        parts = []
        for j in self.joins:
            tbl = j['table']
            cond_sql = j['condition']._output[0]
            if j['alias']:
                cond_sql = cond_sql.replace(
                    f'{tbl.name_}.', f'"{j["alias"]}".'
                )
                parts.append(
                    f'{j["type"]} {tbl.name_} AS "{j["alias"]}" ON {cond_sql}'
                )
            else:
                parts.append(f'{j["type"]} {tbl.name_} ON {cond_sql}')
        return ' '.join(parts)

    def _resolve_column_ref(self, col) -> str:
        """Return the SQL reference for a column, using its join alias if any.

        Given a :class:`Column`, this helper scans the accumulated joins and,
        if the column's parent table appears with an alias, rewrites the
        reference to use that alias. This is necessary when the same table
        has been joined more than once and the plain ``table.column`` form
        would be ambiguous.

        Args:
            col (Column): The column whose SQL reference is needed.

        Returns:
            str: Either the plain qualified name (e.g.
            ``'"users"."id"'``) or the aliased form (e.g.
            ``'"users_0"."id"'``) when the parent table is aliased.

        Example:
            Internal usage::

                >>> ref = jq._resolve_column_ref(users.id)
                >>> ref
                '"users"."id"'
        """
        for j in self.joins:
            if j['table'] is col.table_obj:
                if j['alias']:
                    return f'"{j["alias"]}"."{col.first_name[1:-1]}"'
                return col.name
        return col.name

    def _rewrite_expr(self, expr: str) -> str:
        """Rewrite table references inside a SQL expression to use aliases.

        This helper walks through the accumulated joins and, for each join
        that carries an alias, replaces occurrences of the table's fully
        qualified prefix (e.g. ``"orders".``) with the alias prefix (e.g.
        ``"orders_0".``). It is used to preprocess strings produced by
        :class:`ColumnsOperation` before they are placed in the final SQL.

        Args:
            expr (str): A SQL fragment that may contain table-qualified
                column references.

        Returns:
            str: The same fragment with aliased table references.

        Example:
            Internal usage::

                >>> jq._rewrite_expr('("orders"."total" * %s)')
                '("orders_0"."total" * %s)'
        """
        for j in self.joins:
            if j['alias']:
                expr = expr.replace(
                    f'{j["table"].name_}.', f'"{j["alias"]}".'
                )
        return expr

    def get_row(
        self,
        which_columns: list,
        where: 'ColumnsOperation' = None,
        order_by: 'Column | ColumnsOperation' = None,
        limit: int = None,
        offset: int = None,
        ):
        """Execute the accumulated JOIN query and return the fetched rows.

        Builds and runs a ``SELECT ... FROM base_table <joins> [WHERE ...]
        [ORDER BY ...] [LIMIT ...] [OFFSET ...]`` statement using the joins
        accumulated on this :class:`JoinQuery` instance. Column references
        inside ``which_columns``, ``where`` and ``order_by`` are automatically
        rewritten to use table aliases when a table participates in the join
        more than once, preventing ambiguous column errors.

        Args:
            which_columns (list[Column | ColumnsOperation]): The columns or
                computed expressions to include in the ``SELECT`` list. Each
                element can be a :class:`Column` (returned as-is with a
                ``<table>_<column>`` alias) or a :class:`ColumnsOperation`
                (e.g. ``orders.total * -1``, ``users.name.upper()``). The
                method returns a list of tuples, one per row.
            where (ColumnsOperation, optional): A condition object for
                filtering rows. Both sides of the condition may reference
                columns from any table in the join; aliases are applied
                automatically. Defaults to ``None`` (no filter).
            order_by (Column | ColumnsOperation, optional): The expression
                to order the results by. May be a plain :class:`Column`
                (e.g. ``users.id``) or a computed
                :class:`ColumnsOperation` (e.g. ``orders.total * -1``,
                ``users.name.upper()``). Sorting direction follows SQL
                semantics — use ``col * -1`` for descending order.
                Defaults to ``None`` (no ordering).
            limit (int, optional): Maximum number of rows to return. A value
                of ``0`` returns an empty list. Defaults to ``None`` (no
                limit).
            offset (int, optional): Number of rows to skip before returning
                results. When ``offset`` is greater than the total number of
                rows, an empty list is returned. Defaults to ``None`` (no
                offset).

        Returns:
            list[tuple]: A list of tuples, one per returned row, where each
            tuple contains the values in the order of ``which_columns``.

        Raises:
            Exception: Propagates any database errors
                (:class:`OperationalError`, :class:`ProgrammingError`, etc.)
                with additional context about the failing query.

        Example:
            Basic inner join::

                >>> users = driver.users_j
                >>> orders = driver.orders_j
                >>> rows = (users.inner_join(orders, orders.user_id == users.id)
                ...             .get_row([users.username, orders.amount]))
                >>> # [('alice', 1000), ('alice', 20), ('bob', 50)]

            Chained joins with a computed ``ORDER BY``::

                >>> rows = (users.inner_join(orders, orders.user_id == users.id)
                ...             .inner_join(products,
                ...                        products.id == orders.product_id)
                ...             .get_row([users.username, products.name],
                ...                      where=products.price < 100,
                ...                      order_by=products.price * -1,
                ...                      limit=2,
                ...                      offset=1))

            Using ``order_by`` with a string operation (case-insensitive sort)::

                >>> rows = (users.inner_join(orders, orders.user_id == users.id)
                ...             .get_row([users.id],
                ...                      order_by=users.username.upper()))

            Full stack: filter + computed order + pagination::

                >>> rows = (users.inner_join(orders, orders.user_id == users.id)
                ...             .get_row([orders.amount],
                ...                      where=orders.amount > 20,
                ...                      order_by=orders.amount * -1 + 1000,
                ...                      limit=2,
                ...                      offset=1))
        """
        if not which_columns:
            return []
        tl = []
        select_parts = []
        for i in which_columns:
            if isinstance(i, Column):
                ref = self._resolve_column_ref(i)
                alias = f'{i.table_obj.name_[1:-1]}_{i.first_name[1:-1]}'
                select_parts.append(f'{ref} AS {alias}')
            else:  
                expr = self._rewrite_expr(i._output[0])
                tl.extend(i._output[1])
                if expr.startswith('(') and expr.endswith(')'):
                    expr = expr[1:-1]
                alias = (
                    f'{i.col_obj.table_obj.name_[1:-1]}_'
                    f'{i.col_obj.first_name[1:-1]}'
                )
                select_parts.append(f'{expr} AS {alias}')

        ob = []
        if isinstance(order_by, Column):
            order_sql = self._resolve_column_ref(order_by)
        elif isinstance(order_by, ColumnsOperation):
            order_sql = self._rewrite_expr(order_by._output[0])
            ob.extend(order_by._output[1])
        else:
            order_sql = None

        sql = (
            f"SELECT {', '.join(select_parts)} "
            f"FROM {self.table_obj.name_} "
            f"{self._join_sql()}"
        )
        all_params = tl + list(self.params)

        if where is not None:
            sql += f' WHERE {self._rewrite_expr(where._output[0])}'
            all_params += list(where._output[1])

        if order_sql is not None:
            sql += f' ORDER BY {order_sql}'
            all_params += ob

        if limit is not None:
            sql += ' LIMIT %s'
            all_params.append(limit)
        if offset is not None:
            sql += ' OFFSET %s'
            all_params.append(offset)

        sql += ';'

        rows = self.table_obj._excfp(sql, all_params) if all_params else self.table_obj._excf(sql)
        return rows

from __future__ import annotations
from .. import Column, ColumnsOperation, BatchOperation, JoinQuery
from typing import Any

class Table:

    class _PlaceHolder:
        """Marker object used in :meth:`Table.bulk_update` to inject row values.

        When building a bulk update such as ``employees.salary + employees.PLACE_HOLDER``,
        the placeholder tells :class:`ColumnsOperation` to treat the operand as numeric
        (not string), avoiding ambiguity between ``+`` and ``||``. During
        :meth:`Table.bulk_update`, each placeholder is replaced by a fresh ``%s`` and
        bound to the matching value from ``data_list``. Users normally interact with
        ``table.PLACE_HOLDER``; the literal string can be changed per-table if it
        collides with application data:
        ``my_table.PLACE_HOLDER = "your_own_marker"``.
        """
        def __init__(self, placeholder):
            self.placeholder = placeholder
        
        def __str__(self):
            return self.placeholder
        
    def __init__(self, obj: Driver, table_name: str):
        """Initialize a Table instance representing an existing database table.

        This constructor retrieves the table's schema from the database using
        `get_table_info()` and dynamically creates `Column` attributes for each
        column, allowing direct access via attribute names (e.g., `table.id`).

        Args:
            obj (Driver): The Driver instance managing the database connection pool.
            table_name (str): The name of the existing table in the database.

        Raises:
            Exception: If the table does not exist or the schema cannot be retrieved.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(host='localhost', port=5432, username='user',
            ...                 password='pass', db_name='mydb')
            >>> users = Table(driver, 'users')
            >>> # Access columns as attributes
            >>> users.id, users.name, users.age
            (<Column 'users'."id">, <Column 'users'."name">, <Column 'users'."age">)
        """
        self.name_ = f'"{table_name}"'
        self.db_obj = obj
        self.PLACE_HOLDER = self._PlaceHolder('_MY_S4ULT3D_PL4C3_H0LD3R_%s_')
        for i in self.get_table_info():
            self.__setattr__(i['name'], Column(self, i['name'], i['datatype']))

    def get_table_info(self):
        """Retrieve detailed schema information for all columns of the current table.

        This method queries PostgreSQL's information_schema and related system
        catalogs to obtain comprehensive metadata for each column, including data
        type, nullability, default value, primary key status, auto-increment,
        foreign key references, and more. The returned data is used internally to
        construct `Column` objects and is also useful for introspection.

        Returns:
            list[dict]: A list of dictionaries, each containing the following keys:

                - cid (int): Column ordinal position (1-based).
                - name (str): Column name.
                - type (str): SQL data type name as reported by PostgreSQL.
                - datatype (type): Python type mapping (int, float, str, bytes, or bool)
                inferred from the SQL type.
                - notnull (bool): True if the column is NOT NULL.
                - dflt_value (str or None): Column default value expression, if any.
                - pk (bool): True if the column is part of the primary key.
                - full_type (str): The full user-defined data type name (or the
                base type if not available).
                - auto_increment (bool): True if the column is an identity column
                (GENERATED AS IDENTITY).
                - num_precision (int or None): Numeric precision for numeric/decimal
                columns.
                - num_scale (int or None): Numeric scale for numeric/decimal columns.
                - datetime_precision (int or None): Precision for date/time columns.
                - fk_name (str or None): Name of the foreign key constraint, if any.
                - fk_table (str or None): Referenced table name for a foreign key.
                - fk_column (str or None): Referenced column name for a foreign key.
                - fk_on_update (str or None): ON UPDATE action for foreign key.
                - fk_on_delete (str or None): ON DELETE action for foreign key.

        Raises:
            ProgrammingError: If the query has a syntax error or the table does not exist.
            OperationalError: If a connection issue occurs during execution.
            Exception: Wrapped exceptions from the underlying driver's `_excfp` method.

        Example:
            >>> from ormophine.Postgresql import Driver
            >>> db = Driver(host='localhost', port=5432, username='user',
            ...             password='pass', db_name='test')
            >>> employees = db.employees  # Table object
            >>> info = employees.get_table_info()
            >>> for col in info:
            ...     print(f"{col['name']}: {col['datatype']} (PK: {col['pk']})")
            id: <class 'int'> (PK: True)
            name: <class 'str'> (PK: False)
            salary: <class 'float'> (PK: False)
        """
        query = """
            SELECT
                c.ordinal_position AS cid,
                c.column_name AS name,
                c.data_type AS type,
                CASE WHEN c.is_nullable = 'NO' THEN 1 ELSE 0 END AS notnull,
                c.column_default AS dflt_value,
                CASE WHEN tc.constraint_type = 'PRIMARY KEY' THEN 1 ELSE 0 END AS pk,
                c.udt_name AS full_type,
                CASE WHEN c.is_identity = 'YES' THEN 1 ELSE 0 END AS auto_increment,
                c.numeric_precision AS num_precision,
                c.numeric_scale AS num_scale,
                c.datetime_precision AS datetime_precision,
                rc.unique_constraint_name AS fk_name,
                ccu.table_name AS fk_table,
                ccu.column_name AS fk_column,
                rc.update_rule AS fk_on_update,
                rc.delete_rule AS fk_on_delete
            FROM information_schema.columns c
            LEFT JOIN information_schema.key_column_usage kcu
                ON c.table_schema = kcu.table_schema
                AND c.table_name = kcu.table_name
                AND c.column_name = kcu.column_name
                AND kcu.position_in_unique_constraint IS NOT NULL
            LEFT JOIN information_schema.referential_constraints rc
                ON kcu.constraint_schema = rc.constraint_schema
                AND kcu.constraint_name = rc.constraint_name
            LEFT JOIN information_schema.constraint_column_usage ccu
                ON rc.constraint_schema = ccu.constraint_schema
                AND rc.constraint_name = ccu.constraint_name
            LEFT JOIN information_schema.table_constraints tc
                ON c.table_schema = tc.table_schema
                AND c.table_name = tc.table_name
                AND tc.constraint_type = 'PRIMARY KEY'
                AND EXISTS (
                    SELECT 1 FROM information_schema.constraint_column_usage ccu2
                    WHERE tc.constraint_name = ccu2.constraint_name
                    AND ccu2.column_name = c.column_name
                )
            WHERE c.table_schema = current_schema()
                AND c.table_name = %s
            ORDER BY c.ordinal_position;
        """
        return [{'cid': row[0],'name': row[1],'type': row[2],'datatype': (int if row[2].lower() in ('smallint', 'integer', 'bigint', 'serial', 'smallserial', 'bigserial') else float) if row[2].lower() in ('smallint', 'integer', 'bigint', 'serial', 'smallserial', 'bigserial', 'bit', 'numeric', 'decimal', 'real', 'double precision', 'money') else bool if row[2].lower() == 'boolean' else object if row[2].lower() in ('date', 'time without time zone', 'time with time zone','timestamp without time zone', 'timestamp with time zone','interval') else bool if row[2].lower() == 'boolean' else bytes if row[2].lower() == 'bytea' else str if row[2].lower() in ('character varying', 'character', 'text', 'json', 'jsonb', 'uuid', 'date', 'time without time zone', 'time with time zone', 'timestamp without time zone', 'timestamp with time zone', 'interval') else bytes if row[2].lower() == 'bytea' else bool if row[2].lower() == 'boolean' else str,'notnull': bool(row[3]),'dflt_value': row[4],'pk': bool(row[5]),'full_type': row[6] if row[6] else row[2],'auto_increment': bool(row[7]),'num_precision': row[8],'num_scale': row[9],'datetime_precision': row[10],'fk_name': row[11],'fk_table': row[12],'fk_column': row[13],'fk_on_update': row[14],'fk_on_delete': row[15]} for row in self._excfp(query, (self.name_.strip('"'),))]
        
    def _exc(self, query):
        """Execute a SQL query with no parameters.

        This is an internal wrapper that delegates the execution to the underlying
        :class:`Driver` instance. It is used for statements that do not require
        parameter substitution (e.g., DDL statements, queries with no placeholders).

        Args:
            query (str): The SQL query string to execute.

        Returns:
            None

        Raises:
            Exception: Propagates any exceptions raised by the underlying driver,
                such as :exc:`psycopg.OperationalError` or :exc:`psycopg.ProgrammingError`.

        Example:
            >>> table._exc('DROP INDEX IF EXISTS idx_name;')
        """
        self.db_obj._exc(query)

    def _excp(self, query, params):
        """Execute a parameterized SQL query without fetching results.

        This is a low-level wrapper method that delegates execution to the
        underlying :class:`Driver` instance's ``_excp`` method. It is used
        internally for queries that modify data (INSERT, UPDATE, DELETE, etc.)
        and do not return result sets. The method handles parameter binding
        and transaction management through the driver's connection pool.

        Args:
            query (str): The SQL query string with ``%s`` placeholders for
                parameters.
            params (list or tuple): The parameter values to bind to the query
                placeholders. The number of items must match the number of
                placeholders.

        Returns:
            None: This method does not return any value.

        Raises:
            Exception: Propagates any database-related exceptions (e.g.,
                :class:`psycopg.OperationalError`, :class:`psycopg.ProgrammingError`)
                raised by the underlying driver. The exception message will
                include the query and parameters for debugging.

        Example:
            >>> # Assuming `table` is an instance of Table
            >>> table._excp("UPDATE users SET age = %s WHERE id = %s", [30, 1])
            # The query is executed with the provided parameters.

        Note:
            This method is intended for internal use. For most operations,
            prefer using higher-level methods like :meth:`Table.update`,
            :meth:`Table.insert`, or :meth:`Table.delete_row`.
        """
        self.db_obj._excp(query, params)

    def _excf(self, query):
        """Execute a query and fetch all resulting rows.

        This is a convenience wrapper that delegates to the underlying
        :class:`Driver` object's `_excf` method. It is used internally for
        SELECT queries where all results are needed.

        Args:
            query (str): The SQL query string to execute.

        Returns:
            list[tuple]: A list of tuples representing the fetched rows.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) raised by the driver, with additional
                context about the query.

        Example:
            >>> table = Table(driver, "employees")
            >>> rows = table._excf("SELECT * FROM \"employees\" WHERE id > 10")
            >>> for row in rows:
            ...     print(row)
        """
        return self.db_obj._excf(query)

    def _excfp(self, query, params):
        """Execute a parameterized query and fetch all resulting rows.

        This is a convenience wrapper that delegates to the underlying
        :class:`Driver` object's `_excfp` method. It is used internally for
        SELECT queries that require parameter substitution and return a full
        result set.

        Args:
            query (str): The SQL query string containing placeholder markers
                (e.g., ``%s``) for parameters.
            params (list or tuple): The parameter values to substitute into the
                query placeholders.

        Returns:
            list[tuple]: A list of tuples, each representing a row from the
            query result.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) raised by the driver, with additional
                context about the query and parameters.

        Example:
            >>> table = Table(driver, "employees")
            >>> rows = table._excfp(
            ...     "SELECT name, salary FROM \"employees\" WHERE dept_id = %s",
            ...     [10]
            ... )
            >>> for name, salary in rows:
            ...     print(f"{name}: {salary}")
        """
        return self.db_obj._excfp(query, params)

    def _excm(self, query, params):
        """Execute a query with multiple parameter sets using executemany.

        This is a convenience wrapper that delegates to the underlying
        :class:`Driver` object's `_excm` method. It is used for bulk operations
        such as inserting or updating multiple rows with a single query, where
        each set of parameters corresponds to one row.

        Args:
            query (str): The SQL query string with placeholders (e.g., %s).
            params (list[tuple]): A list of parameter tuples, one for each
                execution. Each tuple contains the values to substitute into
                the query placeholders.

        Returns:
            None: This method does not return any value.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) raised by the driver, with additional
                context about the query and parameters.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> table = Table(driver, "employees")
            >>> # Bulk insert two rows
            >>> query = "INSERT INTO \"employees\" (name, age) VALUES (%s, %s)"
            >>> params = [("Alice", 30), ("Bob", 25)]
            >>> table._excm(query, params)
        """
        self.db_obj._excm(query, params)

    def _excs(self, query_params: list):
        """Execute multiple SQL statements as a batch in a single transaction.

        This internal method delegates to the underlying :class:`Driver` object's
        `_excs` method. It is used to run a list of SQL queries (optionally with
        parameters) together, ensuring atomicity: either all succeed or the entire
        batch is rolled back. This is primarily utilized by :class:`BatchOperation`
        when executing a script.

        Args:
            query_params (list): A list of queries to execute. Each item can be:
                - A string representing a query without parameters.
                - A list or tuple of two elements: ``[query, params]``, where
                ``params`` is a sequence of parameter values.

        Returns:
            None

        Raises:
            Exception: Propagates any database errors (e.g., OperationalError,
                ProgrammingError) raised by the driver. The exception message
                includes details about the failing query and its parameters.

        Example:
            >>> table = Table(driver, "employees")
            >>> queries = [
            ...     ["UPDATE employees SET salary = salary * 1.1 WHERE id = %s", [1]],
            ...     "UPDATE employees SET salary = salary * 1.05 WHERE id = 2"
            ... ]
            >>> table._excs(queries)  # Both updates run in a single transaction
        """
        self.db_obj._excs(query_params)

    def get_columns_name(self):
        """Retrieve the names of all columns in the table.

        This method fetches the current table schema information and returns
        a list containing the name of each column.

        Returns:
            list[str]: A list of column names as strings.

        Example:
            >>> table = Table(driver, "employees")
            >>> columns = table.get_columns_name()
            >>> print(columns)
            ['id', 'name', 'department_id', 'salary']
        """
        return [i['name'] for i in self.get_table_info()]
      
    def batch(self) -> 'BatchOperation':
        """Create a new batch operation builder for this table.

        Batch operations allow multiple SQL statements (INSERT and UPDATE) to be
        grouped together and executed in a single transaction, improving performance
        when performing multiple modifications. This method returns a
        :class:`BatchOperation` instance that can be used to chain multiple
        operations before executing them with :meth:`BatchOperation.run`.

        Returns:
            BatchOperation: A new batch operation builder associated with this table.

        Example:
            Simple batch with an INSERT and an UPDATE:

            >>> employees = driver.employees
            >>> batch_op = employees.batch()
            >>> batch_op.insert({employees.name: "Alice", employees.salary: 60000})
            >>> batch_op.update(
            ...     {employees.salary: 55000},
            ...     employees.department == "Marketing"
            ... )
            >>> batch_op.run()

        Example:
            Complex batch using ColumnsOperation for computed values and conditions:

            >>> from ormophine.Postgresql import ColumnsOperation
            >>> # Increase salary by 10% for managers with more than 5 years experience,
            >>> # and give a bonus to senior engineers.
            >>> batch_op = employees.batch()
            >>> batch_op.update(
            ...     {
            ...         employees.salary: employees.salary * 1.10,
            ...         employees.title: employees.title + " (Senior)"
            ...     },
            ...     (employees.title == "Manager") & (employees.years > 5)
            ... )
            >>> batch_op.update(
            ...     {employees.bonus: employees.salary * 0.05},
            ...     employees.title.contains("Engineer") & (employees.level >= 3)
            ... )
            >>> batch_op.insert({employees.name: "Bob", employees.salary: 70000})
            >>> batch_op.run()
            # This executes all statements in a single transaction.
        """
        return BatchOperation(self)

    def update(self, update: dict[Column, Any], where: 'ColumnsOperation') -> None:
        """Update rows in the table that match a condition.

        This method constructs and executes an UPDATE SQL statement, setting
        specified columns to new values for all rows that satisfy the given
        condition. It safely handles parameterized values to prevent SQL injection.

        Args:
            update (dict[Column, Any]): A dictionary mapping :class:`Column` objects
                to their new values. Values can be literals, other :class:`Column`
                objects (for column-to-column assignment), or
                :class:`ColumnsOperation` objects (for computed expressions).
            where (ColumnsOperation): A :class:`ColumnsOperation` object representing
                the condition that determines which rows to update.

        Returns:
            None: This method executes the update and does not return a value.

        Raises:
            Exception: Propagates any database errors raised during execution,
                including parameter binding or SQL syntax issues.

        Example:
            Simple update with literal values:

            >>> from ormophine.Postgresql import Driver, Table, Column, DataTypes
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> employees = driver.employees
            >>> # Assuming columns exist: id, name, salary, department
            >>> employees.update(
            ...     {employees.salary: 50000},
            ...     employees.department == "Engineering"
            ... )
            >>> # All engineers now have salary 50000.

        Example:
            Complex update using ColumnsOperation for computed values and
            a compound condition:

            >>> from ormophine.Postgresql import ColumnsOperation
            >>> # Increase salary by 10% for managers with experience > 5 years
            >>> employees.update(
            ...     {
            ...         employees.salary: employees.salary * 1.1,  # ColumnOperation
            ...         employees.title: employees.title + " (Senior)"  # string concatenation
            ...     },
            ...     (employees.title == "Manager") & (employees.years > 5)
            ... )
            >>> # This produces: UPDATE "employees" SET "salary" = ("salary" * 1.1),
            >>> # "title" = ("title" || ' (Senior)') WHERE ("title" = 'Manager' AND "years" > 5);
        """
        if not update:
            return
        temp_list = []
        [None if isinstance(value , Column) else temp_list.append(value) if not isinstance(value, ColumnsOperation) else temp_list.extend(value._output[1]) for key, value in update.items()]
        self._excp(f"UPDATE {self.name_} SET {', '.join(f'{key.first_name} = {value.first_name}' if isinstance(value, Column) else f'{key.first_name}=%s' if not isinstance(value, ColumnsOperation) else f'{key.first_name}={value._output[0]}' for key, value in list(update.items()))} WHERE {where._output[0]};", temp_list + where._output[1])

    def get_row(self, which_columns: list['Column' | 'ColumnsOperation'], where: 'ColumnsOperation' = None, order_by: 'Column | ColumnsOperation' = None, limit: int = None, offset: int = None):
        """Fetch rows from the table with selected columns, filtering, ordering and pagination.

        Builds and executes a ``SELECT`` query. The columns can be plain
        :class:`Column` objects or computed :class:`ColumnsOperation`
        expressions. If only one column is requested, the method returns a
        flat list of values from that column; otherwise, it returns a list of
        tuples representing the full rows.

        Args:
            which_columns (list[Column | ColumnsOperation]): A list of columns
                or expressions to select. Each element can be a :class:`Column`
                object or a :class:`ColumnsOperation` (e.g., arithmetic,
                string functions).
            where (ColumnsOperation, optional): A condition object for
                filtering rows. Defaults to ``None`` (no filter).
            order_by (Column | ColumnsOperation, optional): A :class:`Column`
                or a computed :class:`ColumnsOperation` to order the results
                by (e.g. ``tbl.val * -1`` for descending, ``tbl.name.upper()``
                for case-insensitive sorting). Defaults to ``None``
                (no ordering).
            limit (int, optional): Maximum number of rows to return. A value
                of ``0`` returns an empty list. Defaults to ``None`` (no
                limit).
            offset (int, optional): Number of rows to skip before returning
                results. When greater than the total row count, returns an
                empty list. Defaults to ``None`` (no offset).

        Returns:
            list: If only one column is specified in ``which_columns``, returns
                a list of the values from that column (one per row). If
                multiple columns are specified, returns a list of tuples, each
                tuple representing a row with values in the order of the
                selected columns.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) with additional context about the query.

        Example:
            Simple selection with a condition and ordering::

                >>> employees = driver.employees
                >>> names = employees.get_row(
                ...     [employees.name],
                ...     where=employees.department == "Engineering",
                ...     order_by=employees.salary
                ... )

            Complex query with computed columns, ``ORDER BY`` on an operation,
            and pagination::

                >>> employees.get_row(
                ...     [employees.id,
                ...      employees.first_name + " " + employees.last_name],
                ...     where=employees.salary > 50000,
                ...     order_by=employees.salary * -1,
                ...     limit=10,
                ...     offset=20
                ... )
                # ORDER BY ("salary" * -1) LIMIT 10 OFFSET 20

            Case-insensitive ordering using a string operation::

                >>> names = employees.get_row(
                ...     [employees.name],
                ...     order_by=employees.name.upper()
                ... )
                # ORDER BY (UPPER("name"))
        """
        if not which_columns:
            return
        tl, wc, af, ob = [], [], [], []
        af.append(limit) if not limit is None else None
        af.append(offset) if not offset is None else None
        ob.extend(order_by._output[1]) if isinstance(order_by, ColumnsOperation) else None
        [wc.append(i.first_name) if isinstance(i,Column) else [wc.append(i._output[0]), tl.extend(i._output[1])] for i in which_columns]
        return [row[0] for row in (self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} WHERE {where._output[0]} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''} {' LIMIT %s ' if not limit is None else ''}{' OFFSET %s ' if not offset is None else ''};', tl+where._output[1]+ob+af) if where else self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''} {' LIMIT %s ' if not limit is None else ''}{' OFFSET %s ' if not offset is None else ''};',tl+ob+af) if tl else self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''} {' LIMIT %s ' if not limit is None else ''}{' OFFSET %s ' if not offset is None else ''};',ob+af) if af or ob else self._excf(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''};',))] if len(which_columns) == 1 else self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} WHERE {where._output[0]} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''} {' LIMIT %s ' if not limit is None else ''}{' OFFSET %s ' if not offset is None else ''};', tl+where._output[1]+ob+af) if where else self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''} {' LIMIT %s ' if not limit is None else ''}{' OFFSET %s ' if not offset is None else ''};',tl+ob+af) if tl else self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''} {' LIMIT %s ' if not limit is None else ''}{' OFFSET %s ' if not offset is None else ''};',ob+af) if af or ob else self._excf(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if isinstance(order_by, Column) else f'ORDER BY {order_by._output[0]}' if isinstance(order_by, ColumnsOperation) else ''};',)
        # Yeah, this line is ~2500 chars. Pure art — my signature is right there :D

    def insert(self, insert: dict['Column', Any]) -> None:
        """Insert a single row into the table.

        This method constructs and executes an INSERT statement, adding a new row
        with the specified column values. It safely parameterizes values to prevent
        SQL injection.

        Args:
            insert (dict[Column, Any]): A dictionary mapping :class:`Column` objects
                to the values to insert. Keys must be :class:`Column` instances
                belonging to this table, and values can be any Python type that
                is compatible with the column's SQL data type.

        Returns:
            None: This method executes the insert and does not return a value.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) raised during execution, with additional
                context about the query and parameters.

        Example:
            Simple insert with literal values:

            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> employees = Table(driver, "employees")
            >>> employees.insert({
            ...     employees.name: "Alice",
            ...     employees.salary: 60000,
            ...     employees.department: "Engineering"
            ... })
            # Inserts a new row with the given values.
        """
        if not insert:
            self._exc(f'INSERT INTO {self.name_} DEFAULT VALUES;')
            return
        self._excp(f'INSERT INTO {self.name_} ({', '.join(i.first_name for i in list(insert.keys()))}) VALUES ({', '.join(f'%s' for k in insert)})', [v for v in list(insert.values())])

    def custom_execute(self, query: str, params: list = None) -> None:
        """Execute a custom SQL query with optional parameters.

        This method provides a flexible way to execute arbitrary SQL statements
        (e.g., DDL, DML) that are not covered by the ORM's built-in methods.
        It automatically handles parameter binding and connection management
        through the underlying driver.

        Args:
            query (str): The SQL query string to execute.
            params (list, optional): A list of parameter values to bind to the query.
                If provided, the query will be executed using parameterized execution
                to prevent SQL injection. Defaults to None.

        Returns:
            None: This method executes the query and does not return any data.
                For queries that return results, use :meth:`custom_execute_with_fetch`.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) raised during execution, with additional
                context about the query and parameters.

        Example:
            Simple DDL execution:

            >>> employees = Table(driver, "employees")
            >>> employees.custom_execute(
            ...     "ALTER TABLE employees ADD COLUMN bonus DECIMAL(10,2)"
            ... )

        Example:
            Parameterized query for bulk operations:

            >>> employees.custom_execute(
            ...     "UPDATE employees SET salary = salary * 1.05 WHERE department = %s",
            ...     ["Engineering"]
            ... )
            # All engineers get a 5% salary increase.
        """
        self._excp(query, params) if params else self._exc(query)

    def custom_execute_many(self, query: str, params: list = None) -> None:
        """Execute a parameterized SQL statement multiple times with different parameter sets.

        This method is a convenience wrapper around :meth:`_excm` that allows bulk
        execution of the same SQL statement (e.g., INSERT, UPDATE, DELETE) with
        multiple parameter lists. It is useful for batch operations where many rows
        need to be inserted or updated efficiently.

        Args:
            query (str): The SQL query string with placeholders (``%s``) for parameters.
            params (list, optional): A list of parameter tuples or lists, each
                corresponding to one execution of the query. Defaults to None.

        Returns:
            None: This method executes the queries and does not return a value.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) raised by the underlying driver, with
                additional context about the query and parameters.

        Example:
            Simple bulk insert of multiple employee records:

            >>> employees = Table(driver, "employees")
            >>> query = "INSERT INTO \"employees\" (name, salary) VALUES (%s, %s)"
            >>> params = [("Alice", 60000), ("Bob", 55000), ("Charlie", 70000)]
            >>> employees.custom_execute_many(query, params)
            # All three rows are inserted in a single executemany call.

        Example:
            Bulk update with varying conditions:

            >>> query = "UPDATE \"employees\" SET salary = salary * 1.1 WHERE id = %s"
            >>> params = [(1,), (2,), (3,)]
            >>> employees.custom_execute_many(query, params)
            # This updates salaries for employees with IDs 1, 2, and 3.
        """
        self._excm(query, params)

    def custom_execute_with_fetch(self, query: str, params: list = None) -> Any:
        """Execute a custom SQL query and return the fetched results.

        This method provides a flexible way to run arbitrary SELECT queries against
        the table's database connection. It supports both parameterized and
        non‑parameterized queries and returns the full result set.

        Args:
            query (str): The SQL query string to execute. For parameterized queries,
                use `%s` placeholders.
            params (list, optional): A list of parameter values to bind to the query.
                Defaults to None, which executes the query without parameters.

        Returns:
            Any: The query result. Typically this is a list of tuples representing
                the fetched rows, but the exact return type depends on the underlying
                driver's fetchall() implementation.

        Raises:
            Exception: Propagates any database errors (OperationalError,
                ProgrammingError, etc.) with additional context about the query.

        Example:
            Simple query without parameters:

            >>> employees = Table(driver, "employees")
            >>> rows = employees.custom_execute_with_fetch(
            ...     "SELECT * FROM \"employees\" WHERE salary > 50000"
            ... )
            >>> for row in rows:
            ...     print(row)

        Example:
            Parameterized query with placeholders:

            >>> employees = Table(driver, "employees")
            >>> rows = employees.custom_execute_with_fetch(
            ...     "SELECT name, salary FROM \"employees\" WHERE department = %s",
            ...     ["Engineering"]
            ... )
            >>> for name, salary in rows:
            ...     print(f"{name}: {salary}")
        """
        return self._excfp(query, params) if params else self._excf(query)

    def delete_row(self, where: 'ColumnsOperation') -> None:
        """Delete rows from the table that match a condition.

        This method constructs and executes a DELETE SQL statement, removing all
        rows from the table that satisfy the given condition. The condition is
        represented by a :class:`ColumnsOperation` object, which can include
        comparisons, logical operators, and function calls.

        Args:
            where (ColumnsOperation): A :class:`ColumnsOperation` object representing
                the condition that determines which rows to delete.

        Returns:
            None: This method executes the deletion and does not return a value.

        Raises:
            Exception: Propagates any database errors raised during execution,
                including parameter binding or SQL syntax issues.

        Example:
            Simple deletion by a single condition:

            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> employees = Table(driver, "employees")
            >>> # Delete all employees in the "Intern" department
            >>> employees.delete_row(employees.department == "Intern")

        Example:
            Deletion using a compound condition with ColumnsOperation:

            >>> # Delete employees with salary less than 30000 and years > 10
            >>> employees.delete_row(
            ...     (employees.salary < 30000) & (employees.years > 10)
            ... )
            >>> # This produces: DELETE FROM "employees" WHERE ("salary" < 30000 AND "years" > 10);
        """
        self._excp(f'DELETE FROM {self.name_} WHERE {where._output[0]};', where._output[1])

    def delete_table(self, are_you_sure: bool, are_you_really_sure: bool, for_sure: bool) -> None:
        """Permanently drop the current table from the database.

        This method executes a DROP TABLE statement to remove the table and all its
        data from the database. It also deletes the corresponding :class:`Table`
        attribute from the parent :class:`Driver` instance. To prevent accidental
        deletion, three separate confirmation flags must all be `True`.

        Args:
            are_you_sure (bool): First confirmation flag.
            are_you_really_sure (bool): Second confirmation flag.
            for_sure (bool): Third confirmation flag.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: Propagates any database errors from the underlying driver
                if the DROP TABLE statement fails.

        Example:
            >>> employees = Table(driver, "employees")
            >>> # Permanently delete the employees table
            >>> employees.delete_table(True, True, True)
            >>> # After deletion, the table is no longer accessible via driver.employees

        Note:
            This operation is irreversible. Use the confirmation flags as a safeguard
            against accidental data loss.
        """
        if are_you_sure and are_you_really_sure and for_sure:
            self._exc(f'DROP TABLE {self.name_};')
            self.db_obj.__delattr__(self.name_[1:-1])

    def delete_column(
        self,
        column: 'Column',
        are_you_sure: bool,
        are_you_really_sure: bool,
        for_sure: bool
    ) -> None:
        """Permanently drop a column from the table.

        This method executes an ALTER TABLE DROP COLUMN statement to remove the
        specified column and all its data from the table. To prevent accidental
        deletion, three separate confirmation flags must all be `True`. After
        successful execution, the corresponding attribute is also removed from the
        Table instance.

        Args:
            column (Column): The :class:`Column` object representing the column to
                drop.
            are_you_sure (bool): First confirmation flag.
            are_you_really_sure (bool): Second confirmation flag.
            for_sure (bool): Third confirmation flag.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: Propagates any database errors from the underlying driver
                if the ALTER TABLE statement fails.

        Example:
            >>> employees = Table(driver, "employees")
            >>> # Permanently delete the "temp" column
            >>> employees.delete_column(employees.temp, True, True, True)
            >>> # The attribute is removed; accessing it later raises AttributeError

        Note:
            This operation is irreversible. Use the confirmation flags as a safeguard
            against accidental data loss.
        """
        if are_you_sure and are_you_really_sure and for_sure:
            self._exc(f'ALTER TABLE {self.name_} DROP COLUMN {column.first_name};')
            self.__delattr__(column.first_name[1:-1])

    def add_column(self, column_name: str, data_type: str, nullable: bool = True,
                default: Any = None, auto_increment: bool = False,
                primary_key: bool = False, unique: bool = False) -> None:
        """Add a new column to the table.

        This method executes an ``ALTER TABLE ADD COLUMN`` statement and,
        if ``primary_key`` is `True`, also adds a primary key constraint.
        After the column is created, a corresponding :class:`Column` attribute
        is dynamically added to the :class:`Table` instance, allowing it to be
        referenced in future queries (e.g., in ``update()``, ``insert()``, etc.).

        Args:
            column_name (str): The name of the new column.
            data_type (str): The SQL data type string (e.g., from :class:`DataTypes`).
            nullable (bool, optional): Whether the column can contain NULL values.
                Defaults to `True`.
            default (Any, optional): Default value for the column. If a string is
                provided, it will be quoted; otherwise, it is used as-is. Defaults
                to `None`.
            auto_increment (bool, optional): If `True`, makes the column an identity
                column (``GENERATED BY DEFAULT AS IDENTITY``). Only valid for numeric
                or serial types. Defaults to `False`.
            primary_key (bool, optional): If `True`, adds a primary key constraint
                on this column. Note that a primary key column is implicitly ``NOT NULL``.
                Defaults to `False`.
            unique (bool, optional): If `True`, adds a unique constraint to the column.
                Defaults to `False`.

        Returns:
            None: This method mutates the table structure and does not return a value.

        Raises:
            Exception: Propagates database errors if the ALTER TABLE statement fails,
                or if the column addition violates constraints (e.g., duplicate column
                name, invalid data type, etc.).

        Example:
            Simple addition of a non-nullable text column with a default:

            >>> employees = Table(driver, "employees")
            >>> employees.add_column(
            ...     column_name="department",
            ...     data_type=DataTypes.VARCHAR(50),
            ...     nullable=False,
            ...     default="Engineering"
            ... )
            >>> # Now employees.department is available as a Column object.
            >>> employees.update({employees.department: "Marketing"}, employees.id == 1)

        Example:
            Adding an auto-increment primary key column (SERIAL type) and a unique
            constraint:

            >>> from ormophine.Postgresql import DataTypes
            >>> employees.add_column(
            ...     column_name="employee_id",
            ...     data_type=DataTypes.SERIAL(),
            ...     primary_key=True,
            ...     auto_increment=True,
            ...     nullable=False  # SERIAL is implicitly NOT NULL
            ... )
            >>> # The column "employee_id" is now the primary key and auto-increments.
            >>> employees.add_column(
            ...     column_name="email",
            ...     data_type=DataTypes.VARCHAR(100),
            ...     unique=True
            ... )
        """
        col_def = f'"{column_name}" {data_type}'
        col_def += ' NOT NULL' if not nullable else ''
        col_def += (f" DEFAULT '{default}'" if isinstance(default, str) else f" DEFAULT {default}") if default is not None else ''
        col_def += " GENERATED BY DEFAULT AS IDENTITY" if auto_increment and data_type not in ("SMALLSERIAL", "SERIAL", "BIGSERIAL") else ''
        col_def += " UNIQUE" if unique else ''
        self._exc(f"ALTER TABLE {self.name_} ADD COLUMN {col_def};")
        self._exc(f'ALTER TABLE {self.name_} ADD PRIMARY KEY ("{column_name}");') if primary_key else None
        type_lower = data_type.lower().split("(")[0].strip()
        self.__setattr__(column_name, Column(self, column_name, int if type_lower in ('smallint', 'integer', 'bigint', 'serial', 'smallserial', 'bigserial') else float) if type_lower in ('smallint', 'integer', 'bigint', 'serial', 'smallserial', 'bigserial', 'bit', 'numeric', 'decimal', 'real', 'double precision', 'money') else bool if type_lower == 'boolean' else object if type_lower in ('date', 'time without time zone', 'time with time zone','timestamp without time zone', 'timestamp with time zone','interval') else bool if type_lower == 'boolean' else bytes if type_lower == 'bytea' else str if type_lower in ('character varying', 'character', 'text', 'json', 'jsonb', 'uuid', 'date', 'time without time zone', 'time with time zone', 'timestamp without time zone', 'timestamp with time zone', 'interval') else bytes if type_lower == 'bytea' else bool if type_lower == 'boolean' else str)

    def rename_table(self, new_name: str) -> None:
        """Rename the current table to a new name.

        This method executes an `ALTER TABLE ... RENAME TO` SQL statement to change
        the table name in the database. It also updates the corresponding :class:`Table`
        attribute on the parent :class:`Driver` instance by removing the old attribute
        and creating a new one with the updated name.

        Args:
            new_name (str): The new name for the table.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: Propagates any database errors from the underlying driver
                if the `ALTER TABLE` statement fails.

        Example:
            >>> from ormophine.Postgresql import Driver
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> employees = Table(driver, "employees")
            >>> # Rename the table from "employees" to "staff"
            >>> employees.rename_table("staff")
            >>> # The table is now accessible as driver.staff
            >>> staff = driver.staff
        """
        self._exc(f'ALTER TABLE {self.name_} RENAME TO "{new_name}";')
        self.db_obj.__delattr__(self.name_[1:-1])
        self.db_obj.__setattr__(new_name, Table(obj=self.db_obj, table_name=new_name))
        self.name_ = f'"{new_name}"'

    def rename_column(self, column: 'Column', new_name: str) -> None:
        """Rename an existing column in the table.

        This method executes an ALTER TABLE statement to rename a column in the
        database and updates the :class:`Table` instance's attributes accordingly.
        The old attribute is removed and a new attribute with the new column name
        is added, preserving the column's data type.

        Args:
            column (Column): The column object to rename.
            new_name (str): The new name for the column. Must be a valid PostgreSQL
                identifier.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: Propagates any database errors from the underlying driver,
                such as if the column does not exist or the new name is invalid.

        Example:
            >>> employees = Table(driver, "employees")
            >>> # Rename the 'emp_name' column to 'full_name'
            >>> employees.rename_column(employees.emp_name, "full_name")
            >>> # The attribute is now accessible as employees.full_name
        """
        query = f'ALTER TABLE {self.name_} RENAME COLUMN {column.first_name} TO "{new_name}";'
        self._exc(query)
        self.__delattr__(column.first_name.strip('"'))
        self.__setattr__(new_name, Column(self, new_name, column.datatype))

    def create_index(
        self,
        index_name: str,
        columns: list['Column'],
        unique: bool = False,
        where: 'ColumnsOperation' = None
    ) -> None:
        """Create an index on one or more columns of the table.

        This method constructs and executes a CREATE INDEX statement to improve
        query performance on the specified columns. It supports unique indexes,
        multi-column indexes, and partial indexes with a WHERE condition.

        Args:
            index_name (str): The name of the index to create.
            columns (list[Column]): A list of :class:`Column` objects to include
                in the index.
            unique (bool, optional): If `True`, creates a UNIQUE index to enforce
                uniqueness of the indexed columns. Defaults to `False`.
            where (ColumnsOperation, optional): A :class:`ColumnsOperation`
                condition to create a partial index. Only rows satisfying this
                condition are indexed. Defaults to `None`.

        Returns:
            None: This method executes the index creation and does not return
            a value.

        Raises:
            Exception: Propagates any database errors (e.g., duplicate index name,
                column not found, etc.) from the underlying driver.

        Example:
            Simple index on a single column:

            >>> employees = Table(driver, "employees")
            >>> employees.create_index("idx_employees_name", [employees.name])
            # Creates: CREATE INDEX idx_employees_name ON "employees" ("name");

        Example:
            Unique composite index with a partial condition:

            >>> # Create a unique index on (department, title) for active employees
            >>> employees.create_index(
            ...     "idx_employees_dept_title_active",
            ...     [employees.department, employees.title],
            ...     unique=True,
            ...     where=employees.status == "active"
            ... )
            # Creates: CREATE UNIQUE INDEX idx_employees_dept_title_active
            # ON "employees" ("department", "title")
            # WHERE (("status" = 'active'));
        """
        if where:
            wr = f'WHERE {where._output[0]}'
            for i in where._output[1]:
                wr=wr.replace('%s',i if isinstance(i,str) else str(i),1)
        self._excp(f'CREATE {'UNIQUE ' if unique else ''}INDEX {index_name} ON {self.name_} ({','.join(i.first_name for i in columns)}) {wr if where else ''}',[])

    def delete_index(self, index_name: str) -> None:
        """Drop an existing index from the table.

        This method executes a `DROP INDEX IF EXISTS` statement to remove the
        specified index from the database. Using `IF EXISTS` prevents an error
        if the index does not exist.

        Args:
            index_name (str): The name of the index to delete.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: Propagates any database errors from the underlying driver
                if the DROP INDEX statement fails for reasons other than
                non-existence (e.g., permission issues).

        Example:
            >>> employees = Table(driver, "employees")
            >>> # Create an index on the 'last_name' column
            >>> employees.create_index("idx_last_name", [employees.last_name])
            >>> # Delete the index when no longer needed
            >>> employees.delete_index("idx_last_name")
        """
        self._exc(f'DROP INDEX IF EXISTS "{index_name}";')

    def get_indexes_info(self) -> Any:
        """Retrieve detailed information about all indexes defined on the table.

        This method queries PostgreSQL system catalogs to obtain comprehensive
        metadata for each index associated with the table, including the index name,
        type, definition SQL, uniqueness flag, and primary key status.

        Returns:
            list[dict]: A list of dictionaries, each containing the following keys:
                - idx_name (str): The name of the index.
                - index_type (str): The access method (e.g., 'btree', 'hash').
                - definition (str): The full SQL definition of the index
                (e.g., "CREATE INDEX idx_name ON table (column)").
                - unique (bool): True if the index enforces uniqueness, else False.
                - primary (bool): True if the index is the primary key, else False.

        Raises:
            Exception: Propagates any database errors from the underlying driver
                if the query fails.

        Example:
            >>> employees = Table(driver, "employees")
            >>> indexes = employees.get_indexes_info()
            >>> for idx in indexes:
            ...     print(f"{idx['idx_name']} ({idx['index_type']}): unique={idx['unique']}")
            ...
            employees_pkey (btree): unique=True
            idx_employees_last_name (btree): unique=False
        """
        query = """
            SELECT
                i.relname AS index_name,
                am.amname AS index_type,
                pg_get_indexdef(i.oid) AS index_def,
                indisunique::int AS is_unique,
                indisprimary::int AS is_primary
            FROM pg_index x
            JOIN pg_class c ON c.oid = x.indrelid
            JOIN pg_class i ON i.oid = x.indexrelid
            LEFT JOIN pg_am am ON i.relam = am.oid
            WHERE c.relname = %s
            AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = current_schema())
        """
        return [{'idx_name': r[0],'index_type': r[1],'definition': r[2],'unique': bool(r[3]),'primary': bool(r[4])}for r in self._excfp(query, (self.name_.strip('"'),))]

    def bulk_insert(self, columns: list['Column'], data_list: list) -> None:
        """Insert multiple rows into the table in a single efficient operation.

        This method uses `executemany` to insert many rows at once, which is
        significantly faster than calling :meth:`insert` repeatedly, especially
        for large datasets. The data is passed as a list of rows, where each row
        is a list or tuple of values corresponding to the specified columns.

        Args:
            columns (list[Column]): A list of :class:`Column` objects specifying
                the columns to insert into, in the order that values are provided.
            data_list (list): A list of rows, where each row is a sequence (list
                or tuple) of values to insert. The length and order of values in
                each row must match the `columns` list.

        Returns:
            None: This method executes the insert and does not return a value.

        Raises:
            Exception: Propagates any database errors from the underlying driver,
                including parameter binding errors or constraint violations.

        Example:
            >>> employees = driver.employees
            >>> # Bulk insert multiple employee records
            >>> employees.bulk_insert(
            ...     [employees.name, employees.department, employees.salary],
            ...     [
            ...         ["Alice", "Engineering", 75000],
            ...         ["Bob", "Marketing", 65000],
            ...         ["Charlie", "Sales", 70000],
            ...     ]
            ... )
            >>> # All three rows are inserted in a single executemany call.
        """
        self._excm(f'INSERT INTO {self.name_} ({', '.join(i.first_name for i in columns)}) VALUES ({', '.join('%s' for i in columns)});',data_list)

    def bulk_update(self, update: dict['Column', Any], where: 'ColumnsOperation', data_list: list) -> None:
        """Execute a bulk UPDATE operation with parameterized placeholders.

        This method performs a single UPDATE statement for multiple rows by
        using placeholders (``PLACE_HOLDER``) that are replaced with values
        from each row in ``data_list``. It is designed for efficient batch
        updates where the same update structure applies to many rows, but the
        specific values differ per row.

        The ``update`` dictionary and the ``where`` condition can contain the
        special placeholder ``self.PLACE_HOLDER`` (or ``db.PLACE_HOLDER``) to
        indicate that the actual value should be taken from the corresponding
        position in each row of ``data_list``. The method constructs the final
        SQL by replacing ``%s`` placeholders with ``PLACE_HOLDER``, builds a
        parameterized query, and then executes it using ``executemany`` with
        the ``data_list``.

        The number of ``PLACE_HOLDER`` occurrences across ``update`` values
        and the ``where`` clause must equal the number of items in each row
        of ``data_list``. The order of ``PLACE_HOLDER`` occurrences in the
        final SQL is preserved (values from ``update`` first, in dict order,
        then values from ``where``).

        Args:
            update (dict[Column, Any]): A dictionary mapping :class:`Column`
                objects to new values. Values can be literals, :class:`Column`
                objects (for column-to-column assignment), or
                :class:`ColumnsOperation` objects (which may embed
                ``PLACE_HOLDER``). Use ``PLACE_HOLDER`` for values that should
                come from ``data_list``.
            where (ColumnsOperation): A :class:`ColumnsOperation` representing
                the condition that determines which rows to update. May also
                contain ``PLACE_HOLDER`` to be substituted from ``data_list``.
            data_list (list): A list of rows, where each row is a list/tuple
                of values corresponding to the ``PLACE_HOLDER`` occurrences in
                ``update`` and ``where`` (in order of appearance).

        Returns:
            None: This method executes the bulk update and does not return a
            value.

        Raises:
            Exception: If the number of ``PLACE_HOLDER`` occurrences does not
                match the number of items in each row of ``data_list``. Also
                propagates other database errors.

        Example:
            Simple bulk update using placeholders for column values::

                >>> employees = driver.employees
                >>> employees.bulk_update(
                ...     {employees.salary: employees.salary + employees.PLACE_HOLDER},
                ...     employees.department == employees.PLACE_HOLDER,
                ...     data_list=[
                ...         [5000, "Engineering"],
                ...         [3000, "Marketing"],
                ...         [4000, "Sales"],
                ...     ]
                ... )
                >>> # Generates: UPDATE "employees" SET "salary" = ("salary" + %s)
                >>> #             WHERE "department" = %s;
                >>> # Executes with the given data_list.

            Complex bulk update with multiple placeholders and a compound
            condition::

                >>> employees.bulk_update(
                ...     {
                ...         employees.bonus: employees.salary * employees.PLACE_HOLDER / 100,
                ...         employees.title: employees.title + " (Senior)"
                ...     },
                ...     (employees.title == "Manager") &
                ...     (employees.years > employees.PLACE_HOLDER),
                ...     data_list=[[10, 5], [15, 8], [12, 6]]
                ... )
                >>> # First PLACE_HOLDER (percentage) comes from update,
                >>> # second PLACE_HOLDER (years threshold) comes from where.
                >>> # Each row provides [percentage, years_threshold].

        Note:
            The placeholder string is reserved by the ORM. If your data
            legitimately contains the same literal string, you can change
            the placeholder per table::

                employees.PLACE_HOLDER = "my_own_marker_%s"
        """
        temp_list = []
        [None if isinstance(value , Column) else temp_list.append(value) if not isinstance(value, ColumnsOperation) else temp_list.extend(value._output[1]) for key, value in update.items()]
        query_splited = f'UPDATE {self.name_} SET {', '.join(f'{key.first_name} = {value.first_name}' if isinstance(value, Column) else f'{key.first_name}={str(self.PLACE_HOLDER)}' if not isinstance(value , ColumnsOperation) else f'{key.first_name}={value._output[0].replace('%s', str(self.PLACE_HOLDER))}' for key , value in list(update.items()))} WHERE {where._output[0].replace('%s', str(self.PLACE_HOLDER))};'.split(str(self.PLACE_HOLDER))
        query= query_splited[0]
        for a,i in enumerate(temp_list+where._output[1]):
            query = query +( f'"{i}"' if isinstance(i,str) and not i == str(self.PLACE_HOLDER) else str(i))+ query_splited[a+1] #All "? || '%'" thing are because of Column.contain() method and .startswith() and .endswith() that have "%" in output value
        try:
            self._excm(query.replace(str(self.PLACE_HOLDER), '%s'), data_list)
        except Exception as e:
            if "Incorrect number of bindings" in str(e):
                raise Exception(f'number of `PLACE_HOLDERS` must be equals to number of items in each of `data_list` items.\n if it is so, make sure that there is no "{str(self.PLACE_HOLDER)}" literal string in your query because it is reserved for this orm. you can change it on you own need with `mytable.PLACE_HOLDER = "you own idea"`')
            else:
                raise
        
    def inner_join(self, table: 'Table', condition: 'ColumnsOperation') -> 'JoinQuery':
        """
        Start a JOIN query with an INNER JOIN.

        Returns a :class:`JoinQuery` that supports chaining more joins
        (via ``inner_join``, ``left_join``, ``right_join``) and finally
        executing with ``get_row(...)``.

        Args:
            table (Table): The table to join (right side).
            condition (ColumnsOperation): The ON condition (e.g.
                ``orders.user_id == users.id``).

        Returns:
            JoinQuery: A chainable query builder.
        """
        return JoinQuery(self).inner_join(table, condition)

    def left_join(self, table: 'Table', condition: 'ColumnsOperation') -> 'JoinQuery':
        """
        Start a JOIN query with a LEFT JOIN.
        """
        return JoinQuery(self).left_join(table, condition)

    def right_join(self, table: 'Table', condition: 'ColumnsOperation') -> 'JoinQuery':
        """
        Start a JOIN query with a RIGHT JOIN.
        """
        return JoinQuery(self).right_join(table, condition)

from __future__ import annotations
from typing import Literal

class DataTypes:
    """Collection of PostgreSQL 16 data types as static methods.

    This class serves as a central registry of all commonly used PostgreSQL data
    types, offering a clean, programmatic way to specify column types without
    writing raw SQL strings. Each static method returns the corresponding SQL
    type string, ready to be passed directly to methods such as
    :meth:`TableStructure.add_column` or :meth:`Table.add_column`.

    The provided types cover:

    - **Numeric types:** :meth:`SMALLINT`, :meth:`INTEGER`, :meth:`BIGINT`,
      :meth:`DECIMAL`, :meth:`NUMERIC`, :meth:`REAL`, :meth:`DOUBLE_PRECISION`,
      :meth:`MONEY`, :meth:`BIT`.
    - **Serial (auto‑increment) types:** :meth:`SMALLSERIAL`, :meth:`SERIAL`,
      :meth:`BIGSERIAL`.
    - **Character types:** :meth:`CHAR`, :meth:`VARCHAR`, :meth:`TEXT`.
    - **Binary type:** :meth:`BYTEA`.
    - **Date/time types:** :meth:`DATE`, :meth:`TIME`, :meth:`TIMETZ`,
      :meth:`TIMESTAMP`, :meth:`TIMESTAMPTZ`, :meth:`INTERVAL`.
    - **Boolean type:** :meth:`BOOLEAN`.
    - **JSON types:** :meth:`JSON`, :meth:`JSONB`.
    - **UUID type:** :meth:`UUID`.
    - **Spatial (PostGIS) types:** :meth:`GEOMETRY`, :meth:`GEOGRAPHY`,
      :meth:`POINT`, :meth:`LINESTRING`, :meth:`POLYGON`, :meth:`MULTIPOINT`,
      :meth:`MULTILINESTRING`, :meth:`MULTIPOLYGON`,
      :meth:`GEOMETRYCOLLECTION`.
    - **Array type:** :meth:`ARRAY`, which accepts an element type string and
      appends ``[]``.

    All methods are ``@staticmethod``, so they can be called without
    instantiating the class.

    Example:
        >>> from ormophine.Postgresql import DataTypes, TableStructure
        >>> structure = TableStructure("users")
        >>> structure.add_column("id", DataTypes.SERIAL(), primary_key=True)
        >>> structure.add_column("name", DataTypes.VARCHAR(100), not_null=True)
        >>> structure.add_column("balance", DataTypes.NUMERIC(12, 2))
        >>> structure.add_column("created_at", DataTypes.TIMESTAMPTZ())
        >>> structure.add_column("tags", DataTypes.ARRAY(DataTypes.VARCHAR(30)))
    """

    # ========================
    # Numeric Data Types
    # ========================

    @staticmethod
    def BIT(size: int) -> str:
        """Returns the SQL ``BIT(length)`` type string for fixed‑length bit strings.

        This static method generates a valid PostgreSQL data type definition for
        a bit string column with the exact number of bits specified by ``size``.
        The value must be between 1 and 64 inclusive; otherwise a ``ValueError``
        is raised.

        Args:
            size (int): The number of bits for the column. Must be an integer
                in the range [1, 64].

        Returns:
            str: A SQL type string in the form ``'BIT(size)'``, suitable for use
            in column definitions.

        Raises:
            ValueError: If ``size`` is less than 1 or greater than 64.

        Example:
            >>> DataTypes.BIT(8)
            'BIT(8)'
            >>> # Used in a TableStructure definition:
            >>> structure = TableStructure("flags")
            >>> structure.add_column("permissions", DataTypes.BIT(8))
        """
        if size < 1 or size > 64:
            raise ValueError("Size for BIT must be between 1 and 64.")
        return f"BIT({size})"

    @staticmethod
    def SMALLINT() -> str:
        """Returns the SQL string for the SMALLINT data type.

        The ``SMALLINT`` type represents a signed two‑byte integer with a range
        of -32,768 to 32,767. It is typically used for compact storage of
        small whole numbers.

        Returns:
            str: The literal string ``"SMALLINT"``.

        Example:
            >>> from ormophine.Postgresql import DataTypes
            >>> small_int = DataTypes.SMALLINT()
            >>> small_int
            'SMALLINT'
            >>> # Use it when defining a table structure
            >>> structure = TableStructure("example")
            >>> structure.add_column("count", DataTypes.SMALLINT(), not_null=True)
        """
        return "SMALLINT"

    @staticmethod
    def INTEGER() -> str:
        """Returns the PostgreSQL ``INTEGER`` data type string.

        Use this method when defining a table column to specify a 32‑bit
        signed integer.

        Returns:
            str: The string ``"INTEGER"``.

        Example:
            >>> from ormophine.Postgresql import TableStructure, DataTypes
            >>> structure = TableStructure("employees")
            >>> structure.add_column("age", DataTypes.INTEGER())
        """
        return "INTEGER"

    @staticmethod
    def BIGINT() -> str:
        """Returns the SQL ``BIGINT`` data type string.

        Represents a signed 8‑byte (64‑bit) integer, which is the same as
        ``INTEGER`` in PostgreSQL but with explicit sizing.

        Returns:
            str: The string ``'BIGINT'``, ready to be used in a column
            definition or ``CREATE TABLE`` statement.

        Example:
            >>> DataTypes.BIGINT()
            'BIGINT'
        """
        return "BIGINT"

    @staticmethod
    def DECIMAL(precision: int = 10, scale: int = 0) -> str:
        """Returns the SQL ``DECIMAL(precision, scale)`` type string.

        The ``DECIMAL`` type is used for exact numeric values with a fixed
        number of decimal places. This method generates a standard PostgreSQL
        decimal definition that can be passed directly to
        :meth:`TableStructure.add_column`.

        Args:
            precision (int): Total number of significant digits.
                Defaults to ``10``.
            scale (int): Number of digits after the decimal point.
                Defaults to ``0``.

        Returns:
            str: A string like ``'DECIMAL(10, 2)'`` that can be used as the
            ``datatype`` argument when defining a column.

        Example:
            >>> from ormophine.Postgresql import TableStructure, DataTypes
            >>> structure = TableStructure("products")
            >>> structure.add_column("price", DataTypes.DECIMAL(8, 2))
            >>> # Generates: CREATE TABLE "products" ( "price" DECIMAL(8, 2), ... );
        """
        if precision < 1 or scale < 0 or scale > precision:
            raise ValueError("Precision must be >= 1 and scale must be >= 0 and <= precision.")
        return f"DECIMAL({precision}, {scale})"

    @staticmethod
    def NUMERIC(precision: int = 10, scale: int = 0) -> str:
        """Returns the SQL NUMERIC type string with given precision and scale.

        Generates a ``NUMERIC(precision, scale)`` column definition suitable for
        PostgreSQL. NUMERIC is an arbitrary‑precision decimal type. The *precision*
        is the total count of significant digits, and the *scale* is the number of
        fractional digits. Both must be non‑negative integers, and the scale must
        not exceed the precision.

        Args:
            precision (int): Total number of significant digits (must be ≥ 1).
                Defaults to ``10``.
            scale (int): Number of digits to the right of the decimal point
                (must be ≥ 0 and ≤ precision). Defaults to ``0``.

        Returns:
            str: A string like ``"NUMERIC(10, 0)"`` that can be used directly in
            ``CREATE TABLE`` statements or passed to methods such as
            :meth:`TableStructure.add_column` and :meth:`Table.add_column`.

        Raises:
            ValueError: If ``precision < 1``, ``scale < 0``, or ``scale > precision``.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> dt = DataTypes.NUMERIC(12, 2)
            >>> print(dt)
            NUMERIC(12, 2)
            >>> # Use in a table definition
            >>> structure = TableStructure("payments")
            >>> structure.add_column("amount", DataTypes.NUMERIC(8, 2), not_null=True)
        """
        if precision < 1 or scale < 0 or scale > precision:
            raise ValueError("Precision must be >= 1 and scale must be >= 0 and <= precision.")
        return f"NUMERIC({precision}, {scale})"

    @staticmethod
    def REAL() -> str:
        """Returns the SQL REAL type string.

        Represents a 4‑byte, single‑precision floating‑point number in
        PostgreSQL. It is commonly used for columns that store approximate
        numeric values with less storage overhead than
        :meth:`DOUBLE_PRECISION` or :meth:`NUMERIC`. The precision is about
        6 decimal digits.

        Returns:
            str: The SQL type string ``"REAL"``.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> dt = DataTypes.REAL()
            >>> print(dt)
            REAL
            >>> # Use in table definition
            >>> structure = TableStructure("sensors")
            >>> structure.add_column("temperature", DataTypes.REAL(), not_null=True)
        """
        return "REAL"

    @staticmethod
    def DOUBLE_PRECISION() -> str:
        """Returns the SQL DOUBLE PRECISION type string.

        Generates the ``DOUBLE PRECISION`` column definition suitable for
        PostgreSQL. This is an 8‑byte floating‑point data type (synonym for
        ``FLOAT8``).

        Returns:
            str: The string ``"DOUBLE PRECISION"``, which can be used directly
            in ``CREATE TABLE`` statements or passed to methods such as
            :meth:`TableStructure.add_column` and :meth:`Table.add_column`.

        Raises:
            None

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> dt = DataTypes.DOUBLE_PRECISION()
            >>> print(dt)
            DOUBLE PRECISION
            >>> # Use in a table definition
            >>> structure = TableStructure("measurements")
            >>> structure.add_column("value", DataTypes.DOUBLE_PRECISION(), not_null=True)
        """
        return "DOUBLE PRECISION"

    @staticmethod
    def MONEY() -> str:
        """Returns the SQL MONEY type string for monetary values.

        ``MONEY`` is a fixed‑point numeric data type that stores currency
        amounts with a fractional precision of two decimal places. The output
        format is locale‑sensitive, meaning the currency symbol, grouping, and
        decimal separators depend on the database's ``lc_monetary`` setting.
        Despite its formatting behaviour, the underlying storage uses a 64‑bit
        signed integer representing the amount in cents; the maximum range is
        ±9,223,372,036,854,775,807 cents (approximately ±92.23 trillion in
        the base currency unit). Use this type for applications where monetary
        values do not exceed that range and locale‑specific display is desired.

        Args:
            None

        Returns:
            str: The literal string ``"MONEY"``, which can be used directly in
            ``CREATE TABLE`` statements or passed to methods such as
            :meth:`TableStructure.add_column` and :meth:`Table.add_column`.

        Raises:
            None

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> dt = DataTypes.MONEY()
            >>> print(dt)
            MONEY
            >>> # Use in a table definition
            >>> structure = TableStructure("products")
            >>> structure.add_column("price", DataTypes.MONEY(), not_null=True)
        """
        return "MONEY"

    # ========================
    # Serial (Auto-increment) Types
    # ========================

    @staticmethod
    def SERIAL() -> str:
        """Returns the SQL ``SERIAL`` type string for auto‑incrementing integer columns.

        ``SERIAL`` is a PostgreSQL pseudo‑type that creates an ``INTEGER`` column
        with a sequence‑based default value, automatically generating unique
        identifiers for new rows. This method simply returns the string
        ``"SERIAL"``, which can be used directly in column definitions passed to
        :meth:`TableStructure.add_column` or :meth:`Table.add_column`.

        Returns:
            str: The literal string ``"SERIAL"``.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> dt = DataTypes.SERIAL()
            >>> print(dt)
            SERIAL
            >>> # Use in a table definition
            >>> structure = TableStructure("orders")
            >>> structure.add_column("id", DataTypes.SERIAL(), primary_key=True)
        """
        return "SERIAL"

    @staticmethod
    def SMALLSERIAL() -> str:
        """Returns the SQL SMALLSERIAL type string for an auto‑incrementing small integer.

        ``SMALLSERIAL`` is a PostgreSQL pseudo‑type that creates a 2‑byte integer
        column (``SMALLINT``) that automatically increments with each new row,
        backed by a sequence. It is equivalent to ``SMALLINT`` with an implicit
        ``GENERATED BY DEFAULT AS IDENTITY``. This method returns the string
        ``"SMALLSERIAL"``, which can be used directly in ``CREATE TABLE``
        definitions or passed to :meth:`TableStructure.add_column` and
        :meth:`Table.add_column`.

        Returns:
            str: The string ``"SMALLSERIAL"``.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure, Driver
            >>> dt = DataTypes.SMALLSERIAL()
            >>> print(dt)
            SMALLSERIAL
            >>> # Use in a table structure
            >>> structure = TableStructure("logs")
            >>> structure.add_column("log_id", DataTypes.SMALLSERIAL(), primary_key=True)
        """
        return "SMALLSERIAL"

    @staticmethod
    def BIGSERIAL() -> str:
        """Returns the SQL BIGSERIAL type string for auto‑incrementing 64‑bit integers.

        ``BIGSERIAL`` is a PostgreSQL pseudo‑type that creates a ``BIGINT`` column
        with an implicit sequence and default value. It automatically generates
        unique values when inserting rows without specifying the column. This method
        simply returns the literal ``'BIGSERIAL'``, which can be used directly in
        ``CREATE TABLE`` definitions.

        Returns:
            str: The string ``"BIGSERIAL"``, ready for use in a column definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("logs")
            >>> structure.add_column("id", DataTypes.BIGSERIAL(), primary_key=True)
        """
        return "BIGSERIAL"

    # ========================
    # String Data Types
    # ========================

    @staticmethod
    def CHAR(length: int = 1) -> str:
        """Returns the SQL CHAR type string with a fixed length.

        Generates a ``CHAR(length)`` column definition for fixed-length character
        strings in PostgreSQL. The *length* specifies the exact number of characters
        the column can store; values shorter than this are right-padded with spaces.
        This method is intended to be used with :meth:`TableStructure.add_column` or
        :meth:`Table.add_column` when defining a table schema.

        Args:
            length (int): The fixed number of characters for the CHAR column.
                Must be at least ``1``. Defaults to ``1``.

        Returns:
            str: A string like ``"CHAR(10)"`` that can be passed directly to a
            column definition method.

        Raises:
            ValueError: If ``length`` is less than ``1``.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("countries")
            >>> structure.add_column("code", DataTypes.CHAR(2), not_null=True)
        """
        if length < 1:
            raise ValueError("Length for CHAR must be at least 1.")
        return f"CHAR({length})"

    @staticmethod
    def VARCHAR(length: int = 255) -> str:
        """Returns the SQL VARCHAR type string with the specified maximum length.

        Generates a ``VARCHAR(length)`` column definition suitable for PostgreSQL.
        VARCHAR is a variable‑length character string with a user‑defined maximum
        size. The *length* must be a positive integer.

        Args:
            length (int): Maximum number of characters the column can store.
                Must be ≥ 1. Defaults to ``255``.

        Returns:
            str: A string like ``"VARCHAR(100)"`` that can be used directly in
            ``CREATE TABLE`` statements or passed to methods such as
            :meth:`TableStructure.add_column` and :meth:`Table.add_column`.

        Raises:
            ValueError: If ``length`` is less than 1.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("employees")
            >>> structure.add_column("name", DataTypes.VARCHAR(100), not_null=True)
            >>> structure.add_column("bio", DataTypes.VARCHAR())  # defaults to 255
        """
        if length < 1:
            raise ValueError("Length for VARCHAR must be at least 1.")
        return f"VARCHAR({length})"

    @staticmethod
    def TEXT() -> str:
        """Returns the SQL TEXT type string for variable‑length character data.

        In PostgreSQL, ``TEXT`` represents a character string of unlimited length.
        This method returns the literal ``'TEXT'`` so it can be used directly in
        column definitions when creating tables via :class:`TableStructure` or
        :meth:`Table.add_column`.

        Returns:
            str: The string ``"TEXT"``.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("notes")
            >>> structure.add_column("content", DataTypes.TEXT())
        """
        return "TEXT"

    # ========================
    # Binary Data Types
    # ========================

    @staticmethod
    def BYTEA() -> str:
        """Returns the SQL BYTEA type string for storing binary data.

        ``BYTEA`` is the PostgreSQL data type for variable‑length binary strings
        (``bytea``). It can hold raw bytes, similar to ``BLOB`` in other databases.
        This method returns the literal ``'BYTEA'``, ready to be used in column
        definitions.

        Returns:
            str: The string ``"BYTEA"``.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("files")
            >>> structure.add_column("content", DataTypes.BYTEA(), not_null=True)
        """
        return "BYTEA"

    # ========================
    # Date and Time Data Types
    # ========================

    @staticmethod
    def DATE() -> str:
        """Returns the SQL DATE type string for storing dates.

        The ``DATE`` type stores a calendar date (year, month, day) without any
        time zone or time-of-day component, following the PostgreSQL ``date``
        data type. This method simply returns the literal ``'DATE'``, which can be
        used directly in ``CREATE TABLE`` column definitions or passed to
        :meth:`TableStructure.add_column` and :meth:`Table.add_column`.

        Returns:
            str: The string ``"DATE"``, ready for use in a column definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("events")
            >>> structure.add_column("event_date", DataTypes.DATE(), not_null=True)
            >>> print(DataTypes.DATE())
            DATE
        """
        return "DATE"

    @staticmethod
    def TIME(precision: int = None) -> str:
        """Returns the SQL TIME type string, optionally with fractional seconds precision.

        ``TIME`` represents a time of day without a date, storing hours, minutes,
        and seconds. If *precision* is given, it specifies the number of fractional
        digits retained for the seconds part (0–6). Without arguments, the plain
        ``TIME`` string is returned, meaning the default precision of the database
        (typically 6) will be used.

        Args:
            precision (int, optional): Number of fractional digits for seconds
                (0 to 6). If ``None`` (default), no precision is included.

        Returns:
            str: Either ``"TIME"`` or ``"TIME(precision)"``, ready for use in a
            column definition, such as in :meth:`TableStructure.add_column` or
            :meth:`Table.add_column`.

        Raises:
            ValueError: If *precision* is outside the valid range (0–6). (Note:
                The current implementation does not validate the range; this may
                be added in future versions.)

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> # Plain time without fractional seconds
            >>> dt = DataTypes.TIME()
            >>> print(dt)
            TIME
            >>> # Time with milliseconds precision
            >>> dt_ms = DataTypes.TIME(3)
            >>> print(dt_ms)
            TIME(3)
            >>> # Use in a table definition
            >>> structure = TableStructure("schedule")
            >>> structure.add_column("start_time", DataTypes.TIME(0), not_null=True)
        """
        if precision is not None:
            return f"TIME({precision})"
        return "TIME"

    @staticmethod
    def TIMETZ(precision: int = None) -> str:
        """Returns the SQL TIMETZ type string, optionally with fractional seconds precision.

        ``TIMETZ`` is the time‑with‑time‑zone data type, storing a time of day
        together with a time zone offset. It is analogous to :meth:`TIME` but
        includes time zone awareness. If *precision* is provided, it specifies the
        number of fractional digits retained for the seconds part (0–6). Without
        arguments, the plain ``TIMETZ`` string is returned, using the database
        default precision (typically 6).

        Args:
            precision (int, optional): Number of fractional digits for seconds
                (0 to 6). If ``None`` (default), no precision is included in the
                type string.

        Returns:
            str: Either ``"TIMETZ"`` or ``"TIMETZ(precision)"``, ready for use in
            column definitions (e.g., :meth:`TableStructure.add_column`).

        Raises:
            ValueError: (Not currently enforced) If *precision* is outside the
                valid range (0–6). Future versions may add validation.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> # Plain time with time zone
            >>> dt = DataTypes.TIMETZ()
            >>> print(dt)
            TIMETZ
            >>> # With milliseconds precision
            >>> dt_ms = DataTypes.TIMETZ(3)
            >>> print(dt_ms)
            TIMETZ(3)
            >>> # Use in a table definition
            >>> structure = TableStructure("events")
            >>> structure.add_column("start_time", DataTypes.TIMETZ(0), not_null=True)
        """
        if precision is not None:
            return f"TIMETZ({precision})"
        return "TIMETZ"

    @staticmethod
    def TIMESTAMP(precision: int = None) -> str:
        """Returns the SQL TIMESTAMP type string, optionally with fractional seconds precision.

        ``TIMESTAMP`` stores a date and time (without time zone). If *precision*
        is provided, it specifies the number of fractional digits retained for the
        seconds part (0–6). Without arguments, the plain ``TIMESTAMP`` string is
        returned, using the database default precision (typically 6).

        Args:
            precision (int, optional): Number of fractional digits for seconds
                (0 to 6). If ``None`` (default), no precision is included.

        Returns:
            str: Either ``"TIMESTAMP"`` or ``"TIMESTAMP(precision)"``, ready for
            use in a column definition (e.g., in :meth:`TableStructure.add_column`
            or :meth:`Table.add_column`).

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> # Plain timestamp
            >>> dt = DataTypes.TIMESTAMP()
            >>> print(dt)
            TIMESTAMP
            >>> # Timestamp with millisecond precision
            >>> dt_ms = DataTypes.TIMESTAMP(3)
            >>> print(dt_ms)
            TIMESTAMP(3)
            >>> # Use in a table definition
            >>> structure = TableStructure("events")
            >>> structure.add_column("created_at", DataTypes.TIMESTAMP(0), not_null=True)
        """
        if precision is not None:
            return f"TIMESTAMP({precision})"
        return "TIMESTAMP"

    @staticmethod
    def TIMESTAMPTZ(precision: int = None) -> str:
        """Returns the SQL TIMESTAMPTZ type string, optionally with fractional seconds precision.

        ``TIMESTAMPTZ`` represents a date and time with time zone awareness. The
        optional *precision* argument specifies the number of fractional digits
        retained for the seconds part (0–6). If omitted, the default database
        precision (typically 6) is used.

        Args:
            precision (int, optional): Number of fractional digits for seconds
                (0 to 6). If ``None`` (default), no precision is included.

        Returns:
            str: Either ``"TIMESTAMPTZ"`` or ``"TIMESTAMPTZ(precision)"``, ready
            for use in a column definition, such as in
            :meth:`TableStructure.add_column` or :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> # Timestamp with time zone, default precision
            >>> dt = DataTypes.TIMESTAMPTZ()
            >>> print(dt)
            TIMESTAMPTZ
            >>> # With millisecond precision
            >>> dt_ms = DataTypes.TIMESTAMPTZ(3)
            >>> print(dt_ms)
            TIMESTAMPTZ(3)
            >>> # Use in a table definition
            >>> structure = TableStructure("events")
            >>> structure.add_column("created_at", DataTypes.TIMESTAMPTZ(3), not_null=True)
        """
        if precision is not None:
            return f"TIMESTAMPTZ({precision})"
        return "TIMESTAMPTZ"

    @staticmethod
    def INTERVAL() -> str:
        """Returns the SQL INTERVAL type string for storing time spans.

        ``INTERVAL`` represents a duration of time (e.g., days, hours, minutes,
        seconds). It is a native PostgreSQL type that can store a combination of
        different time units. This method simply returns the literal
        ``'INTERVAL'``, which can be used directly in ``CREATE TABLE`` definitions.

        Returns:
            str: The string ``"INTERVAL"``, ready for use in a column definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("events")
            >>> structure.add_column("duration", DataTypes.INTERVAL(), not_null=True)
        """
        return "INTERVAL"

    # ========================
    # Boolean Type
    # ========================

    @staticmethod
    def BOOLEAN() -> str:
        """Returns the SQL BOOLEAN type string for true/false values.

        ``BOOLEAN`` represents a logical truth value, storing ``TRUE``,
        ``FALSE``, or ``NULL``. In PostgreSQL, it is equivalent to the
        ``bool`` type. This method simply returns the literal ``'BOOLEAN'``,
        which can be used directly in ``CREATE TABLE`` definitions or passed
        to methods such as :meth:`TableStructure.add_column` and
        :meth:`Table.add_column`.

        Returns:
            str: The string ``"BOOLEAN"``, ready for use in a column
            definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("users")
            >>> structure.add_column("is_active", DataTypes.BOOLEAN(),
            ...                      default=True, not_null=True)
        """
        return "BOOLEAN"

    # ========================
    # JSON Types
    # ========================

    @staticmethod
    def JSON() -> str:
        """Returns the SQL JSON type string for storing JSON data.

        In PostgreSQL, ``JSON`` is a data type that stores JSON-formatted text
        without enforcing the stricter binary format of ``JSONB``. It preserves
        white space, key order, and duplicate keys exactly as inserted. This
        method simply returns the literal ``'JSON'``, which can be used in
        ``CREATE TABLE`` column definitions or with methods like
        :meth:`TableStructure.add_column` and :meth:`Table.add_column`.

        Returns:
            str: The string ``"JSON"``, ready for use in a column definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("settings")
            >>> structure.add_column("config", DataTypes.JSON(), not_null=True)
        """
        return "JSON"

    @staticmethod
    def JSONB() -> str:
        """Returns the SQL JSONB type string for storing JSON data in a binary format.

        In PostgreSQL, ``JSONB`` is a data type that stores JSON data in a
        decomposed binary format, which allows efficient indexing, faster
        processing, and more advanced querying (e.g., containment, existence, and
        path matching operators). Unlike ``JSON``, it does not preserve white
        space, key order, or duplicate keys. This method simply returns the
        literal ``'JSONB'``, which can be used in ``CREATE TABLE`` column
        definitions or with methods like :meth:`TableStructure.add_column` and
        :meth:`Table.add_column`.

        Returns:
            str: The string ``"JSONB"``, ready for use in a column definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("products")
            >>> structure.add_column("attributes", DataTypes.JSONB(), not_null=True)
        """
        return "JSONB"

    # ========================
    # UUID Type
    # ========================

    @staticmethod
    def UUID() -> str:
        """Returns the SQL UUID type string for storing universally unique identifiers.

        ``UUID`` is a PostgreSQL data type that stores 128‑bit quantities
        generated by algorithms that ensure uniqueness across space and time.
        This method simply returns the literal ``'UUID'``, which can be used
        directly in ``CREATE TABLE`` column definitions or with methods like
        :meth:`TableStructure.add_column` and :meth:`Table.add_column`.

        Returns:
            str: The string ``"UUID"``, ready for use in a column definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("devices")
            >>> structure.add_column("device_id", DataTypes.UUID(), not_null=True)
        """
        return "UUID"

    # ========================
    # Spatial Data Types (PostGIS)
    # ========================

    @staticmethod
    def GEOMETRY() -> str:
        """Returns the SQL GEOMETRY type string for spatial data (PostGIS).

        ``GEOMETRY`` is a spatial data type provided by the PostGIS extension
        for PostgreSQL. It stores geometric shapes such as points, lines, and
        polygons in a planar coordinate system. To use this type, the PostGIS
        extension must be installed and enabled in the database (``CREATE
        EXTENSION postgis;``). This method simply returns the literal
        ``'GEOMETRY'``, which can be used in ``CREATE TABLE`` column definitions
        or with methods like :meth:`TableStructure.add_column` and
        :meth:`Table.add_column`.

        Returns:
            str: The string ``"GEOMETRY"``, ready for use in a column definition.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("landmarks")
            >>> structure.add_column("location", DataTypes.GEOMETRY())
            >>> # After creating the table, spatial data can be inserted with
            >>> # PostGIS functions like ST_MakePoint, ST_GeomFromText, etc.
        """
        return "GEOMETRY"

    @staticmethod
    def GEOGRAPHY() -> str:
        """Returns the SQL GEOGRAPHY type string for geodetic (round‑earth) data.

        ``GEOGRAPHY`` is a PostGIS spatial type that stores coordinates on a
        spheroidal model of the Earth, enabling accurate distance and area
        calculations. Unlike ``GEOMETRY``, which assumes a flat Cartesian plane,
        ``GEOGRAPHY`` accounts for the Earth's curvature. This method returns
        ``'GEOGRAPHY'``, which can be used directly in ``CREATE TABLE`` column
        definitions.

        Returns:
            str: The string ``"GEOGRAPHY"``, suitable for use with
            :meth:`TableStructure.add_column` or :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("cities")
            >>> structure.add_column("location", DataTypes.GEOGRAPHY())
        """
        return "GEOGRAPHY"

    @staticmethod
    def POINT() -> str:
        """Returns the SQL POINT type string for a PostGIS geometry column.

        ``POINT`` is a spatial data type representing a single location on the
        earth's surface, typically stored as a pair of coordinates (longitude,
        latitude). This method returns the string ``'POINT'`` which can be used
        in column definitions for tables that have the PostGIS extension enabled.

        Returns:
            str: The literal ``"POINT"``, suitable for a column definition in a
            ``CREATE TABLE`` statement, e.g. via :meth:`TableStructure.add_column`.

        Note:
            Using this data type requires the PostGIS extension to be installed in
            the PostgreSQL database. If PostGIS is not available, creating a column
            with this type will fail.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("locations")
            >>> structure.add_column("coordinates", DataTypes.POINT(), not_null=True)
        """
        return "POINT"

    @staticmethod
    def LINESTRING() -> str:
        """Returns the SQL LINESTRING type string for PostGIS spatial data.

        ``LINESTRING`` is a PostGIS geometry type representing a sequence of
        points forming a continuous line. This method returns the literal
        ``'LINESTRING'``, which can be used directly in ``CREATE TABLE``
        column definitions when PostGIS is enabled.

        Returns:
            str: The string ``"LINESTRING"``, ready for use in a column
            definition, such as in :meth:`TableStructure.add_column` or
            :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("routes")
            >>> structure.add_column("path", DataTypes.LINESTRING())
        """
        return "LINESTRING"

    @staticmethod
    def POLYGON() -> str:
        """Returns the SQL POLYGON type string for PostGIS spatial data.

        ``POLYGON`` is a PostGIS geometry type representing a closed plane figure
        bounded by a sequence of line segments. This method returns the literal
        ``'POLYGON'``, which can be used directly in ``CREATE TABLE`` column
        definitions when PostGIS is enabled.

        Returns:
            str: The string ``"POLYGON"``, ready for use in a column definition,
            such as in :meth:`TableStructure.add_column` or
            :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("zones")
            >>> structure.add_column("boundary", DataTypes.POLYGON())
        """
        return "POLYGON"

    @staticmethod
    def MULTIPOINT() -> str:
        """Returns the SQL MULTIPOINT type string for PostGIS spatial data.

        ``MULTIPOINT`` is a PostGIS geometry type representing a collection of
        points. This method returns the literal ``'MULTIPOINT'``, which can be
        used directly in ``CREATE TABLE`` column definitions when PostGIS is
        enabled.

        Returns:
            str: The string ``"MULTIPOINT"``, ready for use in a column
            definition, such as in :meth:`TableStructure.add_column` or
            :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("survey_sites")
            >>> structure.add_column("locations", DataTypes.MULTIPOINT())
        """
        return "MULTIPOINT"

    @staticmethod
    def MULTILINESTRING() -> str:
        """Returns the SQL MULTILINESTRING type string for PostGIS spatial data.

        ``MULTILINESTRING`` is a PostGIS geometry type representing a collection
        of :class:`LINESTRING` objects. This method returns the literal
        ``'MULTILINESTRING'``, which can be used directly in ``CREATE TABLE``
        column definitions when the PostGIS extension is enabled.

        Returns:
            str: The string ``"MULTILINESTRING"``, ready for use in a column
            definition, such as in :meth:`TableStructure.add_column` or
            :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("trails")
            >>> structure.add_column("paths", DataTypes.MULTILINESTRING())
        """
        return "MULTILINESTRING"

    @staticmethod
    def MULTIPOLYGON() -> str:
        """Returns the SQL MULTIPOLYGON type string for PostGIS spatial data.

        ``MULTIPOLYGON`` is a PostGIS geometry type representing a collection of
        non‑overlapping polygons. This method simply returns the literal
        ``'MULTIPOLYGON'``, which can be used directly in ``CREATE TABLE``
        column definitions when PostGIS is enabled.

        Returns:
            str: The string ``"MULTIPOLYGON"``, ready for use in a column
            definition, such as in :meth:`TableStructure.add_column` or
            :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("regions")
            >>> structure.add_column("area", DataTypes.MULTIPOLYGON())
        """
        return "MULTIPOLYGON"

    @staticmethod
    def GEOMETRYCOLLECTION() -> str:
        """Returns the SQL GEOMETRYCOLLECTION type string for PostGIS spatial data.

        ``GEOMETRYCOLLECTION`` is a PostGIS geometry type that can hold a
        collection of zero or more geometry values of any type (e.g., points,
        lines, polygons) in a single column. This method returns the literal
        ``'GEOMETRYCOLLECTION'``, which can be used directly in ``CREATE TABLE``
        column definitions when PostGIS is enabled.

        Returns:
            str: The string ``"GEOMETRYCOLLECTION"``, ready for use in a column
            definition, such as in :meth:`TableStructure.add_column` or
            :meth:`Table.add_column`.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> structure = TableStructure("mixed_shapes")
            >>> structure.add_column("shapes", DataTypes.GEOMETRYCOLLECTION())
        """
        return "GEOMETRYCOLLECTION"

    @staticmethod
    def ARRAY(element_type: str) -> str:
        """Returns the SQL array type string for the given element type.

        In PostgreSQL, an array column is declared by appending ``[]`` to the base
        data type. This method accepts the element type string (e.g., ``'INTEGER'``,
        ``'VARCHAR(255)'``) and returns the corresponding array type string (e.g.,
        ``'INTEGER[]'``). The returned string can be used directly in ``CREATE TABLE``
        column definitions, such as when calling :meth:`TableStructure.add_column`
        or :meth:`Table.add_column`.

        Args:
            element_type (str): The base data type of the array elements, typically
                obtained from another :class:`DataTypes` method (e.g.,
                ``DataTypes.INTEGER()``, ``DataTypes.VARCHAR(100)``).

        Returns:
            str: The array type string, formed by appending ``[]`` to the
            element type. For example, ``"INTEGER[]"`` or ``"VARCHAR(255)[]"``.

        Raises:
            None: No validation is performed on the element type; any string
            concatenation will be accepted. It is the caller's responsibility
            to provide a valid PostgreSQL data type.

        Example:
            >>> from ormophine.Postgresql import DataTypes, TableStructure
            >>> # Declare a column that holds an array of integers
            >>> structure = TableStructure("survey")
            >>> structure.add_column("scores", DataTypes.ARRAY(DataTypes.INTEGER()))
            >>> # Declare a column that holds an array of variable-length strings
            >>> structure.add_column("tags", DataTypes.ARRAY(DataTypes.VARCHAR(50)))
        """
        return f"{element_type}[]"
    

class TableStructure:
    """A builder class for programmatically defining PostgreSQL table structures.

    This class provides a fluent interface for constructing table schemas by
    adding columns with data types, constraints (primary key, unique, not null,
    default values, auto-increment), and foreign key relationships. It validates
    the schema consistency (e.g., only one auto-increment column, serial types 
    enforce not null and auto-increment) and generates the final SQL CREATE TABLE
    statement.

    Attributes:
        table_query (str): Accumulated SQL fragment for column definitions.
        primary_keys (list): List of column names that are part of the primary key.
        items (dict): Internal store mapping column names to their properties.
        name (str): The quoted table name.
        foreigns (list): List of SQL foreign key constraint clauses.

    Example:
        >>> from ormophine.Postgresql import TableStructure, DataTypes, Driver
        >>> # Assume driver and existing table objects are available
        >>> departments = TableStructure("departments")
        >>> departments.add_column("id", DataTypes.SERIAL(), primary_key=True)
        >>> departments.add_column("name", DataTypes.VARCHAR(100), not_null=True, unique=True)
        >>>
        >>> employees = TableStructure("employees")
        >>> employees.add_column("id", DataTypes.SERIAL(), primary_key=True)
        >>> employees.add_column("name", DataTypes.VARCHAR(100), not_null=True)
        >>> employees.add_column("dept_id", DataTypes.INTEGER())
        >>> employees.foreign_key("dept_id", departments, departments.id,
        ...                       on_delete="CASCADE")
        >>>
        >>> # Generate and execute the CREATE TABLE statement
        >>> driver.create_table(employees)
    """
    ON_ACTION = Literal['CASCADE', 'SET NULL', 'SET DEFAULT', 'RESTRICT', 'NO ACTION']

    def __init__(self, table_name: str):
        """Initialises a new table structure definition.

        Prepares an empty table structure with the given name. The name is
        automatically wrapped in double quotes to support case‑sensitive and
        special‑character table names in PostgreSQL. After initialisation,
        columns can be added with :meth:`add_column` and foreign keys with
        :meth:`foreign_key` before the structure is passed to
        :meth:`Driver.create_table`.

        Args:
            table_name (str): The name of the table to be created. It will be
                quoted internally, e.g. ``"my_table"``.

        Example:
            >>> structure = TableStructure("employees")
            >>> structure.add_column("id", DataTypes.SERIAL(), primary_key=True)
            >>> structure.add_column("name", DataTypes.VARCHAR(100))
            >>> print(structure.get_structure())
            CREATE TABLE "employees" ("id" SERIAL,... , PRIMARY KEY("id"));
        """
        self.table_query = ''
        self.primary_keys = []
        self.items = {}
        self.name = f'"{table_name}"'
        self.foreigns = []

    def _validate_column(self, column_name, datatype, default_value, unique, not_null, primary_key, auto_increment):
        """Validates the parameters for a new column before adding it to the table structure.

        This internal method enforces a set of rules to ensure that the column
        definition is consistent and compatible with PostgreSQL requirements.
        It checks data type validity, primary key/unique/null constraints,
        duplicate column names, auto‑increment restrictions, serial type
        semantics, and default value types.

        Args:
            column_name (str): The name of the column (already quoted).
            datatype (str): The SQL data type string returned by a
                :class:`DataTypes` method.
            default_value (Any or None): The default value for the column,
                if any.
            unique (bool or None): Whether the column should have a UNIQUE
                constraint.
            not_null (bool or None): Whether the column should be NOT NULL.
            primary_key (bool or None): Whether the column is part of the
                primary key.
            auto_increment (bool): Whether the column is an auto‑increment
                identity column.

        Raises:
            TypeError: If ``datatype`` is not a string.
            Exception: If any of the following invalid configurations are
                detected:
                - A primary key column is also marked UNIQUE.
                - A column with the same name already exists in the table.
                - The default value is a ``bytes`` object.
                - More than one auto‑increment column is defined.
                - An auto‑increment column is not a numeric type.
                - An auto‑increment column is not PRIMARY KEY or UNIQUE.
                - An auto‑increment column has an explicit DEFAULT value.
                - A serial type (SMALLSERIAL, SERIAL, BIGSERIAL) is not
                marked NOT NULL or does not have ``auto_increment=True``.

        Returns:
            None: The method only performs validation; it returns ``None``
            if all checks pass.
        """
        if not isinstance(datatype, str):
            raise TypeError("datatype must be a string returned by DataTypes.")

        if primary_key:
            if unique:
                raise Exception("PRIMARY KEY columns cannot be UNIQUE, as they are inherently unique.")

        if column_name in self.items:
            raise Exception(f"Column {column_name} already exists.")

        if isinstance(default_value, bytes):
            raise Exception("Bytes objects cannot be used as default values.")

        for values in self.items.values():
            if values[5] and auto_increment:
                raise Exception("Only one auto-increment column is allowed.")

        numeric_types = ("SMALLINT","INTEGER","BIGINT","DECIMAL","NUMERIC","REAL","DOUBLE PRECISION","SMALLSERIAL","SERIAL","BIGSERIAL")

        if auto_increment:
            if datatype.split("(")[0].strip() not in numeric_types:
                raise Exception("Auto-increment is only allowed on numeric or serial types.")
            if not (primary_key or unique):
                raise Exception("Auto-increment column must be PRIMARY KEY or UNIQUE.")
            if default_value is not None:
                raise Exception("Auto-increment columns cannot have DEFAULT values.")

        if datatype in ("SMALLSERIAL", "SERIAL", "BIGSERIAL"):
            if not not_null:
                raise Exception("Serial types are inherently NOT NULL, so not_null must be True.")
            if not auto_increment:
                raise Exception("Serial types are inherently auto-increment, so auto_increment must be True.")

    def add_column(self, column_name: str, datatype: DataTypes,default_value=None, unique: bool = None,not_null: bool = None,primary_key: bool = None,auto_increment: bool = False):
        """Adds a column definition to the table structure.

        Appends a column with the given name and data type to the internal
        ``CREATE TABLE`` query. The ``datatype`` argument must be a string
        returned by one of the :class:`DataTypes` static methods (e.g.,
        ``DataTypes.INTEGER()``, ``DataTypes.VARCHAR(100)``). Additional
        constraints such as ``NOT NULL``, ``UNIQUE``, ``PRIMARY KEY``, and
        ``auto_increment`` (``GENERATED BY DEFAULT AS IDENTITY``) are added as
        requested. If the column is a serial type (``SMALLSERIAL``, ``SERIAL``,
        ``BIGSERIAL``), ``primary_key``, ``not_null``, and ``auto_increment``
        are automatically set to ``True`` unless explicitly overridden.

        The method returns ``self``, enabling fluent chaining of multiple
        ``add_column`` calls.

        Args:
            column_name (str): The name of the column (will be double‑quoted).
            datatype (str): A valid PostgreSQL data type string from
                :class:`DataTypes` (e.g., ``DataTypes.INTEGER()``).
            default_value (Any, optional): The default value for the column.
                Strings are automatically quoted in the SQL. Defaults to
                ``None``.
            unique (bool, optional): If ``True``, adds a ``UNIQUE`` constraint.
                Defaults to ``None`` (omitted).
            not_null (bool, optional): If ``True``, adds a ``NOT NULL``
                constraint. Defaults to ``None`` (omitted).
            primary_key (bool, optional): If ``True``, makes the column a
                primary key. Implies ``NOT NULL``. Defaults to ``None``.
            auto_increment (bool): If ``True``, adds ``GENERATED BY DEFAULT
                AS IDENTITY`` for integer types. Cannot be used with
                ``default_value``. Defaults to ``False``.

        Returns:
            :class:`TableStructure`: The same instance (``self``), allowing
            method chaining.

        Raises:
            TypeError: If ``datatype`` is not a string.
            Exception: If validation fails – for example:
                - Duplicate column name.
                - ``PRIMARY KEY`` set but ``not_null`` is ``False`` (or
                ``UNIQUE`` also set).
                - ``auto_increment`` used on a non‑numeric type, or without
                ``primary_key``/``unique``, or with a ``default_value``.
                - More than one ``auto_increment`` column is added.

        Example:
            >>> from ormophine.Postgresql import TableStructure, DataTypes
            >>> structure = TableStructure("employees")
            >>> (structure
            ...  .add_column("id", DataTypes.SERIAL(), primary_key=True)
            ...  .add_column("name", DataTypes.VARCHAR(100), not_null=True)
            ...  .add_column("salary", DataTypes.NUMERIC(10, 2),
            ...              default_value=0.0))
            >>> print(structure.get_structure())
            CREATE TABLE "employees" ("id" SERIAL NOT NULL, "name" VARCHAR(100) NOT NULL, "salary" NUMERIC(10, 2) DEFAULT 0.0);
        """
        column_name = f'"{column_name.strip()}"'
        primary_key, not_null, auto_increment = (True, True, True) if datatype in ("SMALLSERIAL", "SERIAL", "BIGSERIAL") else (primary_key, not_null, auto_increment)
        self._validate_column(column_name,datatype,default_value,unique,not_null,primary_key,auto_increment)
        if type(default_value) == bytes:
            raise Exception('Cant set bytes object as default value')
        self.primary_keys.append(column_name) if primary_key else None
        self.items[column_name] = [datatype, default_value, unique, not_null, primary_key, auto_increment]
        auto_part = " GENERATED BY DEFAULT AS IDENTITY" if auto_increment and datatype not in ("SMALLSERIAL", "SERIAL", "BIGSERIAL") else ""
        self.table_query += f' {column_name.strip()} {datatype}{auto_part}{" UNIQUE" if unique else ""}{" NOT NULL" if not_null else ""}{f" DEFAULT {('TRUE' if default_value else 'FALSE') if isinstance(default_value,bool) else f"'{default_value}'" if type(default_value) == str else str(default_value)}" if default_value is not None else ""},'
        return self

    def get_columns(self):
        """Retrieve a list of column definitions for the table structure.

        This method iterates over the internally stored column metadata and
        returns a list of dictionaries, each containing the properties of a
        column as defined by previous calls to :meth:`add_column`.

        Returns:
            list[dict]: A list of dictionaries, each with the following keys:
                - ``name`` (str): The column name (including surrounding quotes).
                - ``datatype`` (str): The SQL data type string.
                - ``default_value`` (Any): The default value, or ``None``.
                - ``unique`` (bool): Whether the column is marked UNIQUE.
                - ``not_null`` (bool): Whether the column is NOT NULL.
                - ``primari_key`` (bool): Whether the column is a PRIMARY KEY
                (note the typo in the key name, preserved for compatibility).

        Example:
            >>> table = TableStructure("employees")
            >>> table.add_column("id", DataTypes.INTEGER(), primary_key=True, not_null=True)
            >>> table.add_column("name", DataTypes.VARCHAR(50))
            >>> columns = table.get_columns()
            >>> for col in columns:
            ...     print(f"{col['name']} ({col['datatype']}) PK: {col['primari_key']}")
            "id" (INTEGER) PK: True
            "name" (VARCHAR(50)) PK: False
        """
        items_list = []
        for item in self.items:
            items_dict = {}
            values = self.items[item]
            items_dict['name'] = item
            items_dict['datatype'] = values[0]
            items_dict['default_value'] = values[1]
            items_dict['unique'] = True if values[2] else False
            items_dict['not_null'] = True if values[3] else False
            items_dict['primari_key'] = True if values[4] else False
            items_list.append(items_dict)
        return items_list

    def foreign_key(self, column: str, refrences_table: 'Table',refrences_column: 'Column', on_delete: ON_ACTION = None, on_update: ON_ACTION = None):
        """Add a foreign key constraint to the table definition.

        This method appends a FOREIGN KEY clause to the table's SQL definition.
        It references a column in another table, with optional ON DELETE and
        ON UPDATE cascade actions. The constraint is included in the final
        `CREATE TABLE` statement generated by :meth:`get_structure`.

        Args:
            column (str): The name of the column in the current table that will
                act as the foreign key.
            refrences_table (Table): The target table being referenced.
            refrences_column (Column): The target column in the referenced table.
            on_delete (ON_ACTION, optional): Action to take when the referenced
                row is deleted. Must be one of 'CASCADE', 'SET NULL',
                'SET DEFAULT', 'RESTRICT', or 'NO ACTION'.
            on_update (ON_ACTION, optional): Action to take when the referenced
                row is updated. Must be one of the same allowed values.

        Returns:
            TableStructure: The current instance, enabling method chaining.

        Example:
            >>> from ormophine.Postgresql import TableStructure, DataTypes, Table, Column
            >>> orders = TableStructure("orders")
            >>> customers = TableStructure("customers")
            >>> customers.add_column("id", DataTypes.INTEGER(), primary_key=True)
            >>> orders.add_column("customer_id", DataTypes.INTEGER())
            >>> orders.foreign_key(
            ...     column="customer_id",
            ...     refrences_table=customers,
            ...     refrences_column=Column(customers, "id", int),
            ...     on_delete="CASCADE",
            ...     on_update="RESTRICT"
            ... )
            >>> orders.get_structure()
            'CREATE TABLE "orders" ("customer_id" INTEGER, FOREIGN KEY (customer_id) REFERENCES "customers" ("id") ON DELETE CASCADE ON UPDATE RESTRICT);'
        """
        self.foreigns.append(f'FOREIGN KEY ({column}) REFERENCES {refrences_table.name_} ({refrences_column.first_name}){f' ON DELETE {on_delete}' if on_delete else ''}{f' ON UPDATE {on_update}' if on_update else ''}')
        return self

    def get_structure(self):
        """Generate the complete SQL CREATE TABLE statement from the defined structure.

        This method compiles all columns, primary keys, foreign keys, and constraints
        into a single PostgreSQL CREATE TABLE statement. It validates that at least
        one column has been added before generating the statement.

        Returns:
            str: The full SQL CREATE TABLE statement that can be executed to create
                the table in the database.

        Raises:
            Exception: If no columns have been added to the table structure.

        Example:
            >>> struct = TableStructure("employees")
            >>> struct.add_column("id", DataTypes.INTEGER(), primary_key=True)
            >>> struct.add_column("name", DataTypes.VARCHAR(100), not_null=True)
            >>> struct.add_column("dept_id", DataTypes.INTEGER())
            >>> struct.foreign_key("dept_id", departments_table, departments_table.id,
            ...                    on_delete="CASCADE")
            >>> sql = struct.get_structure()
            >>> print(sql)
            CREATE TABLE "employees" (
                "id" INTEGER NOT NULL,
                "name" VARCHAR(100) NOT NULL,
                "dept_id" INTEGER,
                PRIMARY KEY("id"),
                FOREIGN KEY (dept_id) REFERENCES "departments" ("id") ON DELETE CASCADE
            );
        """
        if not self.get_columns():
            raise Exception('You must add at least one column to create a table')
        primary_key_clause = f', PRIMARY KEY({', '.join(self.primary_keys)})' if self.primary_keys else ''
        foreign_key_clause = f', {', '.join(self.foreigns)}' if self.foreigns else ''
        body = self.table_query[:-1] + primary_key_clause + foreign_key_clause
        return f'CREATE TABLE {self.name} ({body});'
