"""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."""


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, and schema
    management.

    The PostgreSQL implementation follows the same public approach as the other
    backends: applications import the public symbols from the package root and
    work with the returned table objects and schema helpers.

    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``, attempt to create the database before connecting.
    pool_size : int, optional
        Number of pooled connections. Defaults to ``5``.
    connect_timeout : int, optional
        Connection timeout in seconds. Defaults to ``10``.
    client_encoding : str, optional
        Connection encoding. Defaults to ``"UTF8"``.
    collate : str or None, optional
        Collation used when creating a new database.
    isolation_level : str, optional
        Transaction isolation level. One of ``'READ UNCOMMITTED'``,
        ``'READ COMMITTED'``, ``'REPEATABLE READ'``, or
        ``'SERIALIZABLE'``.

    Example
    -------
    >>> 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()
    """
    PLACE_HOLDER = '_MY_S4ULT3D_PL4C3_H0LD3R_%s_'
    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'):
        """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'``.

        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.PLACE_HOLDER = '_MY_S4ULT3D_PL4C3_H0LD3R_%s_'
        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.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
        }
        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}'"
                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()
            self.connection_pool.put((con, cur))
            self.connection_pool_storage.append(con)
            cur.execute(f"SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL {self.isolation_level};")
        except OperationalError as e:
            if e.sqlstate in self.CONNECTION_ERRORS:  
                con = connect(**self.config)
                cur = con.cursor()
                self.connection_pool.put((con, cur))
                self.connection_pool_storage.append(con)
                cur.execute(f"SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL {self.isolation_level};")
            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``.
        """
        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',)]
        """
        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}')

    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)
        """
        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}')

    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)
            ... )
        """
        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}')

    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;")
        """
        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}')

    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")]
            ... ])
        """
        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}')

    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)]
            ... )
        """
        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 ProgrammingError 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()
        """
        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.autocommit = True
                cur.execute(f'DROP DATABASE "{database_name}";')
                con.autocommit = False
                self.connection_pool.put((con, cur))
            except Exception:
                con.autocommit = False
                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.autocommit = True
            for i in tables:
                cur.execute(f'VACUUM (ANALYZE) "{i}";')
            con.autocommit = False
            self.connection_pool.put((con, cur))
        except Exception:
            con.autocommit = False
            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!")
        """
        query = f"CREATE USER \"{username.replace('\"', '\"\"')}\" WITH PASSWORD '{password}';"
        self._exc(query)

    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()
        """
        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()

    """
    Comprehensive SQLite Driver for Ormophine.

    The Driver class serves as the central gateway to the SQLite database,
    managing connections, threading, and high‑level operations. It provides
    a thread‑safe, non‑blocking environment where a single writer thread
    serialises all write operations, while an optional pool of reader threads
    handles concurrent read queries without interfering with the writer.

    Key Features
    ------------
    - **Automatic Table Discovery** – Existing tables are immediately
      available as attributes (e.g. ``driver.users``) right after
      initialisation.
    - **Strict Schema Creation** – Use ``TableStructure`` and ``DataTypes``
      to define tables with constraints, defaults, foreign keys, and
      conflict resolutions.
    - **Full CRUD via ``Table`` Objects** – Each table exposes fluent
      APIs for insert, update, delete, select, joins, batch operations,
      indexes, and column manipulation.
    - **Pragma Management** – Pragmas are set through the ``driver.SetPragma``
      helper that safely serialises commands onto the writer thread.
    - **WAL Mode & Checkpointing** – Easily enable WAL mode; a background
      checkpoint timer keeps the WAL file tidy.
    - **Non‑blocking Reads** – Fetches can be directed to a separate
      reader pool, avoiding contention with writes.
    - **Graceful Shutdown** – ``disconnect()`` stops all threads, commits
      pending work, and closes connections cleanly.

    Parameters
    ----------
    db_path : str
        Path to the SQLite database file. If the file does not exist,
        SQLite will create it.
    isolation_level : {'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE'}, optional
        Transaction isolation level (default ``'DEFERRED'``).
    cache_size : int, optional
        Number of cached SQL statements per connection (default 128).
    none_block_reader_pool_size : int, optional
        Number of reader threads to spawn. Each thread holds its own
        SQLite connection. Increase for highly concurrent read workloads
        (default 1).
    setup_time : float, optional
        Time in seconds to wait after initial table discovery (allows
        ``Table`` objects to populate their column attributes). Usually
        no need to change (default 0.5).

    Attributes
    ----------
    db_path : str
        The database file path.
    main_queue : queue.SimpleQueue
        Internal command queue for the writer thread.
    SetPragma : SetPragma
        Pragma interface (see :class:`SetPragma`).
    PLACE_HOLDER : str
        String used internally for complex parameter substitution. May
        be changed if it conflicts with real data (default
        ``'_MY_S4ULT3D_PL4C3_H0LD3R_?_'``).

    Examples
    --------
    >>> # 1. Connect
    >>> driver = Driver('example.db', isolation_level='IMMEDIATE')

    >>> # 2. Create a table with strict schema
    >>> schema = TableStructure('users', strict=True)
    >>> schema.add_column(
    ...     'id', DataTypes.INTEGER(),
    ...     primary_key=True
    ... ).add_column(
    ...     'name', DataTypes.TEXT(max_length=100),
    ...     not_null=True
    ... ).add_column(
    ...     'age', DataTypes.TINYINT(unsigned=True)
    ... )
    >>> users = driver.create_table(schema)

    >>> # 3. Insert a row
    >>> users.insert({users.name: 'Alice', users.age: 30})

    >>> # 4. Query with conditions
    >>> where = (users.name == 'Alice') & (users.age > 25)
    >>> row = users.get_row([users.id, users.name, users.age], where)
    >>> print(row)   # e.g. (1, 'Alice', 30)

    >>> # 5. Update
    >>> users.update({users.age: 31}, where)

    >>> # 6. Bulk insert
    >>> users.bulk_insert(
    ...     [users.name, users.age],
    ...     [('Bob', 25), ('Carol', 28)]
    ... )

    >>> # 7. Complex join
    >>> orders = driver.table_object('orders')  # assuming orders table exists
    >>> joined = users.join(
    ...     columns=[users.name, orders.total],
    ...     joins_list=[Join.Inner(orders, users.id == orders.user_id)],
    ...     where=orders.total > 100
    ... )
    >>> print(joined)

    >>> # 8. Non-blocking read (reader pool)
    >>> res = users.get_row([users.name], from_readers_pool=True)

    >>> # 9. PRAGMA
    >>> driver.SetPragma.journal_mode('WAL')
    >>> driver.SetPragma.foreign_keys(True)

    >>> # 10. Disconnect
    >>> driver.disconnect()
    """

    ISOLATION_LEVEL= Literal['DEFERRED', 'IMMEDIATE', 'EXCLUSIVE']
    PLACE_HOLDER = '_MY_S4ULT3D_PL4C3_H0LD3R_?_'
    def __init__(self, db_path: str, isolation_level: ISOLATION_LEVEL = 'DEFERRED',cache_size: int = 128, none_block_reader_pool_size: int = 1,setup_time: float = 0.5):
        """Initialises the database connection and the worker threads.

        Opens (and immediately closes) a test connection to verify the path,
        then starts a main writer thread and a pool of non‑blocking reader threads.
        All schema objects (tables) are discovered and exposed as attributes on the
        driver instance (e.g. ``driver.my_table``).  SetPragma helper is also
        attached as :attr:`SetPragma`.

        Args:
            db_path: Path to the SQLite database file.  If the file does not exist
                it will be created by the underlying writer thread.
            isolation_level: One of ``'DEFERRED'``, ``'IMMEDIATE'``, or
                ``'EXCLUSIVE'``.  Controls the transaction isolation mode.
                Defaults to ``'DEFERRED'``.
            cache_size: Number of compiled SQL statements to keep in the
                statement cache (passed to :func:`sqlite3.connect` as
                ``cached_statements``).  Defaults to 128.
            none_block_reader_pool_size: Number of separate reader connections
                and threads to create for non‑blocking read operations (used by
                the ``from_readers_pool`` parameter of various methods).
                Defaults to 1.
            setup_time: Time (in seconds) to sleep after discovering tables,
                giving each :class:`Table` instance time to fetch its column
                metadata before the constructor returns.  Defaults to 0.5.

        Raises:
            Exception: If the initial test connection fails (e.g. invalid path,
                permission denied).  Also raised if the subsequent master table
                query fails.

        Example:
            >>> driver = Driver('app.db', isolation_level='IMMEDIATE',
            ...                 none_block_reader_pool_size=3)
            >>> # Access a table directly
            >>> users = driver.users
            >>> # Use pragma helper
            >>> driver.SetPragma.journal_mode('WAL')
        """

        try:
            connector = connect(db_path , isolation_level=isolation_level , cached_statements=cache_size)
            connector.close()
        except Exception as e:
            raise Exception(e)
        self.PLACE_HOLDER = '_MY_S4ULT3D_PL4C3_H0LD3R_?_'
        self.db_path= db_path
        self.main_queue= SimpleQueue()
        self.wal_stop= Event()
        self.wal_enabled= Event()
        self.SetPragma= SetPragma(self)
        self.reader_pool_size = none_block_reader_pool_size
        self.pool_holder = SimpleQueue()
        for i in range(self.reader_pool_size):
            connection_queue = SimpleQueue()
            Thread(target=Driver.reader_driver, args=(connection_queue, self.db_path, isolation_level, cache_size)).start()
            self.pool_holder.put(connection_queue)
        Thread(target=Driver.simple_driver, args=(self.main_queue, self.db_path, isolation_level, cache_size)).start()
        QueueCallBack=SimpleQueue()
        self.main_queue.put(['qf', ('SELECT * FROM SQLITE_MASTER;',), QueueCallBack])
        if (callback:= QueueCallBack.get(block=True))[0]:
            [self.__setattr__(i[1], Table(self, i[1])) if i[0] == 'table' and i[1] != 'sqlite_sequence' else None for i in callback[1]]
            sleep(setup_time) #give Table objects some time to fetch from database and do __setattr__ 
        else:
            raise Exception(callback[1])
        
    def _exc(self, cmd: str, query: tuple) -> Any:
        """Send a command to the writer queue and return the result synchronously.

        This internal helper is the primary communication channel for all write
        operations.  It places a ``(cmd, query, callback_queue)`` tuple on
        :attr:`main_queue`, blocks until the writer thread has processed the
        request, and either returns the result or raises an exception.

        Args:
            cmd: A short string identifying the operation type. Supported values
                are ``'qf'`` (query‑fetch), ``'qcb'`` (query‑commit),
                ``'qsb'`` (script‑batch), ``'qmb'`` (executemany‑batch), and
                ``'cp'`` (checkpoint).  These map to the writer thread's
                ``match`` cases.
            query: The SQL statement and optional parameters. The exact format
                depends on *cmd*:
                - For ``'qf'`` and ``'qcb'``: ``(sql_statement,)`` or
                ``(sql_statement, parameters)``.
                - For ``'qsb'``: a list of such query tuples.
                - For ``'qmb'``: ``(sql_statement, sequence_of_parameters)``.

        Returns:
            The result produced by the writer thread:
            - For ``'qf'``, a list of rows (possibly empty).
            - For ``'qcb'``, ``'qsb'``, and ``'qmb'``, ``None`` on success.
            - For ``'cp'``, ``None`` (checkpoints are fire‑and‑forget from the
            caller's perspective).

        Raises:
            Exception: If the writer thread catches an exception during
                execution, it is re‑raised here with the original traceback
                message.  This includes SQLite operational errors, constraint
                violations, etc.

        Example:
            Usually called indirectly by higher‑level methods, but can be used
            for custom low‑level queries::

                driver = Driver('app.db')
                # Execute a simple PRAGMA
                driver._exc('qcb', ("PRAGMA user_version = 1;",))
                # Fetch results from a system table
                rows = driver._exc('qf', ('SELECT * FROM sqlite_master;',))
        """

        queue_call_back = SimpleQueue()
        self.main_queue.put((cmd, query, queue_call_back))
        if (callback := queue_call_back.get(block=True))[0]:
            return callback[1]
        else:
            raise Exception(callback[1])

    @staticmethod
    def reader_driver(receiver: SimpleQueue, db_path: str, isolation_level: str, cache_size: int):
        """Run a dedicated reader thread that executes queries from a queue.

        This static method is intended to be started as a separate thread. It
        continuously listens on the *receiver* queue for commands of the form
        ``['qf', (sql, [params]), callback_queue]`` or ``['dc']`` (disconnect).
        Each read query is executed on its own SQLite connection, and the
        result (or exception) is put into the *callback_queue*.

        The method is used internally by :class:`Driver` to create the non‑blocking
        reader pool. Applications normally do not call it directly.

        Args:
            receiver: Queue from which the thread receives work items. Each item
                is a list ``[command, query_tuple, callback_queue]``.
            db_path: Path to the SQLite database file.
            isolation_level: SQLite isolation level (e.g. ``'DEFERRED'``).
            cache_size: Number of cached statements for the connection.

        Returns:
            None. The function runs an infinite loop until a ``'dc'`` command is
            received, at which point it returns.

        Raises:
            No exceptions are propagated; any errors during query execution are
            returned to the caller through the callback queue as
            ``(False, exception)``.

        Example:
            This method is launched by :meth:`Driver.__init__` when creating the
            reader pool. For every configured reader a thread similar to::

                Thread(
                    target=Driver.reader_driver,
                    args=(connection_queue, db_path, isolation_level, cache_size)
                ).start()

            is started. A typical command sent to the queue looks like::

                callback = SimpleQueue()
                connection_queue.put(['qf', ('SELECT * FROM users',), callback])
                success, data = callback.get()
        """

        while True:
            try:
                connector = connect(db_path , isolation_level=isolation_level , cached_statements=cache_size)
                cursor = connector.cursor()
                break
            except:
                pass
        while True:
            try:
                query = receiver.get(block=True, timeout=0.05)
                if query[0] == 'dc':
                    break
            except:
                continue
            try:
                query[2].put((True, cursor.execute(query[1][0]).fetchall())) if len(query[1]) == 1 else query[2].put((True, cursor.execute(query[1][0], query[1][1]).fetchall()))
            except Exception as e:
                query[2].put((False, e))

    @staticmethod
    def simple_driver(receiver: SimpleQueue, db_path: str, isolation_level: str, cache_size: int):
        """Runs the main writer thread that processes all database commands serially.

        This static method is intended to be executed in a dedicated background thread.
        It opens a single SQLite connection, creates a cursor, and then enters an
        infinite loop waiting for commands on `receiver`.  Each command is a tuple
        of the form ``(cmd, payload, callback_queue)``.  Supported ``cmd`` values:

        * ``'qf'`` – execute a query and return the fetched rows via the callback.
        * ``'qcb'`` – execute a statement and commit (or rollback on error).
        * ``'qsb'`` – execute a list of statements as a single transaction.
        * ``'qmb'`` – execute a parameterised statement with ``executemany``.
        * ``'cp'`` – force a WAL checkpoint (``PRAGMA wal_checkpoint(TRUNCATE)``).
        * ``'dc'`` – commit, signal shutdown, and break the loop.

        On any exception the transaction is rolled back and an ``(False, exception)``
        tuple is sent back through the callback queue.  Successful operations return
        ``(True, result)``.

        Args:
            receiver: A :class:`queue.SimpleQueue` from which the thread reads
                commands.  Each command is a list/tuple with three elements:
                the command string, the query (with optional parameters), and a
                callback :class:`queue.SimpleQueue` to receive the result.
            db_path: Path to the SQLite database file.
            isolation_level: The transaction isolation level (e.g.
                ``'DEFERRED'``, ``'IMMEDIATE'``, ``'EXCLUSIVE'``) passed to
                :func:`sqlite3.connect`.
            cache_size: Number of compiled statements to cache (``cached_statements``
                parameter).

        Returns:
            None.  The method blocks until a ``'dc'`` command is received and then
            returns.

        Raises:
            This method does not raise exceptions directly; all database errors are
            caught and reported through the callback queue.

        Example:
            Typically this method is not called directly by users.  It is started
            internally by the :class:`Driver` constructor::

                Thread(target=Driver.simple_driver,
                    args=(self.main_queue, db_path, isolation_level, cache_size)).start()

            To simulate a command from outside (for testing purposes) you might do::

                import queue
                receiver = queue.SimpleQueue()
                # start the thread
                thread = Thread(target=Driver.simple_driver,
                                args=(receiver, 'test.db', 'DEFERRED', 128))
                thread.start()
                # send a command
                callback = queue.SimpleQueue()
                receiver.put(('qcb', ('CREATE TABLE t(x INTEGER)',), callback))
                success, _ = callback.get()
                print(success)  # True
                # stop the thread
                receiver.put(('dc', None, callback))
                thread.join()
        """
        
        connector = connect(db_path , isolation_level=isolation_level , cached_statements=cache_size)
        cursor = connector.cursor()
        while True:
            try:
                try:
                    query = receiver.get(block=True, timeout=0.05)
                    cmd = query[0]
                    print(query)
                except:
                    continue
                match cmd:
                    case 'qf':
                        try:
                            query[2].put((True, cursor.execute(query[1][0]).fetchall())) if len(query[1]) == 1 else query[2].put((True,cursor.execute(query[1][0], query[1][1]).fetchall()))
                        except Exception as e:
                            query[2].put((False, e))
                    case 'qcb':
                        try:
                            cursor.execute(query[1][0]) if len(query[1]) == 1 else cursor.execute(query[1][0], query[1][1])
                            connector.commit()
                            query[2].put((True, None))
                        except Exception as e:
                            connector.rollback()
                            query[2].put((False, e))
                    case 'qsb':
                        try:
                            [cursor.execute(i[0]) if len(i) == 1 else cursor.execute(i[0], i[1]) for i in query[1]]
                            connector.commit()
                            query[2].put((True, None))
                        except Exception as e:
                            connector.rollback()
                            query[2].put((False, e))
                    case 'qmb':
                        try:
                            cursor.executemany(query[1][0]) if len(query[1]) == 1 else cursor.executemany(query[1][0], query[1][1])
                            connector.commit()
                            query[2].put((True, None))
                        except Exception as e:
                            connector.rollback()
                            query[2].put((False, e))
                    case 'cp':
                        cursor.execute("PRAGMA wal_checkpoint(TRUNCATE);")
                        query[1].put(True)
                    case 'dc':
                        try:
                            connector.commit()
                            query[1].put((True, None))
                        except Exception as e:
                            connector.rollback()
                            query[1].put((False, e))
                        break
            except Exception as e:
                print_exc()

    @staticmethod
    def checkpoint_timer(main_commit_queue: SimpleQueue, timer: int, stop: Event):
        """Continuously triggers WAL checkpoints at a fixed interval.

        This static method is designed to run in a dedicated thread.  It
        periodically sends a ``'cp'`` command (WAL checkpoint TRUNCATE) to
        the main writer queue, helping to keep the WAL file size under
        control when WAL mode is enabled.  The loop runs until the
        *stop* :class:`~threading.Event` is set.

        This is an internal helper called by :meth:`Driver.set_WAL_mode` and
        should not normally be invoked directly.

        Args:
            main_commit_queue: The :class:`queue.SimpleQueue` used by the
                writer thread.  A ``['cp', callback_queue]`` message is
                placed into this queue to request a checkpoint.
            timer: Number of seconds to sleep between consecutive checkpoint
                requests.
            stop: A :class:`~threading.Event` that signals the loop to
                terminate.  When set, the function prints a message and
                returns.

        Returns:
            None: The function does not return; it blocks indefinitely until
            the stop event is signalled.

        Example:
            This function is started automatically when WAL mode is activated:

            >>> driver = Driver('app.db')
            >>> driver.set_WAL_mode(True, wal_timer=30)
            # Internally starts a thread running checkpoint_timer
        """

        while True:
            if stop.is_set():
                print('checkpoint stopped')
                break
            sleep(timer)
            call_back_queue = SimpleQueue()
            main_commit_queue.put(['cp', call_back_queue])
            try:
                call_back_queue.get(timeout=5.0)
            except:
                pass

    def table_object(self, table_name: str) -> 'Table':
        """Returns a :class:`Table` instance for the given table name.

        This method looks up the table in the database by calling
        :meth:`get_tables` and returns a freshly‑constructed
        :class:`Table` object.  It is useful when you need to work with
        a table that was not automatically exposed as an attribute of the
        :class:`Driver` instance, or when you prefer explicit access.

        Args:
            table_name: The exact name of the table (case‑sensitive) as
                it appears in the SQLite schema.

        Returns:
            A :class:`Table` object bound to this driver and the named
            table.  All column attributes are immediately available.

        Raises:
            Exception: If there are no tables at all in the database
                (``'No table found'``).
            Exception: If the requested *table_name* does not exist
                (``'No such table named …'``).

        Example:
            >>> driver = Driver('app.db')
            >>> # if table 'logs' is not already driver.logs
            >>> logs = driver.table_object('logs')
            >>> print(logs.get_columns_name())
            ['timestamp', 'message', 'level']
        """

        tables = self.get_tables()
        if not table_name in tables:
            if len(tables) == 0:
                raise Exception(f'No table found')
            raise Exception(f'No such table named {table_name} in this db')
        return Table(self, table_name)

    def custom_execute(self, query: str, params: list = None) -> None:
        """Execute a raw SQL statement on the main writer connection.

        Sends *query* and optional *params* to the background writer thread.
        The statement is committed immediately if it succeeds; on failure the
        transaction is rolled back and an exception is raised.

        Args:
            query: The SQL statement to execute.  May contain ``?``
                placeholders if *params* is supplied.
            params: A list of values to bind to the placeholders in *query*.
                Defaults to ``None``.

        Raises:
            Exception: If the writer thread encounters an error (e.g. syntax
                error, constraint violation).  The underlying exception
                message is propagated.

        Example:
            >>> driver = Driver('app.db')
            >>> driver.custom_execute('PRAGMA user_version = 1')
            >>> driver.custom_execute(
            ...     'INSERT INTO logs (message) VALUES (?)',
            ...     ['startup complete']
            ... )
        """

        return self._exc('qcb', (query, params)) if params else self._exc('qcb', (query,))
        
    def custom_execute_many(self, query: str, params: list = None) -> None:
        """Execute a raw SQL statement with multiple parameter sets (``executemany``).

        Sends the statement and the list of parameter sequences to the writer
        queue for execution inside a single transaction.  This is the
        recommended way to run bulk inserts, updates, or deletes that require
        multiple parameter sets but only one SQL command.

        Args:
            query: The SQL query string.  It must contain placeholders
                (``?``) that will be replaced by the elements of each
                parameter tuple in *params*.
            params: A list of tuples (or lists) where each element
                provides the values for one execution of the statement.
                If omitted or ``None``, the statement is executed once
                with no parameters (effectively the same as
                :meth:`custom_execute`).

        Returns:
            ``None``.  The operation is performed asynchronously; if it
            fails an exception will be raised.

        Raises:
            Exception: If the statement execution fails.  The exception
                contains the original SQLite error message.

        Example:
            >>> driver = Driver('app.db')
            >>> driver.custom_execute(
            ...     'CREATE TABLE logs (message TEXT, level INTEGER)'
            ... )
            >>> data = [("startup", 1), ("shutdown", 2), ("error", 3)]
            >>> driver.custom_execute_many(
            ...     'INSERT INTO logs VALUES (?, ?)',
            ...     data
            ... )
        """

        return self._exc('qmb', (query, params)) if params else self._exc('qmb', (query,))

    def custom_execute_with_fetch(self, query: str, params: list = None, from_readers_pool: bool = False) -> Any:
        """Execute an arbitrary SQL query and return the fetched rows.

        This is a low‑level method that submits a read query (typically a
        ``SELECT``) to the worker thread infrastructure.  By default it uses the
        main writer thread; when *from_readers_pool* is ``True`` it borrows a
        connection from the non‑blocking reader pool, which is useful for
        long‑running queries that should not block other writers.

        Args:
            query: The SQL statement to execute (usually a ``SELECT``).
            params: Optional list or tuple of bind parameters to substitute
                into *query* (using SQLite ``?`` placeholders).  Defaults to
                ``None``.
            from_readers_pool: If ``True`` the query is executed on a reader
                pool connection; otherwise the main writer thread is used.
                Defaults to ``False``.

        Returns:
            The result of ``cursor.fetchall()`` after executing the query.
            Typically a list of tuples (one tuple per row).  The exact format
            depends on the SQL statement.

        Raises:
            Exception: If the underlying worker reports an error (e.g.
                malformed SQL, missing table, or parameter mismatch).

        Example:
            >>> driver = Driver('app.db')
            >>> rows = driver.custom_execute_with_fetch(
            ...     "SELECT name FROM users WHERE age > ?", [18]
            ... )
            >>> # Use the reader pool to avoid blocking the writer
            >>> heavy = driver.custom_execute_with_fetch(
            ...     "SELECT * FROM large_logs WHERE processed = 0",
            ...     from_readers_pool=True
            ... )
        """

        if not from_readers_pool:
            return self._exc('qf', (query,params)) if params else self._exc('qf', (query,))
        else:
            queueCallBack = SimpleQueue()
            connection_queue = self.pool_holder.get(block=True)
            connection_queue.put(['qf', (query,params), queueCallBack]) if params else connection_queue.put(['qf', (query,), queueCallBack])
            self.pool_holder.put(connection_queue)
            if (callback := queueCallBack.get(block=True))[0]:
                return callback[1]
            else:
                raise Exception(callback[1])

    def get_tables(self) -> dict[str, 'Table']:
        """Retrieve a dictionary of all user tables in the database.

        Queries the ``SQLITE_MASTER`` table via
        :meth:`custom_execute_with_fetch` and returns a mapping from table
        name (string) to :class:`Table` instance for every table that is not
        the internal ``sqlite_sequence``.  The :class:`Table` objects are
        freshly constructed and have their column attributes populated.

        Returns:
            A dictionary where keys are table names (``str``) and values
            are corresponding :class:`Table` objects.

        Raises:
            Exception: Propagated from the underlying writer thread if the
                ``SQLITE_MASTER`` query fails.

        Example:
            >>> driver = Driver('app.db')
            >>> all_tables = driver.get_tables()
            >>> for name, tbl in all_tables.items():
            ...     print(f'{name}: {len(tbl.get_columns_name())} columns')
            users: 5 columns
            orders: 8 columns
        """

        tables_list = self.custom_execute_with_fetch('SELECT * FROM SQLITE_MASTER;')
        tables_dict = {}
        for item in tables_list:
            if item[0] == 'table' and not item[1] == 'sqlite_sequence':
                tables_dict[item[1]] = Table(self, item[1])
        return tables_dict

    def create_table(self, table_structure: 'TableStructure') -> 'Table':
        """Creates a new table in the database from a :class:`TableStructure` definition.

        Executes the ``CREATE TABLE`` statement generated by
        :meth:`TableStructure.get_structure`, then immediately makes the
        table accessible as an attribute of the driver instance (e.g.
        ``driver.new_table``) and returns the corresponding :class:`Table`
        object.  All column metadata is fetched and attached to the table.

        Args:
            table_structure: A fully configured :class:`TableStructure`
                instance describing the columns, constraints, foreign keys,
                and strict mode setting.

        Returns:
            :class:`Table`: The newly created table object, ready for
            inserts, updates, queries, etc.

        Raises:
            Exception: Propagated from the writer thread if the SQL
                execution fails (e.g. syntax error in the structure,
                duplicate table name, or violation of database constraints).

        Example:
            >>> structure = TableStructure('employees', strict=True)
            >>> structure.add_column('id', DataTypes.INTEGER(), primary_key=True)
            >>> structure.add_column('name', DataTypes.TEXT(max_length=100))
            >>> structure.add_column('salary', DataTypes.REAL(min_val=0.0))
            >>> driver = Driver('company.db')
            >>> emp_table = driver.create_table(structure)
            >>> # Now driver.employees is also available
            >>> driver.employees.insert({emp_table.name: 'John'})
        """

        self._exc('qcb', (table_structure.get_structure(),))
        self.__setattr__(table_structure.name, Table(self,table_structure.name))
        return Table(self, table_structure.name)

    def defragment(self) -> None:
        """Rebuilds the database file and updates statistics for the query planner.

        Executes the SQLite ``VACUUM`` command followed by ``PRAGMA optimize``.
        ``VACUUM`` rebuilds the entire database, reclaiming unused space and
        defragmenting the file.  ``PRAGMA optimize`` analyzes the database
        and updates internal statistics to help the query planner choose
        efficient execution plans.

        Returns:
            None.  The operation is performed synchronously on the writer
            thread.

        Raises:
            Exception: If either the ``VACUUM`` or ``PRAGMA optimize``
                command fails (e.g. disk full, permission error).

        Example:
            >>> driver = Driver('my_data.db')
            >>> driver.defragment()
            # The database file is now compacted and optimized.
        """

        self._exc('qcb', ("VACUUM;PRAGMA optimize;",))

    def set_WAL_mode(self, is_set: bool, wal_timer: int = 60) -> None:
        """Enables or disables Write‑Ahead Logging (WAL) mode.

        When enabled, the journal mode is set to ``WAL`` and a background
        thread periodically performs truncate checkpoints (see
        :meth:`checkpoint_timer`).  When disabled, the checkpoint timer is
        stopped, journal mode is switched back to ``PERSIST``, and a final
        manual checkpoint is executed.

        Args:
            is_set: ``True`` to enable WAL mode, ``False`` to disable it.
            wal_timer: Interval in seconds between automatic checkpoints
                while WAL is active.  Ignored when ``is_set`` is ``False``.
                Defaults to 60.

        Returns:
            None.

        Raises:
            Exception: Propagated from the writer thread if a ``PRAGMA``
                statement fails (e.g. database is locked).

        Example:
            >>> driver = Driver('mydb.db')
            >>> # Enable WAL with 30‑second checkpoints
            >>> driver.set_WAL_mode(True, wal_timer=30)
            >>> # ... perform heavy writes ...
            >>> # Disable WAL and return to PERSIST
            >>> driver.set_WAL_mode(False)
        """

        if is_set:
            self.wal_enabled.set()
            self.wal_stop.clear()
            self._exc('qcb', ("PRAGMA journal_mode=WAL;",))
            Thread(target=Driver.checkpoint_timer, args=(self.main_queue, wal_timer, self.wal_stop)).start()
        else:
            self.wal_stop.set()
            self._exc('qcb', ("PRAGMA journal_mode=PERSIST;",))
        call_back_queue = SimpleQueue()
        self.main_queue.put(['cp', call_back_queue])
        call_back_queue.get(block=True)

    def disconnect(self) -> None:
        """Gracefully shut down the database connection and all worker threads.

        This method shuts down the driver in a controlled order:

        1. It signals any active WAL-checkpoint timer to stop.
        2. If WAL mode was enabled, it triggers a final checkpoint through the
           main writer thread and waits for completion.
        3. It sends a disconnect command to the writer thread so pending work
           is committed and the connection is closed.
        4. It sends disconnect commands to every reader-pool connection so the
           reader threads can exit cleanly.

        After this call, the driver instance should no longer be used.

        Raises:
            Exception: May propagate exceptions from the writer thread if the
                final commit or rollback fails.

        Example:
            >>> driver = Driver('app.db')
            >>> driver.disconnect()
        """

        self.wal_stop.set()
        callback_dc = SimpleQueue()
        if self.wal_enabled.is_set():
            call_back_queue = SimpleQueue()
            self.main_queue.put(['cp', call_back_queue])
            self.main_queue.put(['dc' , callback_dc])
            call_back_queue.get(block=True)
        else:
            self.main_queue.put(['dc' , callback_dc])
        callback_dc.get(block=True)
        for i in range(self.reader_pool_size):
            connection_queue = self.pool_holder.get(block=True)
            connection_queue.put(['dc'])

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

    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]): The right-hand
                operand. Can be another :class:`ColumnsOperation`, a :class:`Column`,
                or a literal value (int, float, or str).

        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
            >>> # Add 10% bonus to salary
            >>> expr = employees.salary * 1.1 + 1000
            >>> # This generates: (("salary" * 1.1) + %s) with params [1000]

        Example:
            String concatenation with a column and a literal:

            >>> # Assuming 'first_name' and 'last_name' are string columns
            >>> full_name = employees.first_name + " " + employees.last_name
            >>> # Generates: (("first_name" || %s) || "last_name") with params [' ']

        Note:
            The operation uses `+` for numeric types and `||` for strings,
            determined by the `col_obj.datatype` attribute. For literals, the
            method automatically chooses the appropriate operator based on
            the column's datatype.
        """
        self._output = (f'({self._output[0]} {'||' if self.col_obj.datatype == str else '+'} {other._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({self._output[0]} {'||' if self.col_obj.datatype == str else '+'} {other.name})', self._output[1]) if isinstance(other, Column) else (f'({self._output[0]} + %s)', self._output[1]+[other]) if isinstance(other, int) or isinstance(other , float) else (f'({self._output[0]} || %s)', self._output[1]+[other if isinstance(other, str) else str(other)])
        return self

    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")
        """
        self._output = (f'({other._output[0]} {'||' if self.col_obj.datatype == str else '+'} {self._output[0]})', other._output[1]+self._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} {'||' if self.col_obj.datatype == str else '+'} {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(%s + {self._output[0]})', [other]+self._output[1]) if isinstance(other, int) or isinstance(other , float) else (f'(%s || {self._output[0]})', [other if isinstance(other, str) else str(other)]+self._output[1])
        return self

    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)
        """
        self._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 self

    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
            []
        """
        self._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 self

    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")'
        """
        self._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 self

    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
            []
        """
        self._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 self

    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
        """
        self._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 self

    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]
        """
        self._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 self

    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")
        """
        self._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 self

    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
            []
        """
        self._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 self

    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]
        """
        self._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 self

    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
            []
        """
        self._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 self


    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])  # parameters
            [3]

            >>> # Extract from index 2 to the end (Python 0-based, SQL 1-based)
            >>> op = employees.name[2:]
            >>> print(op._output[0])
            'SUBSTRING("employees"."name" , %s , LENGTH("employees"."name"))'
            >>> print(op._output[1])
            [3]  # because 2+1

            >>> # Negative slicing: last 5 characters
            >>> 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

            >>> # Combined with other operations
            >>> op = employees.name[1:5].upper()
            >>> print(op._output[0])
            'UPPER(SUBSTRING("employees"."name" , %s , %s))'
            >>> print(op._output[1])
            [2, 4]  # start=1 -> 2, stop=5 -> length=4
        """
        if self._output:
            if key.start == None and key.stop ==  None:
                self._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:
                self._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:
                self._output = (f'SUBSTRING({self._output[0]} , 1 , %s)', self._output[1] + [key.stop])  #  
            elif key.start >= 0 and key.stop ==  None:
                self._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:
                self._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:
                self._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:
                self._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:
                self._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:
                self._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:
                self._output = (f'SUBSTRING({self.col_obj.name} , 1 , LENGTH({self.col_obj.name}) + 1)', [])   #
            elif key.start == None and key.stop < 0:
                self._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:
                self._output = (f'SUBSTRING({self.col_obj.name} , 1 , %s)', [key.stop])  #  
            elif key.start >= 0 and key.stop ==  None:
                self._output = (f'SUBSTRING({self.col_obj.name} , %s , LENGTH({self.col_obj.name}))', [key.start + 1])  #   
            elif key.start < 0 and key.stop == None:
                self._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:
                self._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:
                self._output = (f'SUBSTRING({self.col_obj.name} , %s , %s)', [key.start + 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop < 0:
                self._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:
                self._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 self

    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.).

        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")'
        """
        self._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 self

    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.

        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]
        """
        self._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 self

    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.).

        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")
        """
        self._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 self

    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.).

        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")
        """
        self._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 self

    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")
        """
        self._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 self

    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
        """
        self._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 self

    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")
        """
        self._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 self

    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')
        """
        self._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 self

    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)
        """
        self._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 self

    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)
        """
        self._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 self

    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)
        """
        self._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 self

    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)
        """
        self._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 self

    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))
        """
        self._output = (f'({self._output[0]} AND {value._output[0]})', self._output[1] + value._output[1])
        return self

    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.
        """
        self._output = (f'({self._output[0]} OR {value._output[0]})', self._output[1] + value._output[1])
        return self

    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)
        """
        self._output = (f"{self._output[0]} like {value._output[0]}", (self._output[1] + value._output[1]) if self._output 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 self

    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%'
        """
        self._output = (f"{self._output[0]} like {prefix._output[0]} || '%%'", (self._output[1] + prefix._output[1]) if self._output 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 self

    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)
        """
        self._output = (f"{self._output[0]} like '%%' || {suffix._output[0]}", (self._output[1] + suffix._output[1]) if self._output 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 self

    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")
        """
        self._output = (f"{self._output[0]} like '%%' || {value._output[0]} || '%%'", (self._output[1] + value._output[1]) if self._output 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 self

    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")
        """
        self._output = (f'({self._output[0]} || {content._output[0]})', self._output[1]+content._output[1] if self._output else content._output[1]) if isinstance(content, ColumnsOperation) else (f'({self._output[0]} || {content.name})', self._output[1] if self._output else []) if isinstance(content, Column) else (f'({self._output[0]} || %s)', self._output[1]+[content] if self._output else [content])
        return self

    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"))
        """
        self._output = (f'({content._output[0]} || {self._output[0]})', content._output[1]+self._output[1] if self._output else content._output[1]) if isinstance(content, ColumnsOperation) else (f'({content.name} || {self._output[0]})', self._output[1] if self._output else []) if isinstance(content, Column) else (f'(%s || {self._output[0]})', [content]+self._output[1] if self._output else [content])
        return self

    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')
        """
        self._output = (f'REPLACE({self._output[0]} , %s , %s)', self._output[1] + [old, new]) if self._output else (f'REPLACE({self.col_obj.name} , %s , %s)', [old, new])
        return self

    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()
        """
        self._output = (f'UPPER({self._output[0]})', self._output[1]) if self._output else (f'UPPER({self.col_obj.name})', [])
        return self

    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"))'
        """
        self._output = (f'LOWER({self._output[0]})', self._output[1]) if self._output else (f'LOWER({self.col_obj.name})', [])
        return self

    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\"))"
        """
        self._output = (f"TRIM(BOTH '{chars}' FROM {self._output[0]})", self._output[1]) if self._output else (f"TRIM(BOTH '{chars}' FROM {self.col_obj.name})", [])
        return self

    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\"))"
        """
        self._output = (f"TRIM(LEADING '{chars}' FROM {self._output[0]})", self._output[1]) if self._output else (f"TRIM(LEADING '{chars}' FROM {self.col_obj.name})", [])
        return self

    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])
            []
        """
        self._output = (f"TRIM(TRAILING '{chars}' FROM {self._output[0]})", self._output[1]) if self._output else (f"TRIM(TRAILING '{chars}' FROM {self.col_obj.name})", [])
        return self

    def In(self, value):
        """Generate a SQL `IN` clause or equality for this column operation.

        This method constructs a SQL `IN` expression that checks whether the current
        column operation's value matches any value in a given set or subquery.
        The behavior depends on the type of `value`:

        - If `value` is a :class:`ColumnsOperation`, it generates a subquery IN clause
        (e.g., `column IN (subquery)`).
        - If `value` is a list or tuple, it generates `IN (?, ?, ...)` with one
        placeholder per item, and adds all items as parameters.
        - If `value` is a scalar (single value), it generates an equality condition
        `= ?` instead of `IN`, which is equivalent and more efficient.

        The result is stored internally, allowing chained operations.

        Args:
            value (Any): The set of values or subquery to check against.
                Can be a :class:`ColumnsOperation`, :class:`Column` (though this
                would be unusual), `list`, `tuple`, or a scalar 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
            >>> # Using a list of values
            >>> cond = employees.department.In(['Engineering', 'Sales', 'Marketing'])
            >>> print(cond._output[0])
            '("employees"."department" IN (%s,%s,%s))'
            >>> print(cond._output[1])
            ['Engineering', 'Sales', 'Marketing']
            >>>
            >>> # Using a subquery (ColumnsOperation)
            >>> subquery = driver.departments.id  # assuming a column
            >>> cond2 = employees.dept_id.In(subquery)
            >>> # The generated SQL will be something like:
            >>> # ("employees"."dept_id" IN ("departments"."id"))
            >>>
            >>> # Scalar value produces equality
            >>> cond3 = employees.id.In(100)
            >>> print(cond3._output[0])
            '("employees"."id" = %s)'
            >>> print(cond3._output[1])
            [100]
        """
        self._output = (f"{self._output[0]} IN ({value._output[0]})", self._output[1] + value._output[1]) if isinstance(value, ColumnsOperation) else (f"{self._output[0]} IN ({','.join(['%s'] * len(value))})",self._output[1] + list(value)) if isinstance(value, (list, tuple)) else (f"{self._output[0]} = %s",self._output[1] + [value])
        return self

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 temp_ob ** value

    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.).

        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} = %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`.

        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} = %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.).

        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} != %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`.

        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} != %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 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 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 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 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 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 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 else value._output[1]) if isinstance(value, ColumnsOperation) else (f'{self.name} like {value.name}', temp_ob._output[1] if temp_ob._output else []) if isinstance(value , Column) else (f'{self.name} like %s', (temp_ob._output[1] + [f'{value}']) if temp_ob._output 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 else value._output[1]) if isinstance(value, ColumnsOperation) else (f"{self.name} like {value.name} || '%%'", temp_ob._output[1] if temp_ob._output else []) if isinstance(value , Column) else (f"{self.name} like %s || '%%'", (temp_ob._output[1] + [f'{value}']) if temp_ob._output 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 else value._output[1]) if isinstance(value, ColumnsOperation) else (f"{self.name} like '%%' || {value.name}", temp_ob._output[1] if temp_ob._output else []) if isinstance(value , Column) else (f"{self.name} like '%%' || %s", (temp_ob._output[1] + [f'{value}']) if temp_ob._output 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 else value._output[1]) if isinstance(value, ColumnsOperation) else (f"{self.name} like '%%' || {value.name} || '%%'", temp_ob._output[1] if temp_ob._output else []) if isinstance(value , Column) else (f"{self.name} like '%%' || %s || '%%'", (temp_ob._output[1] + [f'{value}']) if temp_ob._output 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, value):
        """Generate a SQL `IN` clause or equality condition for this column.

        This method creates a :class:`ColumnsOperation` that represents a SQL `IN`
        expression, checking whether the column's value matches any value in a set
        or subquery. The behavior depends on the type of `value`:

        - If `value` is a :class:`ColumnsOperation`, it generates an `IN` clause with
        a subquery (e.g., `column IN (subquery)`).
        - If `value` is a list or tuple, it generates `IN (?, ?, ...)` with one
        placeholder per item, and adds all items to the parameter list.
        - If `value` is a scalar (single value), it generates an equality condition
        `= ?` instead of `IN`, which is equivalent and more efficient.

        The result is a :class:`ColumnsOperation` that can be used directly in
        `WHERE` clauses or combined with other conditions using logical operators.

        Args:
            value (Any): The set of values or subquery to check against. Can be a
                :class:`ColumnsOperation` (subquery), a list or tuple of values,
                or a scalar (int, float, str, etc.).

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance representing the
            `IN` or equality expression, ready for chaining or use in queries.

        Example:
            >>> from ormophine.Postgresql import Driver, Table
            >>> driver = Driver(...)
            >>> employees = driver.employees
            >>> departments = driver.departments
            >>>
            >>> # Using a list of values
            >>> cond = employees.department.In(['Engineering', 'Sales', 'Marketing'])
            >>> print(cond._output[0])
            '("employees"."department" IN (%s,%s,%s))'
            >>> print(cond._output[1])
            ['Engineering', 'Sales', 'Marketing']
            >>>
            >>> # Using a subquery (ColumnsOperation)
            >>> # Assuming we have a column from another table
            >>> subquery = departments.id  # Column object, which will be wrapped
            >>> cond2 = employees.dept_id.In(subquery)
            >>> # This generates: ("employees"."dept_id" IN ("departments"."id"))
            >>>
            >>> # Scalar value produces equality
            >>> cond3 = employees.id.In(100)
            >>> print(cond3._output[0])
            '("employees"."id" = %s)'
            >>> print(cond3._output[1])
            [100]
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f"{self.name} IN ({value._output[0]})",value._output[1]) if isinstance(value, ColumnsOperation) else (f"{self.name} IN ({','.join(['%s'] * len(value))})",list(value)) if isinstance(value, (list, tuple)) else (f"{self.name} = %s",[value])
        return temp_ob

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()
        """
        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()
        """
        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)

class Join:

    """Factory namespace for creating SQL JOIN clauses.

    This class serves as a container for nested join type classes (`Inner`, `Left`,
    `Right`). Each nested class, when instantiated, produces a join fragment that
    can be passed to the :meth:`Table.join` method to build complex SELECT queries
    with joined tables.

    The nested classes store both the SQL join string and its associated
    parameter list, which are used internally by the ORM.

    Example:
        Basic usage with an INNER JOIN:

        >>> employees = driver.employees
        >>> departments = driver.departments
        >>> join_condition = employees.dept_id == departments.id
        >>> inner = Join.Inner(departments, join_condition)
        >>> results = employees.join(
        ...     [employees.name, departments.name],
        ...     [inner]
        ... )

    Example:
        Using a LEFT JOIN to include all employees even if they have no department:

        >>> left = Join.Left(departments, join_condition)
        >>> results = employees.join(
        ...     [employees.name, departments.name],
        ...     [left],
        ...     where=employees.salary > 50000
        ... )

    Example:
        Using a RIGHT JOIN to include all departments even if they have no employees:

        >>> right = Join.Right(departments, join_condition)
        >>> results = employees.join(
        ...     [employees.name, departments.name],
        ...     [right]
        ... )

    Note:
        Multiple joins can be combined by passing a list of join objects to
        :meth:`Table.join`.
    """
        
    class Inner:
        def __init__(self, table: Table, match_case_condition: ColumnsOperation):
            """Initialize an INNER JOIN clause for a query.

            This constructor creates an INNER JOIN fragment that can be used in the
            :meth:`Table.join` method. It stores the SQL representation and its
            associated parameters for later use in building a complete SELECT query.

            Args:
                table (Table): The table to join with.
                match_case_condition (ColumnsOperation): The join condition, typically
                    a comparison between columns from the main table and the joined table.

            Returns:
                None: This method initializes the instance and does not return a value.

            Example:
                >>> employees = driver.employees
                >>> departments = driver.departments
                >>> join_condition = employees.dept_id == departments.id
                >>> inner_join = Join.Inner(departments, join_condition)
                >>> # Then use in a join query:
                >>> results = employees.join(
                ...     [employees.name, departments.name],
                ...     [inner_join]
                ... )
            """
            self._output = (f'INNER JOIN {table.name_} ON {match_case_condition._output[0]}', match_case_condition._output[1])
            
    class Left:
        def __init__(self, table: Table, match_case_condition: ColumnsOperation):
            """Create a LEFT JOIN clause for use in a :meth:`Table.join` query.

            This class represents a LEFT JOIN between the current table and another
            table, with a specified join condition. It stores the generated SQL
            fragment and its parameters in the `_output` attribute, which is
            consumed by :meth:`Table.join` to build the final query.

            Args:
                table (Table): The table to join with.
                match_case_condition (ColumnsOperation): A condition expression
                    defining how the tables are related (e.g., using equality
                    comparisons). This is used in the `ON` clause of the join.

            Returns:
                None: The constructor initializes the instance and does not return
                a value.

            Example:
                >>> employees = driver.employees
                >>> departments = driver.departments
                >>> join_condition = employees.dept_id == departments.id
                >>> left_join = Join.Left(departments, join_condition)
                >>> results = employees.join(
                ...     [employees.name, departments.name],
                ...     [left_join]
                ... )
                >>> # This generates: SELECT ... FROM "employees"
                >>> # LEFT JOIN "departments" ON ("employees"."dept_id" = "departments"."id")
            """
            self._output = (f'LEFT JOIN {table.name_} ON {match_case_condition._output[0]}', match_case_condition._output[1])

    class Right:
        def __init__(self, table: Table, match_case_condition: ColumnsOperation):
            """Initialize a RIGHT JOIN clause for a query.

            This constructor creates a RIGHT JOIN fragment that can be used in the
            :meth:`Table.join` method. A RIGHT JOIN returns all rows from the right
            table (the one being joined), and the matching rows from the left table.
            If no match is found, NULL values are returned for the left table's columns.

            Args:
                table (Table): The table to join with (the right side of the join).
                match_case_condition (ColumnsOperation): The join condition, typically
                    a comparison between columns from the main table and the joined table.

            Returns:
                None: This method initializes the instance and does not return a value.

            Example:
                >>> employees = driver.employees
                >>> departments = driver.departments
                >>> join_condition = employees.dept_id == departments.id
                >>> right_join = Join.Right(departments, join_condition)
                >>> # This will include all departments, even those with no employees.
                >>> results = employees.join(
                ...     [employees.name, departments.name],
                ...     [right_join]
                ... )
            """
            self._output = (f'RIGHT JOIN {table.name_} ON {match_case_condition._output[0]}', match_case_condition._output[1])

class Table:
    PLACE_HOLDER = '_MY_S4ULT3D_PL4C3_H0LD3R_%s_'
    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 = '_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 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);
        """
        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' = None):
        """Fetch rows from the table with selected columns, optional filtering and ordering.

        This method 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, optional): A :class:`Column` object to order the
                results by. Defaults to None (no ordering).

        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:

            >>> from ormophine.Postgresql import Driver, Table, Column
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> employees = driver.employees
            >>> # Assume columns: id, name, salary, department
            >>> # Get names of all engineers, ordered by salary
            >>> names = employees.get_row(
            ...     [employees.name],
            ...     where=employees.department == "Engineering",
            ...     order_by=employees.salary
            ... )
            >>> # names is a list like ['Alice', 'Bob', ...]

        Example:
            Complex query with computed columns using :class:`ColumnsOperation`:

            >>> from ormophine.Postgresql import ColumnsOperation
            >>> # Get employee id and full name (concatenated) for those with
            >>> # salary greater than average (using arithmetic and string ops)
            >>> employees.get_row(
            ...     [
            ...         employees.id,
            ...         employees.first_name + " " + employees.last_name  # string concat
            ...     ],
            ...     where=employees.salary > (employees.salary * 0.5 + 30000)  # complex condition
            ... )
            >>> # Returns list of tuples like [(1, 'John Doe'), (2, 'Jane Smith')]
        """
        tl = []
        wc = []
        [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 order_by else ''};', tl+where._output[1]) if where else self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if order_by else ''};',tl) if tl else self._excf(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if order_by 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 order_by else ''};', tl+where._output[1]) if where else self._excfp(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if order_by else ''};',tl) if tl else self._excf(f'SELECT {', '.join(wc)} FROM {self.name_} {f'ORDER BY {order_by.first_name}' if order_by else ''};',)
        
    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.
        """
        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", "smallserial", "serial", "bigserial") else float if type_lower in ("real", "double precision", "numeric", "decimal", "money") 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`.

        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.
                Use `PLACE_HOLDER` for values that should come from `data_list`.
            where (ColumnsOperation): A :class:`ColumnsOperation` object representing
                the condition that determines which rows to update. Can 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 the `update` and
                `where` clauses (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:

            >>> # Increase salary by a variable amount for each department
            >>> employees = Table(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"]]
            ... )
            >>> # This generates: UPDATE "employees" SET "salary" = ("salary" + %s)
            >>> # WHERE "department" = %s; and executes with the given data.

        Example:
            Complex bulk update with computed expressions and multiple placeholders:

            >>> # Set bonus as a percentage of salary and update title for managers
            >>> 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]]
            ... )
            >>> # The PLACE_HOLDER in the update (for percentage) and in the where condition
            >>> # (for years threshold) are replaced from data_list rows.
            >>> # Each row provides [percentage, years_threshold].
        """
        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}={self.PLACE_HOLDER}' if not isinstance(value , ColumnsOperation) else f'{key.first_name}={value._output[0].replace('%s', self.PLACE_HOLDER)}' for key , value in list(update.items()))} WHERE {where._output[0].replace('%s', self.PLACE_HOLDER)};'.split(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 == 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(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 "{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 join(
        self,
        columns: list['Column'],
        joins_list: list['Join.Inner | Join.Left | Join.Right'],
        where: 'ColumnsOperation' = None,
        order_by: 'Column' = None
    ) -> Any:
        """Perform a JOIN query on this table with other tables.

        This method constructs and executes a SELECT statement that joins the current
        table with one or more other tables using the specified join types
        (INNER, LEFT, RIGHT). The result set can be filtered with a WHERE clause
        and ordered by a column. Columns are automatically aliased using the format
        ``<table_name>_<column_name>`` to avoid name conflicts.

        Args:
            columns (list[Column]): A list of :class:`Column` objects or
                :class:`ColumnsOperation` expressions to select. Each item will be
                included in the SELECT clause.
            joins_list (list[Union[Join.Inner, Join.Left, Join.Right]]): A list of
                join definitions created using the :class:`Join` inner classes.
                Each join specifies a table and the join condition.
            where (ColumnsOperation, optional): A :class:`ColumnsOperation`
                expression for filtering rows. Defaults to None (no filter).
            order_by (Column, optional): A :class:`Column` to order the results by.
                Defaults to None (no ordering).

        Returns:
            list[tuple]: A list of tuples where each tuple represents a row in the
                result set. The values correspond to the selected columns in the
                order they were specified. If columns include aliases, the result
                tuples will have the aliased names (though the return format is
                raw tuples).

        Raises:
            Exception: Propagates any database errors from the underlying driver,
                including SQL syntax errors or join condition issues.

        Example:
            Simple join between employees and departments:

            >>> from ormophine.Postgresql import Driver, Table, Join
            >>> driver = Driver("localhost", 5432, "user", "pass", "mydb")
            >>> employees = driver.employees
            >>> departments = driver.departments
            >>>
            >>> # Join employees with departments on department_id
            >>> results = employees.join(
            ...     columns=[employees.id, employees.name, departments.name],
            ...     joins_list=[Join.Inner(departments, employees.dept_id == departments.id)],
            ...     where=employees.salary > 50000,
            ...     order_by=employees.name
            ... )
            >>> for row in results:
            ...     print(row)  # e.g., (1, 'Alice', 'Engineering')

        Example:
            Complex join with multiple tables and computed columns:

            >>> from ormophine.Postgresql import Join, ColumnsOperation
            >>> # Assume tables: orders, customers, products
            >>> orders = driver.orders
            >>> customers = driver.customers
            >>> products = driver.products
            >>>
            >>> # Select order details with customer name and product price with tax
            >>> results = orders.join(
            ...     columns=[
            ...         orders.id,
            ...         customers.name,
            ...         products.name,
            ...         orders.quantity * orders.unit_price,  # ColumnsOperation
            ...         (orders.quantity * orders.unit_price) * 1.1  # computed total with tax
            ...     ],
            ...     joins_list=[
            ...         Join.Inner(customers, orders.customer_id == customers.id),
            ...         Join.Left(products, orders.product_id == products.id)
            ...     ],
            ...     where=(orders.order_date >= '2024-01-01') & (orders.status == 'completed'),
            ...     order_by=orders.order_date
            ... )
            >>> # Results are returned as tuples with aliased column names
        """
        tl = []
        [tl.extend(i._output[1]) if isinstance(i,ColumnsOperation) else None for i in columns]
        [tl.extend(i._output[1]) for i in joins_list]
        return self._excfp(f'SELECT {','.join(f'{i.name} AS {i.table_obj.name_[1:-1]}_{i.first_name[1:-1]}'if isinstance(i,Column)else f'{i._output[0][1:-1] if i._output[0].startswith("(") and i._output[0].endswith(")")else i._output[0]} AS {i.col_obj.table_obj.name_[1:-1]}_{i.col_obj.first_name[1:-1]}' for i in columns)} FROM {self.name_} {' '.join(i._output[0] for i in joins_list)} {f'WHERE {where._output[0]}'if where else''} {f'ORDER BY {order_by.name}' if order_by else''}', tl+where._output[1]) if where else self._excfp(f'SELECT {','.join(f'{i.name} AS {i.table_obj.name_[1:-1]}_{i.first_name[1:-1]}'if isinstance(i,Column)else f'{i._output[0][1:-1] if i._output[0].startswith("(") and i._output[0].endswith(")") else i._output[0] } AS {i.col_obj.table_obj.name_[1:-1]}_{i.col_obj.first_name[1:-1]}'for i in columns)} FROM {self.name_} {' '.join(i._output[0]for i in joins_list)} {f'ORDER BY {order_by.name}' if order_by else''}', tl) if tl else self._excf(f'SELECT {','.join(f'{i.name} AS {i.table_obj.name_[1:-1]}_{i.first_name[1:-1]}' if isinstance(i,Column) else f'{i._output[0][1:-1] if i._output[0].startswith('(') and i._output[0].endswith(')') else i._output[0] } AS {i.col_obj.table_obj.name_[1:-1]}_{i.col_obj.first_name[1:-1]}' for i in columns)} FROM {self.name_} {' '.join(i._output[0] for i in joins_list)} {f'ORDER BY {order_by.name}'if order_by else''}')
        # The above line is approximately 1381 characters, which is not standard, but it is written this way
        # to improve performance in the Driver class and to avoid checking whether the second item in the query
        # is an empty list for each input.

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), ... );
        """
        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`.

        When ``SMALLSERIAL`` is used in :meth:`TableStructure.add_column`, the
        method automatically sets ``primary_key=True``, ``not_null=True``, and
        ``auto_increment=True`` unless explicitly overridden. The underlying
        Python type mapped for SMALLSERIAL columns is :class:`int`.

        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"

        # ========================
        # Array Type
        # ========================

    @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, primary key
    columns are not null, 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 not marked NOT NULL.
                - 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 not not_null:
                raise Exception("PRIMARY KEY columns must also be NOT NULL.")
            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 {f"'{default_value}'" if type(default_value) == str else default_value}' if default_value is not None else ''},'
        return self

    def delete_column(self, column_name: str):
        """Remove a column from the table definition.

        This method deletes the specified column from the internal column store
        and updates the SQL creation string accordingly. It is useful for modifying
        a table structure before creation.

        Args:
            column_name (str): The name of the column to remove.

        Returns:
            TableStructure: The current instance, allowing method chaining.

        Raises:
            KeyError: If the specified column does not exist in the table definition.

        Example:
            >>> table = TableStructure("employees")
            >>> table.add_column("id", DataTypes.INTEGER(), primary_key=True)
            >>> table.add_column("name", DataTypes.VARCHAR(50))
            >>> table.delete_column("name")
            >>> table.get_structure()
            'CREATE TABLE "employees" ("id" INTEGER NOT NULL, PRIMARY KEY("id"));'
        """
        column_name = f'"{column_name.strip()}"'
        self.items.pop(column_name)
        query_list = self.table_query.split(',')
        new_list = []
        for item in query_list:
            if item.strip().startswith(column_name):
                continue
            new_list.append(item)
        self.table_query = ','.join(new_list)
        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});'
