"""You are an expert assistant specialized in the Ormophine SQLite Python ORM.
The text below this line is the COMPLETE source code of the Ormophine SQLite 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:
    """
    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 builder for SQL expressions involving columns and literals.

    Instances of this class represent a SQL expression (e.g., arithmetic
    operations, string concatenations, function calls, or comparison
    conditions) that can be used in ``SELECT``, ``WHERE``, ``UPDATE``,
    or other clauses. The class overloads Python operators (``+``, ``-``,
    ``*``, ``**``, ``/``, ``%``, ``&``, ``|``, comparison operators, and
    subscription) to generate corresponding SQL syntax.

    The expression state is stored in the ``_output`` attribute as a tuple
    ``(sql_string, parameters)``, where ``sql_string`` is the raw SQL
    fragment (with placeholders ``?``) and ``parameters`` is a list of
    parameter values for safe query execution. Most methods mutate the
    instance in‑place and return ``self``, enabling method chaining.

    Typical usage starts from a :class:`Column` instance, which creates a
    new ``ColumnsOperation`` object. Operations can then be chained:

    .. code-block:: python

        name = users.name
        expr = (name + ' (active)').upper().startswith('A')
        # expr._output[0] -> "(upper((users.[name] || ?)) like ? || '%')"
        # expr._output[1] -> [' (active)', 'A']

    The resulting expression can be passed to methods like
    :meth:`Table.get_row` or :meth:`Table.update` as a condition or
    computed value.

    Attributes:
        col_obj (Column): The underlying :class:`Column` object that this
            operation is derived from. This is used to determine datatype
            (e.g., whether ``+`` should be string concatenation or
            arithmetic addition) and to provide context.
        _output (tuple[str, list]): A 2‑tuple containing the SQL string
            with placeholders and the list of parameter values. This is
            the internal representation of the expression.
    """

    def __init__(self, col_obj):
        """Initialize a new ColumnsOperation instance.

        This class is used to build SQL expressions involving column operations
        (arithmetic, string concatenation, function calls, comparisons, etc.)
        in a chainable manner. It is typically created indirectly via the
        :class:`Column` class's operator overloads (e.g., ``col + 5``, ``col * 2``)
        or by calling methods like :meth:`Column.upper()`.

        The instance stores the generated SQL expression and its parameter list
        in the ``_output`` attribute as a tuple ``(sql_string, param_list)``.
        Initially, before any operation is applied, ``_output`` is set to an
        empty string, but most methods will overwrite it.

        Args:
            col_obj (Column): The column object that this operation is associated
                with. This is used to determine the column's data type (which
                influences whether ``+`` is mapped to SQL ``+`` or ``||``) and
                to provide a fallback column name when no operation has been
                applied yet.

        Example:
            This class is not meant to be instantiated directly. Instead, use
            column operators::

                from ormophine.Sqlite import Driver, Table

                db = Driver('my.db')
                users = db.users
                name_col = users.name

                # This creates a ColumnsOperation internally:
                expr = name_col.upper().startswith('A')
                # expr._output -> ("upper(users.[name]) like ? || '%'", ['A'])

            In the example above, ``name_col.upper()`` returns a
            :class:`ColumnsOperation` object, and subsequent chained calls
            modify its ``_output`` accordingly.
        """        
        self._output = '' # To apply operations in a chained manner
        self.col_obj = col_obj

    def __add__(self, other):
        """Add a value or expression to the current column expression.

        This method implements the ``+`` operator for :class:`ColumnsOperation`.
        The behavior depends on the column's datatype:

        * If the column's datatype is ``str``, SQL string concatenation is used (``||``).
        * Otherwise, arithmetic addition (``+``) is used.

        The method supports adding:

        * Another :class:`ColumnsOperation` – both expressions are combined.
        * A :class:`Column` – the column's name is used as the right operand.
        * An ``int`` or ``float`` literal – a parameter placeholder (``?``) is used.
        * A ``str`` literal – a parameter placeholder is used for string concatenation.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The value or expression to add. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``
                - ``str``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the addition. This allows method chaining.

        Example:
            Assuming a ``products`` table with columns ``price`` (``REAL``) and
            ``name`` (``TEXT``)::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price
                name = products.name

                # Arithmetic addition for numeric column
                expr1 = price + 10
                # expr1._output[0] -> "(products.[price] + ?)"
                # expr1._output[1] -> [10]

                # String concatenation for text column
                expr2 = name + ' (discounted)'
                # expr2._output[0] -> "(products.[name] || ?)"
                # expr2._output[1] -> [' (discounted)']

                # Combining two expressions
                expr3 = (price + 5) + (price * 2)
                # expr3._output[0] -> "((products.[price] + ?) + (products.[price] * ?))"
                # expr3._output[1] -> [5, 2]
        """
        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]} + ?)', self._output[1]+[other]) if isinstance(other, int) or isinstance(other , float) else (f'({self._output[0]} || ?)', self._output[1]+[other if isinstance(other, str) else str(other)])
        return self

    def __radd__(self, other):
        """Implement reflected addition for SQL expression generation.

        This method is invoked when the left operand does not support addition
        with a :class:`ColumnsOperation` object, i.e., when evaluating
        ``other + self``. It builds a SQL expression representing the addition
        or concatenation of the right-hand side (this operation) with the
        left-hand side operand.

        The SQL operator used depends on the data type of the associated column:

        * For string columns (:attr:`~Column.datatype` is ``str``), the
        concatenation operator ``||`` is used.
        * For numeric columns (e.g., ``int``, ``float``), the arithmetic
        addition operator ``+`` is used.

        The method supports multiple types for ``other``:

        * A :class:`ColumnsOperation` – the SQL expression from that object is
        combined with this one.
        * A :class:`Column` – the column name is used as the left operand.
        * A literal (``int``, ``float``, ``str``) – the literal is used as a
        parameterised value (``?``) in the final query.

        The method updates the internal ``_output`` attribute of the current
        instance to hold the resulting SQL fragment and its parameter list,
        then returns the instance for chaining.

        Args:
            other: The left-hand operand. Can be one of:
                - :class:`ColumnsOperation`: a pre‑built expression.
                - :class:`Column`: a database column.
                - A literal of type ``int``, ``float``, or ``str``.

        Returns:
            :class:`ColumnsOperation`: The same instance (``self``), with its
            ``_output`` updated to represent the SQL expression
            ``(other + self)`` or ``(other || self)``.

        Example:
            Assuming a ``Product`` table with columns ``name`` (string) and
            ``price`` (numeric)::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                name_col = products.name
                price_col = products.price

                # Right addition with a literal (triggers __radd__)
                expr1 = 'Mr. ' + name_col
                # expr1._output[0] -> "(? || products.[name])"
                # expr1._output[1] -> ['Mr. ']

                # Numeric right addition
                expr2 = 100 + price_col
                # expr2._output[0] -> "(? + products.[price])"
                # expr2._output[1] -> [100]

                # Right addition with another ColumnsOperation
                expr3 = (price_col * 2) + name_col
                # expr3._output[0] -> "((products.[price] * ?) + products.[name])"
        """
        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'(? + {self._output[0]})', [other]+self._output[1]) if isinstance(other, int) or isinstance(other , float) else (f'(? || {self._output[0]})', [other if isinstance(other, str) else str(other)]+self._output[1])
        return self

    def __sub__(self, other):
        """Subtract a value or expression from the current column expression.

        This method implements the ``-`` operator for :class:`ColumnsOperation`,
        generating a SQL subtraction expression. The method supports subtracting:

        * Another :class:`ColumnsOperation` – both expressions are subtracted.
        * A :class:`Column` – the column's name is used as the right operand.
        * A literal (``int``, ``float``, etc.) – a parameter placeholder (``?``)
        is used.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The value or expression to subtract. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int``, ``float``, or any numeric literal

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the subtraction. This allows method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price
                discount = products.discount

                # Subtract a literal
                expr1 = price - 10
                # expr1._output[0] -> "(products.[price] - ?)"
                # expr1._output[1] -> [10]

                # Subtract another column
                expr2 = price - discount
                # expr2._output[0] -> "(products.[price] - products.[discount])"

                # Combine with other expressions
                expr3 = (price - 5) - (discount * 2)
                # expr3._output[0] -> "((products.[price] - ?) - (products.[discount] * ?))"
                # expr3._output[1] -> [5, 2]
        """
        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]} - ?)', self._output[1]+[other])
        return self

    def __rsub__(self, other):
        """Implement reverse subtraction for the column expression.

        This method is called when the left operand does not support subtraction
        (e.g., ``int - Column``). It generates a SQL subtraction expression
        where the other value is subtracted from the current column expression.
        The operator used is always ``-``, regardless of the column's datatype
        (subtraction is only meaningful for numeric types).

        The method supports:

        * Another :class:`ColumnsOperation` – the other expression is the left
        operand, and the current expression is the right operand.
        * A :class:`Column` – the other column's value is subtracted from the
        current expression.
        * A literal (``int``, ``float``, etc.) – the literal is used as a
        parameter placeholder.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The value or expression to subtract from. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int``, ``float``, or other numeric literal

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the reverse subtraction. This allows method
            chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Reverse subtraction: 100 - price
                expr = 100 - price
                # expr._output[0] -> "(? - products.[price])"
                # expr._output[1] -> [100]

                # Using in a query
                condition = (100 - price) > 50
                # condition._output[0] -> "((? - products.[price]) > ?)"
                # condition._output[1] -> [100, 50]
        """
        self._output = (f'({other._output[0]} - {self._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} - {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(? - {self._output[0]})', self._output[1]+[other])
        return self

    def __mul__(self, other):
        """Multiply the current column expression by a value or expression.

        This method implements the ``*`` operator for :class:`ColumnsOperation`.
        It generates a SQL multiplication expression using the ``*`` operator.
        The method supports:

        * Another :class:`ColumnsOperation` – both expressions are multiplied.
        * A :class:`Column` – the column's name is used as the right operand.
        * A numeric literal (``int`` or ``float``) – a parameter placeholder
        (``?``) is used for the literal.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The value or expression to multiply by. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the multiplication. This allows method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column (``REAL``)::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Multiply by a literal
                expr1 = price * 1.2
                # expr1._output[0] -> "(products.[price] * ?)"
                # expr1._output[1] -> [1.2]

                # Multiply by another column
                tax_rate = products.tax_rate
                expr2 = price * tax_rate
                # expr2._output[0] -> "(products.[price] * products.[tax_rate])"

                # Combine with other operations
                expr3 = (price + 10) * 0.9
                # expr3._output[0] -> "((products.[price] + ?) * ?)"
                # expr3._output[1] -> [10, 0.9]
        """
        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]} * ?)', self._output[1]+[other])
        return self

    def __rmul__(self, other):
        """Implement reflected multiplication for the column expression.

        This method handles the case where a numeric value or another expression
        appears on the left side of the ``*`` operator and the
        :class:`ColumnsOperation` appears on the right (e.g., ``3 * expr``).
        It generates a SQL multiplication expression (``*``) with the operands
        in the correct order.

        The method supports:

        * Another :class:`ColumnsOperation` – both expressions are multiplied.
        * A :class:`Column` – the column's name is used as the left operand.
        * An ``int`` or ``float`` literal – a parameter placeholder (``?``) is used.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The value or expression to multiply by. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the reflected multiplication. This allows
            method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Reflected multiplication: numeric literal on the left
                expr1 = 3 * price
                # expr1._output[0] -> "(? * products.[price])"
                # expr1._output[1] -> [3]

                # Reflected multiplication with another expression
                expr2 = (price + 5) * (price * 2)
                # expr2._output[0] -> "((products.[price] + ?) * (products.[price] * ?))"
                # expr2._output[1] -> [5, 2]

                # Use in a query to calculate discounted price
                discounted = 0.9 * price
                # discounted._output[0] -> "(? * products.[price])"
                # discounted._output[1] -> [0.9]
        """
        self._output = (f'({other._output[0]} * {self._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} * {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(? * {self._output[0]})', self._output[1]+[other])
        return self

    def __pow__(self, other):
        """Raise the column expression to a power using SQL exponentiation.

        This method implements the ``**`` operator for :class:`ColumnsOperation`.
        It generates a SQL expression using the exponentiation operator ``**``,
        which is supported by SQLite (and other databases) for numeric exponentiation.

        The method supports three types of operands:

        * Another :class:`ColumnsOperation` – both expressions are combined with ``**``.
        * A :class:`Column` – the column's name is used as the exponent.
        * A literal (``int`` or ``float``) – a parameter placeholder (``?``) is used.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The exponent value or expression. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the exponentiation. This allows method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Square the price
                expr1 = price ** 2
                # expr1._output[0] -> "(products.[price] ** ?)"
                # expr1._output[1] -> [2]

                # Use another column as exponent
                exponent_col = products.discount
                expr2 = price ** exponent_col
                # expr2._output[0] -> "(products.[price] ** products.[discount])"

                # Combine two expressions
                expr3 = (price + 5) ** (price * 2)
                # expr3._output[0] -> "((products.[price] + ?) ** (products.[price] * ?))"
                # expr3._output[1] -> [5, 2]
        """
        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]} ** ?)', self._output[1]+[other])
        return self

    def __rpow__(self, other):
        """Implement the reflected (right‑hand side) power operator for column expressions.

        This method handles the case where a value or expression appears on the left
        of the ``**`` operator and the current :class:`ColumnsOperation` is on the right.
        It generates a SQL exponentiation expression of the form ``left ** right``,
        where ``right`` is this column expression. The result updates the instance's
        ``_output`` attribute and returns ``self`` to support chaining.

        The method supports three types of ``other``:

        * A :class:`ColumnsOperation` – both expressions are combined, and their
        parameter lists are merged.
        * A :class:`Column` – the column's name is used as the left operand.
        * A literal (e.g., ``int``, ``float``) – the literal is bound as a parameter
        using a placeholder (``?``) in the SQL string.

        Args:
            other: The left operand of the power operation. Can be one of:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - A numeric literal (``int`` or ``float``)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent ``other ** self``. The ``_output`` attribute is
            a tuple ``(sql_string, parameters)``.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Using a literal on the left
                expr = 2 ** price
                # expr._output[0] -> "(? ** products.[price])"
                # expr._output[1] -> [2]

                # Using another column
                discount = products.discount
                expr2 = discount ** price
                # expr2._output[0] -> "(products.[discount] ** products.[price])"

                # Using a complex expression on the left
                expr3 = (price + 10) ** price
                # expr3._output[0] -> "((products.[price] + ?) ** products.[price])"
        """
        self._output = (f'({other._output[0]} ** {self._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} ** {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(? ** {self._output[0]})', self._output[1]+[other])
        return self

    def __truediv__(self, other):
        """Divide the column expression by a value or expression.

        This method implements the ``/`` (division) operator for
        :class:`ColumnsOperation`. It generates a SQL division expression
        and supports three types of operands:

        * Another :class:`ColumnsOperation` – both expressions are divided.
        * A :class:`Column` – the column's name is used as the divisor.
        * A numeric literal (``int`` or ``float``) – a parameter placeholder (``?``) is used.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The divisor value or expression. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the division. This allows method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Divide price by 2
                expr1 = price / 2
                # expr1._output[0] -> "(products.[price] / ?)"
                # expr1._output[1] -> [2]

                # Divide by another column
                divisor_col = products.discount
                expr2 = price / divisor_col
                # expr2._output[0] -> "(products.[price] / products.[discount])"

                # Divide two composite expressions
                expr3 = (price + 10) / (price * 2)
                # expr3._output[0] -> "((products.[price] + ?) / (products.[price] * ?))"
                # expr3._output[1] -> [10, 2]
        """
        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]} / ?)', self._output[1]+[other])
        return self

    def __rtruediv__(self, other):
        """Divide a value by the column expression (reverse division).

        This method implements the reflected (right-hand) division operator for
        :class:`ColumnsOperation`. It is called when a numeric value or column
        appears on the left side of the division operator and the
        :class:`ColumnsOperation` appears on the right (e.g., ``100 / price_expr``).
        The generated SQL uses the division operator ``/``.

        The method supports three types of operands for the left-hand side:

        * Another :class:`ColumnsOperation` – both expressions are combined with ``/``.
        * A :class:`Column` – the column's name is used as the dividend.
        * A literal (``int`` or ``float``) – a parameter placeholder (``?``) is used.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The left-hand side value or expression. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the reverse division. This allows method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price_expr = products.price + 10

                # Reverse division: 100 / (price + 10)
                expr = 100 / price_expr
                # expr._output[0] -> "(? / (products.[price] + ?))"
                # expr._output[1] -> [100, 10]

                # Using another column as the dividend
                discount_col = products.discount
                expr2 = discount_col / price_expr
                # expr2._output[0] -> "(products.[discount] / (products.[price] + ?))"
                # expr2._output[1] -> [10]
        """
        self._output = (f'({other._output[0]} / {self._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} / {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(? / {self._output[0]})', self._output[1]+[other])
        return self

    def __mod__(self, other):
        """Apply the modulo operator to the column expression.

        This method implements the ``%`` operator for :class:`ColumnsOperation`.
        It generates a SQL expression using the modulo operator ``%``, which
        computes the remainder of division between the current expression and
        the provided operand.

        The method supports three types of operands:

        * Another :class:`ColumnsOperation` – both expressions are combined with ``%``.
        * A :class:`Column` – the column's name is used as the divisor.
        * A literal (``int`` or ``float``) – a parameter placeholder (``?``) is used.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow
        chaining.

        Args:
            other: The divisor value or expression. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the modulo operation. This allows method chaining.

        Example:
            Assuming a ``products`` table with a ``stock`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                stock = products.stock

                # Check if stock is odd (stock % 2)
                expr = stock % 2
                # expr._output[0] -> "(products.[stock] % ?)"
                # expr._output[1] -> [2]

                # Use another column as divisor
                divisor_col = products.divisor
                expr2 = stock % divisor_col
                # expr2._output[0] -> "(products.[stock] % products.[divisor])"

                # Combine two expressions
                expr3 = (stock + 10) % (stock - 5)
                # expr3._output[0] -> "((products.[stock] + ?) % (products.[stock] - ?))"
                # expr3._output[1] -> [10, 5]
        """
        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]} % ?)', self._output[1]+[other])
        return self

    def __rmod__(self, other):
        """Compute the modulo of a value with the column expression (reverse modulo).

        This method implements the reverse ``%`` operator for :class:`ColumnsOperation`.
        It is called when the left operand is not a :class:`ColumnsOperation` (e.g.,
        a literal or another :class:`Column`) and the right operand is this
        expression. The generated SQL uses the modulo operator ``%``.

        The method supports:
        * Another :class:`ColumnsOperation` – the expressions are combined with ``%``
        (left expression modulo right expression).
        * A :class:`Column` – the column's name is used as the left operand.
        * A literal (``int`` or ``float``) – a parameter placeholder (``?``) is used.

        The result is stored in the instance's ``_output`` attribute as a tuple
        ``(sql_string, parameters)``, and the instance is returned to allow chaining.

        Args:
            other: The left operand (value or expression) to be divided by this
                expression. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - ``int`` or ``float``

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the reverse modulo operation. This allows method
            chaining.

        Example:
            Assuming a ``products`` table with a ``stock`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                stock = products.stock

                # Reverse modulo: 100 % stock
                expr = 100 % stock
                # expr._output[0] -> "(? % products.[stock])"
                # expr._output[1] -> [100]

                # Use another column as left operand
                total_col = products.total
                expr2 = total_col % stock
                # expr2._output[0] -> "(products.[total] % products.[stock])"
        """
        self._output = (f'({other._output[0]} % {self._output[0]})', self._output[1] + other._output[1]) if isinstance(other, ColumnsOperation) else (f'({other.name} % {self._output[0]})', self._output[1]) if isinstance(other, Column) else (f'(? % {self._output[0]})', self._output[1]+[other])
        return self

    def __getitem__(self, key: slice):
        """Implement string slicing on a column expression using SQLite's ``substr``.

        This method allows Python-style slicing (e.g., ``column[1:5]``) on a
        :class:`ColumnsOperation` object. It generates a SQL ``substr`` expression
        that extracts a substring from the column value or from a previously
        constructed expression. The behavior mimics Python string slicing with
        support for positive/negative indices and omitted start/stop values.

        The generated SQL uses SQLite's ``substr(X, Y, Z)`` function, where:
        - The start position is adjusted for 1‑based indexing.
        - Negative indices are converted to ``length(X) - N``.
        - An omitted start defaults to 0 (beginning).
        - An omitted stop defaults to the end of the string.

        The method updates the instance's ``_output`` attribute with a tuple
        ``(sql_string, parameters)`` and returns the instance itself for chaining.

        Args:
            key (slice): A slice object defining the substring range. The
                ``start`` and ``stop`` attributes can be ``None``, positive,
                or negative integers. Negative values count from the end of
                the string.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``substr`` expression. This allows
            additional operations to be chained.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Extract first 3 characters (Python slice 0:3)
                expr = name_col[:3]
                # expr._output[0] -> "substr(users.[name] , 0 , ?)"
                # expr._output[1] -> [3]  # note: SQLite's substr is 1-based,
                # but this ORM adjusts: stop+1 is used, so for 0:3, we get substr(..., 0, 3)
                # Actually this ORM uses 0-based start with substr, so it's fine.

                # Extract from index 1 to 4 (Python slice 1:5)
                expr2 = name_col[1:5]
                # expr2._output[0] -> "substr(users.[name] , ? , ?)"
                # expr2._output[1] -> [2, 4]  # start adjusted to 1-based: 1+1=2, length = 5-1=4

                # Extract last 3 characters (Python slice -3:)
                expr3 = name_col[-3:]
                # expr3._output[0] -> "substr(users.[name] , length(users.[name]) - ? , length(users.[name]))"
                # expr3._output[1] -> [2]  # -3 becomes abs(-3)-1 = 2

                # Use in a query to get initials (first character)
                initial_expr = name_col[0:1]
                results = users.get_row([initial_expr], where=users.id == 1)
                # retrieves the first character of the name for user with id=1
        """
        if self._output:
            if key.start == None and key.stop ==  None:
                self._output = (f'substr({self._output[0]} , 0 , length({self._output[0]}) + 1)', self._output[1] + self._output[1])   #
            elif key.start == None and key.stop < 0:
                self._output = (f'substr({self._output[0]} , 0 , length({self._output[0]}) - ?)', self._output[1] + self._output[1] + [abs(key.stop) - 1])  #
            elif key.start == None and key.stop >= 0:
                 self._output = (f'substr({self._output[0]} , 0 , ?)', self._output[1] + [key.stop + 1])  #  
            elif key.start >= 0 and key.stop ==  None:
                self._output = (f'substr({self._output[0]} , ? , length({self._output[0]}))', self._output[1] + [key.start + 1] + self._output[1])  #   
            elif key.start < 0 and key.stop == None:
                self._output = (f'substr({self._output[0]} , length({self._output[0]}) - ? , 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'substr({self._output[0]} , ? , length({self._output[0]}) - ?)', 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'substr({self._output[0]} , ? , ?)', self._output[1] + [key.start + 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop < 0:
                self._output = (f'substr({self._output[0]} , length({self._output[0]}) - ? , ?)', 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'substr({self._output[0]} , length({self._output[0]}) - ? ,  ? - (length({self._output[0]}) - ?))', 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'substr({self.col_obj.name} , 0 , length({self.col_obj.name}) + 1)', [])   #
            elif key.start == None and key.stop < 0:
                self._output = (f'substr({self.col_obj.name} , 0 , length({self.col_obj.name}) - ?)', [abs(key.stop) - 1])  #
            elif key.start == None and key.stop >= 0:
                 self._output = (f'substr({self.col_obj.name} , 0 , ?)', [key.stop + 1])  #  
            elif key.start >= 0 and key.stop ==  None:
                self._output = (f'substr({self.col_obj.name} , ? , length({self.col_obj.name}))', [key.start + 1])  #   
            elif key.start < 0 and key.stop == None:
                self._output = (f'substr({self.col_obj.name} , length({self.col_obj.name}) - ? , length({self.col_obj.name}))', [abs(key.start) - 1])  #
            elif key.start >= 0 and key.stop < 0:
                self._output = (f'substr({self.col_obj.name} , ? , length({self.col_obj.name}) - ?)', [key.start + 1, abs(key.stop - key.start)])  #  
            elif key.start >= 0 and key.stop > 0:
                self._output = (f'substr({self.col_obj.name} , ? , ?)', [key.start + 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop < 0:
                self._output = (f'substr({self.col_obj.name} , length({self.col_obj.name}) - ? , ?)', [abs(key.start) - 1, key.stop - key.start])  #
            elif key.start < 0 and key.stop > 0:
                self._output = (f'substr({self.col_obj.name} , length({self.col_obj.name}) - ? ,  ? - (length({self.col_obj.name}) - ?))', [abs(key.start) - 1, key.stop, abs(key.start)])
        return self

    def eq(self, value):
        """Create an equality comparison condition for the column expression.

        This method generates a SQL equality expression (``=``) between the current
        expression and the provided value. The result is a :class:`ColumnsOperation`
        object that can be used in ``WHERE`` clauses of queries, updates, or deletes.
        This is the named version of the ``__eq__`` operator, allowing explicit
        usage when operator overloading is not desirable.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the equality condition. This allows method chaining
            with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with columns ``id`` and ``name``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Use eq to compare with a literal
                condition = name_col.eq('Alice')
                # condition._output[0] -> "(users.[name] = ?)"
                # condition._output[1] -> ['Alice']

                # Compare two columns
                id_col = users.id
                condition2 = name_col.eq(id_col)
                # condition2._output[0] -> "(users.[name] = users.[id])"

                # Chain with AND
                final_condition = condition & (id_col > 10)
        """
        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]} = ?', self._output[1] + [value])
        return self

    def __eq__(self, value):
        """Create an equality comparison condition for the column expression.

        This method implements the ``==`` operator for :class:`ColumnsOperation`.
        It generates a SQL equality expression (``=``) between the current
        expression and the provided value. The result is stored in the instance's
        ``_output`` attribute as a tuple ``(sql_string, parameters)``, and the
        instance is returned to allow chaining.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the equality condition. This allows method chaining
            with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Compare with a literal using ==
                condition = name_col == 'Alice'
                # condition._output[0] -> "(users.[name] = ?)"
                # condition._output[1] -> ['Alice']

                # Compare two columns
                id_col = users.id
                condition2 = name_col == id_col
                # condition2._output[0] -> "(users.[name] = users.[id])"

                # Compare with a computed expression
                expr = name_col.upper()
                condition3 = expr == 'ALICE'
                # condition3._output[0] -> "(upper(users.[name]) = ?)"
                # condition3._output[1] -> ['ALICE']
        """
        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]} = ?', self._output[1] + [value])
        return self

    def ne(self, value):
        """Create a not-equal comparison condition for the column expression.

        This method generates a SQL inequality expression (``!=``) between the
        current expression and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes. This is the named version of the
        ``__ne__`` operator, allowing explicit usage when operator overloading
        is not desirable.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions with ``!=``.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the inequality condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Use ne to exclude a specific name
                condition = name_col.ne('Admin')
                # condition._output[0] -> "(users.[name] != ?)"
                # condition._output[1] -> ['Admin']

                # Compare two columns
                id_col = users.id
                condition2 = name_col.ne(id_col)
                # condition2._output[0] -> "(users.[name] != users.[id])"

                # Chain with AND
                final_condition = condition & (users.age > 18)
        """
        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]} != ?', self._output[1] + [value])
        return self

    def __ne__(self, value):
        """Create a not-equal comparison condition for the column expression.

        This method implements the ``!=`` operator for :class:`ColumnsOperation`.
        It generates a SQL inequality expression (``!=``) between the current
        expression and the provided value. The result is a :class:`ColumnsOperation`
        object that can be used in ``WHERE`` clauses of queries, updates, or deletes.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions with ``!=``.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the inequality condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Use != with a literal
                condition = price != 100
                # condition._output[0] -> "(products.[price] != ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount = products.discount
                condition2 = price != discount
                # condition2._output[0] -> "(products.[price] != products.[discount])"

                # Use in a query
                results = products.get_row([price], where=condition)
                # retrieves rows where price != 100
        """
        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]} != ?', self._output[1] + [value])
        return self

    def gt(self, value):
        """Create a greater-than comparison condition for the column expression.

        This method generates a SQL ``>`` (greater than) expression between the
        current expression and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes. This is the named version of the
        ``__gt__`` operator, allowing explicit usage when operator overloading
        is not desirable.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the greater-than condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price_col = products.price

                # Use gt to compare with a literal
                condition = price_col.gt(100)
                # condition._output[0] -> "(products.[price] > ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount_col = products.discount
                condition2 = price_col.gt(discount_col)
                # condition2._output[0] -> "(products.[price] > products.[discount])"

                # Chain with AND
                final_condition = condition & (price_col < 500)
        """
        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]} > ?', self._output[1] + [value])
        return self

    def __gt__(self, value):
        """Create a greater-than comparison condition for the column expression.

        This method implements the ``>`` operator for :class:`ColumnsOperation`.
        It generates a SQL expression using the greater-than operator (``>``) between
        the current expression and the provided value. The result is stored in the
        instance's ``_output`` attribute and can be used in ``WHERE`` clauses.

        The method supports comparisons with:
        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (``int``, ``str``, ``float``, etc.) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``>`` condition, allowing method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Compare price greater than 100
                condition = price > 100
                # condition._output[0] -> "(products.[price] > ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount = products.discount
                condition2 = price > discount
                # condition2._output[0] -> "(products.[price] > products.[discount])"

                # Use in a query
                results = products.get_row([price], where=condition)
        """
        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]} > ?', self._output[1] + [value])
        return self

    def lt(self, value):
        """Create a less-than comparison condition for the column expression.

        This method generates a SQL ``<`` (less than) expression between the
        current expression and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes. This is the named version of the
        ``__lt__`` operator, allowing explicit usage when operator overloading
        is not desirable.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the less-than condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Find products with price less than 100
                condition = price.lt(100)
                # condition._output[0] -> "(products.[price] < ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount = products.discount
                condition2 = price.lt(discount)
                # condition2._output[0] -> "(products.[price] < products.[discount])"

                # Use in a query
                results = products.get_row([price], where=condition)
        """
        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]} < ?', self._output[1] + [value])
        return self

    def __lt__(self, value):
        """Create a less-than comparison condition for the column expression.

        This method implements the ``<`` operator for :class:`ColumnsOperation`.
        It generates a SQL ``<`` (less than) expression between the current
        expression and the provided value. The result is stored in the instance's
        ``_output`` attribute and the instance is returned, allowing method
        chaining.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the less-than condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Find products with price less than 100
                condition = price < 100
                # condition._output[0] -> "(products.[price] < ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount = products.discount
                condition2 = price < discount
                # condition2._output[0] -> "(products.[price] < products.[discount])"

                # Use in a query
                results = products.get_row([price], where=condition)
        """
        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]} < ?', self._output[1] + [value])
        return self

    def ge(self, value):
        """Create a greater-than-or-equal comparison condition for the column expression.

        This method generates a SQL ``>=`` (greater than or equal) expression
        between the current expression and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes. This is the named version of the
        ``__ge__`` operator, allowing explicit usage when operator overloading
        is not desirable.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the greater-than-or-equal condition. This allows
            method chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Find products with price >= 100
                condition = price.ge(100)
                # condition._output[0] -> "(products.[price] >= ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount = products.discount
                condition2 = price.ge(discount)
                # condition2._output[0] -> "(products.[price] >= products.[discount])"

                # Use in a query
                results = products.get_row([price], where=condition)
        """
        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]} >= ?', self._output[1] + [value])
        return self

    def __ge__(self, value):
        """Create a greater-than-or-equal-to comparison condition for the column expression.

        This method implements the ``>=`` operator for :class:`ColumnsOperation`.
        It generates a SQL ``>=`` expression between the current expression and
        the provided value. The result is a :class:`ColumnsOperation` object that
        can be used in ``WHERE`` clauses of queries, updates, or deletes.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        The instance's ``_output`` attribute is updated with the SQL string and
        parameter list, and the instance itself is returned to allow chaining
        of multiple conditions via logical operators (``&`` and ``|``).

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``>=`` condition. This allows method chaining.

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Find products with price >= 100
                condition = price.__ge__(100)
                # Alternatively: price >= 100
                # condition._output[0] -> "(products.[price] >= ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount = products.discount
                condition2 = price.__ge__(discount)
                # condition2._output[0] -> "(products.[price] >= products.[discount])"

                # Use in a query
                results = products.get_row([price], where=condition)
        """
        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]} >= ?', self._output[1] + [value])
        return self

    def le(self, value):
        """Create a less-than-or-equal-to comparison condition for the column expression.

        This method generates a SQL ``<=`` (less than or equal) expression between
        the current expression and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes. This is the named version of the
        ``__le__`` operator, allowing explicit usage when operator overloading
        is not desirable.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the less-than-or-equal condition. This allows
            method chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming an ``orders`` table with a ``total`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                orders = db.orders
                total = orders.total

                # Find orders with total <= 100.0
                condition = total.le(100.0)
                # condition._output[0] -> "(orders.[total] <= ?)"
                # condition._output[1] -> [100.0]

                # Compare two columns
                discount = orders.discount
                condition2 = total.le(discount)
                # condition2._output[0] -> "(orders.[total] <= orders.[discount])"

                # Use with AND
                final_condition = condition & (orders.id > 10)
        """
        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]} <= ?', self._output[1] + [value])
        return self

    def __le__(self, value):
        """Create a less-than-or-equal comparison condition for the column expression.

        This method implements the ``<=`` operator for :class:`ColumnsOperation`.
        It generates a SQL ``<=`` (less than or equal) expression between the
        current expression and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes.

        The method supports comparisons with:

        * Another :class:`ColumnsOperation` – combines two expressions.
        * A :class:`Column` – compares the expression to a column.
        * A literal (e.g., ``int``, ``str``, ``float``) – uses a parameter placeholder.

        Args:
            value: The value or expression to compare against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (``int``, ``str``, ``float``, etc.)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the less-than-or-equal condition. This allows
            method chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Find products with price <= 100
                condition = price <= 100
                # condition._output[0] -> "(products.[price] <= ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount = products.discount
                condition2 = price <= discount
                # condition2._output[0] -> "(products.[price] <= products.[discount])"

                # Use in a query
                results = products.get_row([price], where=condition)
        """
        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]} <= ?', self._output[1] + [value])
        return self

    def __and__(self, value):
        """Combine two conditions with SQL ``AND``.

        This method implements the ``&`` operator for :class:`ColumnsOperation`.
        It generates a SQL ``AND`` expression combining the current condition
        with another condition. The result is a new condition that can be used
        in ``WHERE`` clauses of queries, updates, or deletes.

        The method expects both operands to be :class:`ColumnsOperation` instances.
        It combines their SQL strings and parameter lists into a single expression.

        Args:
            value (ColumnsOperation): Another condition expression to combine
                with the current one using ``AND``.

        Returns:
            ColumnsOperation: The current instance with its ``_output`` updated
            to represent the combined condition. This allows method chaining.

        Example:
            Assuming a ``users`` table with columns ``age`` and ``active``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                age = users.age
                active = users.active

                # Build a compound condition
                condition = (age > 18) & (active == 1)
                # condition._output[0] -> "((users.[age] > ?) AND (users.[active] = ?))"
                # condition._output[1] -> [18, 1]

                # Use in a query
                results = users.get_row([users.name], where=condition)
                # retrieves names of active users older than 18
        """
        self._output = (f'({self._output[0]} AND {value._output[0]})', self._output[1] + value._output[1])
        return self

    def __or__(self, value):
        """Combine this condition with another using SQL ``OR``.

        This method implements the bitwise OR operator (``|``) for
        :class:`ColumnsOperation` objects. It generates a SQL expression
        that combines two conditions with the ``OR`` logical operator,
        producing a new condition that is true if either subcondition is true.

        The ``value`` must be another :class:`ColumnsOperation` object
        (e.g., a comparison condition created by ``==``, ``>``, ``&``, etc.).
        The result is stored in the instance's ``_output`` attribute as
        a tuple ``(sql_string, parameters)``, and the instance is returned
        to allow chaining with other conditions.

        Args:
            value (ColumnsOperation): Another condition object to combine
                with ``OR``.

        Returns:
            ColumnsOperation: The current instance with its ``_output``
            updated to represent the combined ``OR`` condition. This allows
            method chaining (e.g., ``(price > 100) | (price < 50)``).

        Example:
            Assuming a ``products`` table with a ``price`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price

                # Create two conditions
                cond1 = price > 100
                cond2 = price < 50

                # Combine with OR
                final_cond = cond1 | cond2
                # final_cond._output[0] -> "((products.[price] > ?) OR (products.[price] < ?))"
                # final_cond._output[1] -> [100, 50]

                # Use in a query
                results = products.get_row([price], where=final_cond)
                # retrieves rows where price > 100 OR price < 50
        """
        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`` condition for pattern matching on the column expression.

        This method generates a ``LIKE`` expression that compares the current
        expression to a pattern. The result is a :class:`ColumnsOperation` object
        that can be used in ``WHERE`` clauses of queries, updates, or deletes.

        The method supports three types of input:

        * Another :class:`ColumnsOperation` – the pattern is a computed expression.
        * A :class:`Column` – the pattern is the value of another column.
        * A literal (``str``, ``int``, etc.) – the pattern is the literal value,
        and a parameter placeholder (``?``) is used.

        The SQL ``LIKE`` operator supports wildcards: ``%`` matches any sequence
        of characters, and ``_`` matches a single character. For literal patterns
        with wildcards, you must include them in the string (e.g., ``'%John%'``).

        Args:
            value: The pattern to match against. Supported types:
                - :class:`ColumnsOperation`
                - :class:`Column`
                - Any literal (typically ``str``)

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``LIKE`` condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Find users whose names contain 'smith' (case-sensitive)
                condition = name_col.like('%smith%')
                # condition._output[0] -> "(users.[name] like ?)"
                # condition._output[1] -> ['%smith%']

                # Use with another column as pattern
                pattern_col = users.pattern
                condition2 = name_col.like(pattern_col)
                # condition2._output[0] -> "(users.[name] like users.[pattern])"

                # Use in a query
                results = users.get_row([name_col], where=condition)
        """
        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 ?', self._output[1] + [f'{value}'])
        return self

    def startswith(self, prefix):
        """Just like python startswith(), create a SQL ``LIKE`` condition to check if the expression starts with a prefix.

        This method generates a ``LIKE`` expression with the pattern
        ``prefix || '%'``, where ``prefix`` is the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes.

        The method supports three types of input:

        * A literal (e.g., ``str``, ``int``) – the literal is used as the prefix.
        * A :class:`Column` – the column's value is used as the prefix.
        * A :class:`ColumnsOperation` – the computed expression is used as the prefix.

        The generated SQL uses the ``||`` concatenation operator to append the
        wildcard ``%``.

        Args:
            prefix: The prefix to test against. Can be one of:
                - A literal (e.g., ``'John'``) – the expression value is compared
                to ``'John%'``.
                - A :class:`Column` – compares the expression to the concatenation
                of that column's value and ``'%'``.
                - A :class:`ColumnsOperation` – compares the expression to the
                concatenation of the expression's result and ``'%'``.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``LIKE`` condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Find users whose names start with 'Jo'
                condition = name_col.startswith('Jo')
                # condition._output[0] -> "(users.[name] like ? || '%')"
                # condition._output[1] -> ['Jo']

                # Use in a query
                results = users.get_row([name_col], where=condition)
                # retrieves rows where name LIKE 'Jo%'

                # Using another column as the prefix
                prefix_col = users.prefix
                condition2 = name_col.startswith(prefix_col)
                # condition2._output[0] -> "(users.[name] like users.[prefix] || '%')"
        """
        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 ?', self._output[1] + [f'{prefix}%'])
        return self

    def endswith(self, suffix):
        """Just like python endswith(), create a SQL ``LIKE`` condition to check if the column expression ends with a suffix.

        This method generates a ``LIKE`` expression with the pattern ``'%' || suffix``,
        where ``suffix`` is the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses.
        The method supports three types of input:

        * A literal (e.g., ``str``, ``int``) – the literal is used as the suffix.
        * A :class:`Column` – the column's value is used as the suffix.
        * A :class:`ColumnsOperation` – the computed expression is used as the suffix.

        The generated SQL uses the ``||`` concatenation operator to prepend the
        wildcard ``'%'``.

        Args:
            suffix: The suffix to test against. Can be one of:
                - A literal (e.g., ``'son'``) – the column value is compared
                to ``'%son'``.
                - A :class:`Column` – compares the column to the concatenation
                of ``'%'`` and that column's value.
                - A :class:`ColumnsOperation` – compares the column to the
                concatenation of ``'%'`` and the expression's result.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``LIKE`` condition. This object can be
            chained with other conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Find users whose names end with 'son'
                condition = name_col.endswith('son')
                # condition._output[0] -> "(users.[name] like '%' || ?)"
                # condition._output[1] -> ['son']

                # Use in a query
                results = users.get_row([name_col], where=condition)
                # retrieves rows where name LIKE '%son'

                # Using another column as the suffix
                suffix_col = users.suffix
                condition2 = name_col.endswith(suffix_col)
                # condition2._output[0] -> "(users.[name] like '%' || users.[suffix])"
        """
        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 ?', self._output[1] + [f'%{suffix}'])
        return self

    def contains(self, value):
        """Create a SQL ``LIKE`` condition to check if the column expression contains a substring.

        This method generates a ``LIKE`` expression with the pattern
        ``'%' || substring || '%'``, which tests whether the expression's value
        contains the given substring anywhere. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses.

        The method supports three types of input:

        * Another :class:`ColumnsOperation` – the substring is a computed expression.
        * A :class:`Column` – the substring is the value of another column.
        * A literal (e.g., ``str``, ``int``) – the substring is the literal value.

        The generated SQL uses the ``||`` concatenation operator to build the
        pattern with wildcards.

        Args:
            value: The substring to search for. Can be one of:
                - A literal (e.g., ``'abc'``) – the column value is checked
                for containment of ``'abc'``.
                - A :class:`Column` – the substring is taken from the column's value.
                - A :class:`ColumnsOperation` – the substring is the result of
                a computed expression.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the containment condition. This allows method
            chaining with other conditions via logical operators (``&``, ``|``).

        Example:
            Assuming a ``products`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                name = products.name

                # Find products whose name contains 'phone'
                condition = name.contains('phone')
                # condition._output[0] -> "(products.[name] like '%' || ? || '%')"
                # condition._output[1] -> ['phone']

                # Use another column as the substring
                keyword_col = products.search_term
                condition2 = name.contains(keyword_col)
                # condition2._output[0] -> "(products.[name] like '%' || products.[search_term] || '%')"

                # Use in a query
                results = products.get_row([name], where=condition)
        """
        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 ?', self._output[1] + [f'%{value}%'])
        return self

    def add_end(self, content):
        """Append content to the end of the column expression using SQL concatenation.

        This method generates a SQL expression that concatenates the current
        expression's value with the given ``content`` using the ``||`` operator,
        placing the content after the current value. The result is a
        :class:`ColumnsOperation` object that can be used in ``SELECT``,
        ``WHERE``, or other SQL clauses.

        The method supports three types of input:

        * Another :class:`ColumnsOperation` – concatenates the two expressions.
        * A :class:`Column` – uses the column's value as the content.
        * A literal (e.g., ``str``, ``int``) – uses the literal as the content.

        If the current expression is empty (i.e., the operation is called directly
        on a :class:`Column` without prior operations), the method uses the
        column's name as the base.

        Args:
            content: The content to append. Can be one of:
                - :class:`ColumnsOperation` – a computed expression.
                - :class:`Column` – a table column.
                - Any literal (``str``, ``int``, ``float``, etc.) – a static value.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the concatenation. This allows method chaining.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Append a literal suffix
                expr = name_col.add_end(' (active)')
                # expr._output[0] -> "(users.[name] || ?)"
                # expr._output[1] -> [' (active)']

                # Append another column's value
                suffix_col = users.status
                expr2 = name_col.add_end(suffix_col)
                # expr2._output[0] -> "(users.[name] || users.[status])"

                # Use in a query
                result = users.get_row([expr], where=users.id == 1)
                # retrieves the concatenated string for the user with id=1
        """
        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]} || ?)', self._output[1]+[content] if self._output else [content])
        return self

    def add_first(self, content):
        """Prepend content to the beginning of the column expression using SQL concatenation.

        This method generates a SQL expression that concatenates the given ``content``
        before the current expression's value using the ``||`` operator.
        The result is a :class:`ColumnsOperation` object that can be used in
        ``SELECT``, ``WHERE``, or other SQL clauses.

        The method supports three types of input:

        * Another :class:`ColumnsOperation` – concatenates the two expressions.
        * A :class:`Column` – uses the column's value as the prefix.
        * A literal (e.g., ``str``, ``int``) – uses the literal as the prefix.

        If the current expression is empty (i.e., the operation is called directly
        on a :class:`Column` without prior operations), the method uses the
        column's name as the base.

        Args:
            content: The content to prepend. Can be one of:
                - :class:`ColumnsOperation` – a computed expression.
                - :class:`Column` – a table column.
                - Any literal (``str``, ``int``, ``float``, etc.) – a static value.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the concatenation. This allows method chaining.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Prepend a literal prefix
                expr = name_col.add_first('Mr. ')
                # expr._output[0] -> "(? || users.[name])"
                # expr._output[1] -> ['Mr. ']

                # Prepend another column's value
                prefix_col = users.title
                expr2 = name_col.add_first(prefix_col)
                # expr2._output[0] -> "(users.[title] || users.[name])"

                # Use in a query
                result = users.get_row([expr], where=users.id == 1)
                # retrieves the concatenated string for the user with id=1
        """        
        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'(? || {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(), replace all occurrences of a substring within the column value.

        This method generates a SQL expression using the SQLite ``replace()``
        function, which returns the string with every occurrence of ``old``
        replaced by ``new``. The operation is applied to the current column
        expression (or the base column if no prior operations exist).

        The method modifies the instance's ``_output`` in place and returns
        ``self`` to support method chaining. The resulting ``_output`` is a tuple
        ``(sql_string, parameters)`` where the parameters include the ``old``
        and ``new`` strings as placeholders.

        Args:
            old (str): The substring to be replaced.
            new (str): The replacement string.

        Returns:
            ColumnsOperation: The current instance with its ``_output`` updated
            to represent the ``replace()`` SQL function call.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Replace 'John' with 'Jonathan'
                expr = name_col.replace('John', 'Jonathan')
                # expr._output[0] -> "replace(users.[name] , ? , ?)"
                # expr._output[1] -> ['John', 'Jonathan']

                # Use in a SELECT query
                result = users.get_row([expr], where=users.id == 1)
                # retrieves the name with replacements applied

                # Chain with other operations
                expr2 = name_col.upper().replace('A', 'X')
                # expr2._output[0] -> "replace(upper(users.[name]) , ? , ?)"
        """
        self._output = (f'replace({self._output[0]} , ? , ?)', self._output[1] + [old, new]) if self._output else (f'replace({self.col_obj.name} , ? , ?)', [old, new])
        return self

    def upper(self):
        """Just like python upper(), convert the column expression to uppercase using SQL's UPPER function.

        This method generates a SQL expression that wraps the current expression
        in the ``UPPER()`` function, which converts all characters to uppercase.
        The result is a :class:`ColumnsOperation` object that can be used in
        ``SELECT``, ``WHERE``, or other SQL clauses.

        The method updates the internal ``_output`` attribute to reflect the
        transformation and returns ``self`` to allow method chaining.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``UPPER()`` expression. This allows method
            chaining.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Convert name to uppercase in SQL
                expr = name_col.upper()
                # expr._output[0] -> "upper(users.[name])"
                # expr._output[1] -> []

                # Use in a query to retrieve uppercase names
                results = users.get_row([expr], where=users.id == 1)
                # retrieves the uppercase version of the user's name

                # Chain with other operations
                expr2 = name_col.upper().strip()
                # expr2._output[0] -> "trim(upper(users.[name]), ' ')"
        """
        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(), convert the column expression to lowercase using SQLite's `LOWER` function.

        This method generates a SQL expression that applies the ``LOWER()`` function
        to the current column expression. The result is a :class:`ColumnsOperation`
        object that can be used in ``SELECT``, ``WHERE``, or other SQL clauses.

        If the current expression is empty (i.e., the method is called directly on a
        :class:`Column` without prior operations), the method uses the column's name
        as the base expression.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``LOWER()`` function call. This allows method
            chaining.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Convert the column to lowercase
                expr = name_col.lower()
                # expr._output[0] -> "lower(users.[name])"
                # expr._output[1] -> []

                # Use in a query
                results = users.get_row([expr], where=users.id == 1)
                # retrieves the lowercase name for the user with id=1

                # Chain with another operation
                expr2 = (name_col + ' (test)').lower()
                # expr2._output[0] -> "lower((users.[name] || ?))"
                # expr2._output[1] -> [' (test)']
        """
        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(), remove leading and trailing characters from the column expression.

        This method generates a SQL ``trim`` function call that removes all
        occurrences of the specified characters from both ends of the expression's
        string value. By default, it strips whitespace characters.

        The method mutates the instance's ``_output`` attribute to store the SQL
        string and parameters, and returns ``self`` to allow method chaining.

        If the instance already has an accumulated expression (i.e., ``_output`` is
        a tuple), the ``trim`` is applied to that expression. Otherwise, it is
        applied directly to the underlying column name (``self.col_obj.name``).

        Args:
            chars (str, optional): A string of characters to remove from both ends.
                Defaults to a single space (' '). The characters can be specified in
                any order; the SQLite ``trim`` function removes any combination of
                these characters.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``trim`` operation. This allows chaining.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Strip whitespace from both ends of the name
                expr = name_col.strip()
                # expr._output[0] -> 'trim(users.[name]," ")'
                # expr._output[1] -> []

                # Strip specific characters (e.g., '-' and '_')
                expr2 = name_col.strip('-_')
                # expr2._output[0] -> 'trim(users.[name],"-_")'

                # Chain with other operations
                expr3 = name_col.strip().upper()
                # expr3._output[0] -> 'upper(trim(users.[name]," "))'
        """
        self._output = (f'trim({self._output[0]},"{chars}")', self._output[1]) if self._output else (f'trim({self.col_obj.name},"{chars}")', [])
        return self

    def lstrip(self, chars: str = ' '):
        """Just like python lstrip(), trim leading characters from the column expression using SQL LTRIM.

        This method generates a SQL ``LTRIM`` function call that removes all
        occurrences of the specified characters from the beginning (left side)
        of the column or expression's string value. The result is stored in the
        instance's ``_output`` attribute and the instance is returned to allow
        chaining.

        If the current expression already contains operations (i.e., ``_output``
        is not empty), the ``LTRIM`` is applied to that expression. Otherwise,
        it is applied directly to the underlying :class:`Column` object.

        The method supports specifying which characters to strip via the
        ``chars`` parameter. The default is a space character.

        Args:
            chars (str): A string containing the characters to remove from the
                left side. Defaults to a single space (``' '``). The order of
                characters does not matter; SQLite removes all characters in the
                set until a non-matching character is encountered.

        Returns:
            ColumnsOperation: The current instance with its ``_output`` updated
            to represent the SQL ``LTRIM`` expression. This allows method
            chaining.

        Example:
            Assuming a ``users`` table with a ``name`` column that may contain
            leading spaces::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Remove leading spaces
                expr = name_col.lstrip()
                # expr._output[0] -> "ltrim(users.[name],' ')"

                # Remove leading 'x' and 'y'
                expr2 = name_col.lstrip('xy')
                # expr2._output[0] -> "ltrim(users.[name],'xy')"

                # Use in a query to get cleaned names
                result = users.get_row([expr])
        """
        self._output = (f'ltrim({self._output[0]},"{chars}")', self._output[1]) if self._output else (f'ltrim({self.col_obj.name},"{chars}")', [])
        return self

    def rstrip(self, chars: str = ' '):
        """Just like python rstrip(), remove trailing characters from the column expression using SQL rtrim.

        This method generates a SQL ``rtrim`` function call that removes all
        occurrences of the specified characters from the end (right side) of the
        expression's string value. By default, it strips trailing whitespace.

        The method mutates the instance's ``_output`` attribute to store the SQL
        string and parameters, and returns ``self`` to allow method chaining.

        If the instance already has an accumulated expression (i.e., ``_output`` is
        a tuple), the ``rtrim`` is applied to that expression. Otherwise, it is
        applied directly to the underlying column name (``self.col_obj.name``).

        Args:
            chars (str, optional): A string of characters to remove from the right
                end. Defaults to a single space (' '). The characters can be
                specified in any order; the SQLite ``rtrim`` function removes any
                combination of these characters from the end of the string.

        Returns:
            :class:`ColumnsOperation`: The current instance with its ``_output``
            updated to represent the ``rtrim`` operation. This allows chaining.

        Example:
            Assuming a ``products`` table with a ``description`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                desc_col = products.description

                # Strip trailing whitespace
                expr = desc_col.rstrip()
                # expr._output[0] -> 'rtrim(products.[description]," ")'
                # expr._output[1] -> []

                # Strip trailing dashes and underscores
                expr2 = desc_col.rstrip('-_')
                # expr2._output[0] -> 'rtrim(products.[description],"-_")'

                # Chain with other operations
                expr3 = desc_col.rstrip().upper()
                # expr3._output[0] -> 'upper(rtrim(products.[description]," "))'
        """
        self._output = (f'rtrim({self._output[0]},"{chars}")', self._output[1]) if self._output else (f'rtrim({self.col_obj.name},"{chars}")', [])
        return self

    def In(self, value):
        """Generates an ``IN`` clause for the column expression.

        Creates a SQL ``IN`` condition with the given value(s). If a list or
        tuple is provided, multiple ``?`` placeholders are inserted and the
        values are appended to the parameter list. If a single value (that is
        not a column or expression) is given, the method falls back to an
        equality check (``= ?``). Passing a :class:`ColumnsOperation` embeds
        its SQL fragment directly.

        Args:
            value (Any): The value(s) to match against. May be:
                - a single scalar (int, float, str, etc.) → ``= ?``,
                - a list or tuple of scalars → ``IN (?, ?, ...)``,
                - a :class:`ColumnsOperation` → ``IN (<nested SQL>)``.

        Returns:
            :class:`ColumnsOperation`: The same instance with ``_output``
            updated, allowing method chaining.

        Example:
            >>> db = Driver('mydb.sqlite3')
            >>> users = db.users
            >>> # Single value: equality
            >>> cond = users.name.In('Alice')
            >>> # Multiple values: IN clause
            >>> cond = users.age.In([25, 30, 35])
            >>> # Use with another column expression
            >>> subquery_op = users.salary * 2
            >>> cond = users.salary.In(subquery_op)
            >>> # in other tables
            >>> rows = users.get_row([users.id], where=users.name.In(ban_table.get_row([ban_table.name])))
        """
        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(['?'] * len(value))})", self._output[1] + list(value)) if isinstance(value, (list, tuple)) else (f"{self._output[0]} = ?", self._output[1] + [value])
        return self


class Column:
    """Represents a database column in the SQLite ORM.

    This class acts as a proxy for a specific column in a database table,
    providing a Pythonic interface for building SQL expressions and performing
    schema operations. Columns are typically not instantiated directly but
    are automatically created as attributes of a :class:`Table` object during
    table initialization.

    The class overloads Python operators (``+``, ``-``, ``*``, ``/``, ``%``,
    ``**``, ``&``, ``|``, etc.) to generate corresponding SQL expressions.
    For string columns, ``+`` is translated to the SQL concatenation operator
    ``||``; for numeric columns, it translates to ``+``. Comparison operators
    (``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=``) generate SQL comparison
    conditions. The result of these operations is a :class:`ColumnsOperation`
    object, which can be used in the ``where`` clause of query methods like
    :meth:`Table.get_row`, :meth:`Table.update`, and :meth:`Table.delete_row`.

    Additionally, the class provides string manipulation methods (``lower``,
    ``upper``, ``strip``, ``startswith``, ``endswith``, ``contains``, ``like``,
    ``replace``, slicing via ``__getitem__``) that translate to SQLite's
    built-in functions, as well as methods for renaming (``rename``) and
    deleting (``delete_column``) the column in the database schema.

    Attributes:
        name (str): The fully qualified column name for use in SQL queries,
            formatted as ``[table_name].[column_name]``. This includes the
            table name and brackets to safely handle special characters.
        first_name (str): The column name formatted with brackets only,
            e.g., ``[column_name]``. This is typically used in DDL
            statements like ``ALTER TABLE ... RENAME COLUMN``.
        table_obj (Table): The parent :class:`Table` instance that this
            column belongs to.
        datatype (type): The Python type mapping for the column, derived
            from the SQLite column affinity (e.g., ``int`` for INTEGER,
            ``str`` for TEXT, ``float`` for REAL, ``bytes`` for BLOB).

    Example:
        Accessing columns from a table and building conditions::

            from ormophine.Sqlite import Driver

            # Connect to the database
            db = Driver('myapp.db')
            users = db.users  # creates Table instance

            # Access columns as attributes
            name_col = users.name      # Column instance
            age_col = users.age        # Column instance

            # Build SQL expressions using operators
            condition = (age_col >= 18) & name_col.startswith('A')
            # condition._output[0] -> "([users].[age] >= ? AND [users].[name] like ? || '%')"
            # condition._output[1] -> [18, 'A']

            # Use the condition in a query
            results = users.get_row([name_col, age_col], where=condition)
            # Executes: SELECT [users].[name], [users].[age] FROM [users]
            #           WHERE [users].[age] >= 18 AND [users].[name] LIKE 'A%'

            # Perform a string transformation in SQL
            upper_name = name_col.upper()
            # upper_name._output[0] -> "upper([users].[name])"

            # Rename the column (requires triple confirmation)
            name_col.rename('full_name')
    """

    def __init__(self, table_obj: Table, column_name: str, datatype: type):

        """Initializes a Column instance representing a column in a database table.

        This object holds the column's fully qualified name used in SQL generation
        (e.g., ``[table_name].[column_name]``), a short name without the table
        prefix, a reference to the parent :class:`Table`, and the Python type that
        corresponds to the column's SQL data type.

        Args:
            table_obj (Table): The :class:`Table` instance to which this column belongs.
            column_name (str): The name of the column as it appears in the database
                (without brackets). The name will be automatically wrapped in square
                brackets for safe SQL usage.
            datatype (type): The Python type that represents the column's data. This
                is used to decide between string concatenation (``||``) and arithmetic
                operators (``+``) when building expressions with
                :class:`ColumnsOperation`.

        Example:
            >>> from myorm import Driver, Table, Column
            >>> driver = Driver('example.db') #tables automatically imported to this object, and columns imported to each table
            >>> my_table = driver.my_sample_table #Table object
            >>> users = mytable.users #Column object
        """
        self.name= table_obj.name_+'.['+column_name+']'
        self.first_name= f'[{column_name}]'
        self.table_obj= table_obj
        self.datatype= datatype

    def __hash__(self):

        """Return a hash value for the column, derived from its fully qualified name.

        The column's fully qualified name is composed of the table name and the
        column name enclosed in brackets (e.g., ``[users].[id]``). Hashing
        allows :class:`Column` instances to be used as dictionary keys or in sets,
        ensuring uniqueness based on the column identity across different tables.

        Returns:
            int: The hash of the column's fully qualified name string.

        Example:
            >>> users_table = driver.get_tables()['users']
            >>> col = users_table.id
            >>> col_set = {col}
            >>> col in col_set
            True
        """
        
        return hash(self.name)

    def __add__(self, value):
        """Return a :class:`ColumnsOperation` representing addition or string concatenation with this column.

        This method overloads the ``+`` operator to create a SQL expression that adds or concatenates
        a value to the column. The operation depends on the column's data type:
        
        * For numeric columns (``int``, ``float``), it generates ``{column} + {value}``.
        * For string columns (``str``), it generates ``{column} || {value}`` (SQLite concatenation operator).
        
        The returned :class:`ColumnsOperation` can be used directly in methods like
        :meth:`Table.update`, :meth:`Table.get_row`, :meth:`Table.join`, or combined further with
        other arithmetic/comparison operations.

        Args:
            value: The right-hand operand. It can be a :class:`Column` (to add another column),
                a :class:`ColumnsOperation` (to chain from an existing operation), a numeric literal
                (``int`` or ``float`` for arithmetic), or a string literal for concatenation.

        Returns:
            ColumnsOperation: A chainable operation object whose internal SQL representation
            reflects the addition/concatenation. This object can be used in further operations
            like comparison or string manipulation.

        Raises:
            TypeError: If the operand type is incompatible with the column's data type
                (e.g., adding a number to a text column will still generate ``||``, but the
                type handling is determined by the column's ``datatype`` attribute).

        Example:
            Creating an expression for a SELECT or UPDATE:

            >>> # Assuming 'price' is a Column of type float and 'quantity' is an int Column
            >>> total_expr = table.price + table.quantity  # price + quantity (numeric)
            >>> name_with_prefix = table.name + '_suffix'  # name || '_suffix' (text concatenation)
            >>> combined_expr = (table.price + 10) * table.quantity  # (price + 10) * quantity

            Using the expression in a query:

            >>> result = table.get_row([table.price + table.quantity], where=table.price > 100)
            >>> table.update({table.description: table.description + ' - updated'}, where=table.id == 1)
        """

        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob + value

    def __radd__(self, value):
        """Implements reflected addition for a :class:`Column` instance.

        This method is invoked when the left operand does not support addition
        with a :class:`Column` object (e.g., ``"Hello " + col``). It creates
        a :class:`ColumnsOperation` that encapsulates the SQL addition expression.
        For columns of type :class:`str`, the concatenation operator ``||`` is
        used; for numeric types (``int``, ``float``), the arithmetic ``+`` is used.

        Args:
            value: The left operand in the addition. It can be:
                - A :class:`Column` instance.
                - A :class:`ColumnsOperation` instance.
                - An :class:`int` or :class:`float` (numeric literal).
                - A :class:`str` (text literal, treated as string even if the
                column's datatype is numeric, as SQLite's ``||`` will perform
                implicit conversion, but the generated SQL will use ``||``
                for consistency with the column's declared type).

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute is a tuple of (SQL fragment, parameter list). The SQL
            fragment uses ``||`` if the column's datatype is :class:`str`,
            otherwise ``+``. The parameter list contains any literal values
            that were bound into the expression.

        Raises:
            No exceptions are raised by this method itself, but further
            evaluation of the expression may raise database-related errors.

        Example:
            >>> col = my_table.name  # datatype is str
            >>> expr = "Hello " + col
            >>> expr._output
            ('? || [my_table].[name]', ['Hello '])

            >>> col2 = my_table.age  # datatype is int
            >>> expr2 = 10 + col2
            >>> expr2._output
            ('? + [my_table].[age]', [10])
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value + temp_ob

    def __sub__(self, value):
        """Return a :class:`ColumnsOperation` representing subtraction from this column.

        Produces a SQL expression that subtracts *value* from the column.
        The type of *value* determines the exact generated SQL:

        - If *value* is a :class:`ColumnsOperation`, the subtraction expression
        is ``(column_name - <operation expression>)`` and the parameter lists
        are concatenated.
        - If *value* is a :class:`Column`, the expression becomes
        ``(column_name - <other column name>)`` with no extra parameters.
        - Otherwise (a numeric literal), the expression is ``(column_name - ?)``
        and the value is added to the parameter list.

        Args:
            value: The right-hand operand. Can be:
                - :class:`ColumnsOperation` – another operation to subtract.
                - :class:`Column` – another column to subtract.
                - numeric (int or float) – a literal value to subtract.

        Returns:
            :class:`ColumnsOperation`: A new operation object whose ``_output``
            attribute is a tuple ``(sql_fragment, parameter_list)``. The object
            can be further chained with other operators or comparisons.

        Example:
            >>> col = table.salary  # salary is a Column
            >>> op = col - 500      # uses __sub__
            >>> op._output
            ('([salary] - ?)', [500])

            >>> bonus = table.bonus
            >>> op2 = col - bonus   # subtraction of two columns
            >>> op2._output
            ('([salary] - [bonus])', [])

            The result can be used in WHERE clauses, SELECT expressions, etc.
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob - value

    def __rsub__(self, value):
        """Implements reflected subtraction for a :class:`Column` instance.

        This method is invoked when the left operand in a subtraction does not
        support the operation with a :class:`Column` object (e.g.,
        ``100 - my_table.age``). It creates a :class:`ColumnsOperation` that
        represents the SQL subtraction expression. The subtraction is always
        arithmetic (``-``) regardless of the column's :attr:`datatype`.

        Args:
            value: The left operand of the subtraction. Can be:
                - :class:`int` or :class:`float`: a numeric literal, wrapped
                  as a parameterized value.
                - :class:`Column`: another column reference.
                - :class:`ColumnsOperation`: a pre‑existing expression.

        Returns:
            :class:`ColumnsOperation`: An expression object whose
            :attr:`_output` attribute is a tuple of
            ``(SQL_fragment, parameter_list)``. The SQL fragment is of the
            form ``(? - column_name)`` or ``(other - column_name)``, and
            the parameter list contains any bound literal values.

        Raises:
            No exceptions are directly raised; database‑related errors may
            occur when the expression is evaluated later.

        Example:
            >>> col = my_table.age  # integer column
            >>> expr = 100 - col
            >>> expr._output
            ('(? - [my_table].[age])', [100])

            >>> col2 = my_table.name  # string column (subtraction is still arithmetic)
            >>> expr2 = 10 - col2  # likely a mistake, but the SQL will be generated
            >>> expr2._output
            ('(? - [my_table].[name])', [10])
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value - temp_ob
        
    def __mul__(self, value):
        """Implements multiplication for a :class:`Column` instance.

        This method is invoked when a :class:`Column` object is used on the
        left side of the ``*`` operator (e.g., ``col * 2``). It creates a
        :class:`ColumnsOperation` that encapsulates the SQL multiplication
        expression. The expression uses the SQL ``*`` operator regardless
        of the column's declared datatype (SQLite performs implicit numeric
        conversion if needed).

        Args:
            value: The right operand in the multiplication. It can be:
                - A :class:`Column` instance (e.g., ``col1 * col2``).
                - A :class:`ColumnsOperation` instance.
                - An :class:`int` or :class:`float` literal.
                - A :class:`str` literal (will be treated as a numeric
                literal if SQLite can convert it; otherwise an error may
                occur at execution time).

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute is a tuple of (SQL fragment, parameter list). For
            example, ``col * 2`` produces ``('([table].[col] * ?)', [2])``.
            The expression can be further combined using other operators or
            used in WHERE clauses, joins, and selections.

        Raises:
            No exceptions are raised by this method itself. SQLite runtime
            errors (e.g., type mismatch) may occur when the query is executed.

        Example:
            Multiply an integer column by a constant:

            >>> col = my_table.price
            >>> discounted = col * 0.9
            >>> discounted._output
            ('([my_table].[price] * ?)', [0.9])

            Multiply two columns:

            >>> total = my_table.quantity * my_table.unit_price
            >>> total._output
            ('([my_table].[quantity] * [my_table].[unit_price])', [])
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob * value

    def __rmul__(self, value):
        """Implements reflected multiplication for a :class:`Column`.

        Called when a literal or expression appears on the left of the ``*``
        operator, e.g. ``3 * my_column``.  A new :class:`ColumnsOperation` is
        created that wraps the column name and then delegates to
        :meth:`ColumnsOperation.__rmul__` to build the actual SQL expression.
        The generated SQL uses the arithmetic ``*`` operator (multiplication).

        Note:
            This method is intended for numeric columns.  Using it with a
            :class:`str` column or non-numeric literal may still produce SQL
            that SQLite will attempt to convert implicitly, but is not
            recommended.

        Args:
            value: The left operand of the multiplication.  Accepts:
                - :class:`int` or :class:`float` literals.
                - Another :class:`Column` instance.
                - A :class:`ColumnsOperation` instance.

        Returns:
            :class:`ColumnsOperation`: An expression object whose internal
            ``_output`` tuple contains ``(sql_fragment, parameters)``, ready
            to be used in WHERE clauses, SELECT lists, or UPDATE assignments.

        Raises:
            No exceptions are raised during construction.  Database errors
            (e.g. type mismatches) may occur only when the query is executed.

        Example:
            >>> age = my_table.age  # numeric column
            >>> expr = 2 * age
            >>> expr._output
            ('? * [my_table].[age]', [2])

            The resulting expression can be used directly with :meth:`Table.get_row`::

                my_table.get_row([expr], where=expr > 0)
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value * temp_ob

    def __pow__(self, value):
        """Generate an SQL exponentiation (power) expression for this column.

        This special method is invoked when the ``**`` operator is used with a
        :class:`Column` instance on the left side (e.g., ``col ** 2``). It creates a
        :class:`ColumnsOperation` that will produce an SQL fragment raising the column
        to the given power. The resulting SQL uses the ``**`` operator, which is not
        standard SQLite syntax; it is assumed that the underlying SQL execution
        context either supports this syntax or that a custom SQL function ``power``
        is mapped to ``**``. The generated expression is wrapped in parentheses to
        ensure correct operator precedence.

        Args:
            value: The exponent. It can be:
                - A :class:`ColumnsOperation` instance, in which case its SQL fragment
                and parameters are combined.
                - A :class:`Column` instance (another table column), using its name
                directly in the SQL.
                - A numeric literal (int or float), which will be replaced with a
                parameter placeholder (``?``) and added to the parameter list.

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute is a tuple of ``(sql_fragment, param_list)``. The SQL
            fragment contains ``**`` between the column and the exponent, and the
            parameter list holds any bound literal values.

        Example:
            >>> age_col = my_table.age  # Column for integer age
            >>> expr = age_col ** 2
            >>> expr._output
            ('([my_table].[age] ** ?)', [2])

            >>> another_col = my_table.salary
            >>> combined_expr = age_col ** another_col
            >>> combined_expr._output
            ('([my_table].[age] ** [my_table].[salary])', [])
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob ** value

    def __rpow__(self, value):
        """Implements the reflected exponentiation operator for a Column.

        This method is called when the left operand does not support power
        with a Column (e.g., ``2 ** col``). It creates a
        :class:`ColumnsOperation` that generates a SQL exponentiation
        expression using the ``**`` operator. The resulting SQL fragment and
        parameters can be used in :meth:`Table.get_row`, :meth:`Table.update`,
        or other query methods.

        Args:
            value: The left operand in the exponentiation. It can be:
                - An :class:`int` or :class:`float` literal (e.g., 2).
                - A :class:`Column` instance.
                - A :class:`ColumnsOperation` instance.

        Returns:
            :class:`ColumnsOperation`: An expression object whose
            :attr:`_output` attribute is a tuple of the form
            (SQL fragment, parameter list). The SQL fragment represents
            ``(value ** column_name)``, and the parameter list contains
            any bound literal values.

        Raises:
            No exceptions are raised by this method. However, subsequent
            use in a SQL query may raise database errors if the operation
            is invalid (e.g., non‑numeric column).

        Example:
            >>> col = my_table.score  # a numeric Column
            >>> expr = 2 ** col
            >>> expr._output
            ('(? ** [my_table].[score])', [2])
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob ** value

    def __truediv__(self, value):
        """Implement the division operator (``/``) for a :class:`Column`.

        This method creates a :class:`ColumnsOperation` that represents the SQL
        division of this column by ``value``. The generated SQL uses the standard
        ``/`` operator, and any literal operands are parameterized with ``?`` to
        prevent SQL injection.

        The division is performed by first wrapping the column into a
        :class:`ColumnsOperation` with its initial SQL fragment set to the column's
        fully qualified name and an empty parameter list, then delegating to
        :meth:`ColumnsOperation.__truediv__`.

        Args:
            value: The divisor. It can be:
                - A :class:`Column` instance – division by another column.
                - A :class:`ColumnsOperation` instance – division by a sub‑expression.
                - A numeric literal (:class:`int` or :class:`float`) – division by a
                constant value (parameterized).

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute is a tuple ``(sql_fragment, params)``. For example, dividing
            a column named ``price`` by 2 would produce the SQL fragment
            ``([table_name].[price] / ?)`` and a parameter list ``[2]``.

        Raises:
            No exceptions are raised directly by this method. However, subsequent
            use of the returned expression in a query may raise database errors if
            the column types are incompatible or division by zero occurs.

        Example:
            >>> # Assume a Table 'products' with Column 'price' (datatype float)
            >>> half_price = products.price / 2
            >>> half_price._output
            ('([products].[price] / ?)', [2])

            >>> # Use in a query
            >>> products.update(
            ...     {products.price: half_price},
            ...     where=products.id == 42
            ... )
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob / value

    def __rtruediv__(self, value):
        """Implements reflected (right-hand) division for a :class:`Column`.

        This method is called when the left operand in a division operation does
        not support division with a :class:`Column` instance, for example when
        writing ``10 / my_column``. It creates a :class:`ColumnsOperation` that
        wraps the column and then delegates the actual operation to the
        :meth:`ColumnsOperation.__rtruediv__` method, resulting in a SQL expression
        using the standard ``/`` operator with parameterized literal values.

        The generated SQL fragment places the right operand's expression on the
        left side of the division to preserve mathematical correctness: the
        resulting fragment will be ``(value / column_name)``.

        Args:
            value: The numerator in the division. It can be:
                - A :class:`Column` instance – division of another column by this
                column.
                - A :class:`ColumnsOperation` instance – a sub‑expression as
                numerator.
                - A numeric literal (:class:`int` or :class:`float`) – division
                of a constant by this column (parameterized with ``?``).

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute is a tuple ``(sql_fragment, params)``. For example,
            ``10 / products.price`` would produce ``'(? / [products].[price])'``
            with a parameter list ``[10]``.

        Raises:
            No exceptions are raised by this method itself. Database‑level errors
            (e.g., type mismatch or division by zero) may occur later when the
            expression is executed.

        Example:
            >>> # Assume a Table 'products' with Column 'price' (datatype float)
            >>> inverted = 100 / products.price
            >>> inverted._output
            ('(? / [products].[price])', [100])

            >>> # Use in a query
            >>> products.get_row(
            ...     [products.price, 100 / products.price],
            ...     where=products.id == 1
            ... )
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value / temp_ob

    def __mod__(self, value):
        """Implements the modulo operator (``%``) for a :class:`Column`.

        Generates a :class:`ColumnsOperation` that represents the SQL modulo
        expression (``%`` in SQLite). The column is wrapped in a new
        :class:`ColumnsOperation` with its fully qualified name and an empty
        parameter list, then the modulo operation is applied via
        :meth:`ColumnsOperation.__mod__`. Literal values are parameterized
        with ``?`` placeholders to prevent SQL injection.

        Args:
            value: The divisor for the modulo operation. Can be a
                :class:`Column` (reference to another column), a
                :class:`ColumnsOperation` (a sub‑expression), or a numeric
                literal (:class:`int` or :class:`float`).

        Returns:
            :class:`ColumnsOperation`: An expression object whose
            :attr:`_output` attribute is a tuple
            ``(sql_fragment, params)``. For example, ``col % 3`` produces
            the SQL fragment ``([table_name].[col] % ?)`` and a parameter
            list ``[3]``.

        Example:
            >>> # Assume a Table 'users' with Column 'id' (datatype int)
            >>> remainder = users.id % 2
            >>> remainder._output
            ('([users].[id] % ?)', [2])

            >>> # Use in a query to find odd IDs
            >>> users.get_row(
            ...     [users.id],
            ...     where=remainder == 1
            ... )
        """

        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return temp_ob % value

    def __rmod__(self, value):
        """Implements the reflected modulo operator (``%``) for a :class:`Column`.

        This method is invoked when the left operand does not support modulo
        with a :class:`Column` object (e.g., ``3 % col``). It creates a
        :class:`ColumnsOperation` that represents the SQL modulo expression
        with the column on the right. The generated SQL uses the ``%``
        operator, and any literal operands are parameterized with ``?`` to
        prevent SQL injection.

        The column is first wrapped in a :class:`ColumnsOperation` whose
        initial SQL fragment is the column's fully qualified name and an
        empty parameter list. Then the reflected modulo is delegated to
        :meth:`ColumnsOperation.__rmod__`.

        Args:
            value: The dividend (left operand) of the modulo operation.
                Can be a :class:`Column` instance, a
                :class:`ColumnsOperation` instance, or a numeric literal
                (:class:`int` or :class:`float`).

        Returns:
            :class:`ColumnsOperation`: An expression object whose
            :attr:`_output` attribute is a tuple
            ``(sql_fragment, params)``. For example, ``3 % col``
            produces the SQL fragment ``(? % [table_name].[col])``
            and a parameter list ``[3]``.

        Example:
            >>> # Assume a Table 'inventory' with Column 'quantity' (datatype int)
            >>> remainder = 10 % inventory.quantity
            >>> remainder._output
            ('(? % [inventory].[quantity])', [10])

            >>> # Use in a query
            >>> inventory.get_row(
            ...     [inventory.id],
            ...     where=remainder == 0
            ... )
        """
     
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (self.name, [])
        return value % temp_ob

    def eq(self, value):
        """
        Create an equality comparison expression for this column.

        This method generates a SQL `=` condition, wrapping the column and the
        provided value in a `ColumnsOperation` object. The resulting object can be
        used in WHERE clauses, joins, or other conditional contexts.

        The method supports three types of `value`:
        - Another `Column`: produces `column1 = column2`.
        - A `ColumnsOperation`: produces `column = (expression)` with its parameters.
        - A literal (e.g., int, str, float): produces `column = ?` with the value
        bound as a parameter.

        The returned `ColumnsOperation` holds the SQL fragment and the list of
        bound parameters, suitable for passing to `Table` methods like
        `update()`, `delete_row()`, or `get_row()`.

        Args:
            value (Any): The right-hand side of the equality.
                Can be a `Column`, a `ColumnsOperation`, or a literal value.

        Returns:
            ColumnsOperation: A new operation object representing the equality
            condition `(self = value)`. The object's `_output` attribute is a
            tuple `(sql_expression, parameters)`.

        Example:
            >>> from your_module import Driver, Table, Column
            >>> db = Driver('example.db')
            >>> users = db.users
            >>> age = users.age  # Column instance
            >>> condition = age.eq(25)
            >>> # condition._output -> ('([users].[age] = ?)', [25])
            >>> # Use in a query:
            >>> rows = users.get_row([users.name], where=condition)
        """
        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} = ?)', [value])
        return temp_ob

    def __eq__(self, value):
        """Create an equality comparison condition for the column.

        This method generates a SQL equality expression (`=`) between the column
        and the provided value. The result is a :class:`ColumnsOperation` object
        that can be used in ``WHERE`` clauses of queries, updates, or deletes.
        The method supports comparisons with literals, other :class:`Column`
        objects, and :class:`ColumnsOperation` expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column
                is compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a
                computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            equality comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``User`` table with columns ``id`` and ``name``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('database.db')
                users = db.users
                name_col = users.name  # a Column instance

                # Compare column to a literal
                condition = name_col == 'Alice'
                # condition._output[0] -> "(users.[name] = ?)"
                # condition._output[1] -> ['Alice']

                # Compare two columns
                id_col = users.id
                condition2 = id_col == name_col
                # condition2._output[0] -> "(users.[id] = users.[name])"
                # condition2._output[1] -> []

                # Use in a query
                results = users.get_row([id_col], where=condition)
                # retrieves rows where name == 'Alice'
        """
        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} = ?)', [value])
        return temp_ob

    def ne(self, value):
        """
        Create a not-equal comparison condition between this column and a value.

        This method generates a SQL `!=` expression that can be used in WHERE clauses.
        The returned `ColumnsOperation` object holds the SQL fragment and its bound
        parameters. It handles comparisons with literal values, other columns, or
        composite expressions (e.g., arithmetic or function calls).

        Args:
            value: The right-hand side of the comparison. Supported types:
                - A literal (e.g., int, float, str, bool): produces a placeholder `?`.
                - A `Column` object: compares column to column using the column's name.
                - A `ColumnsOperation` object: uses the operation's SQL fragment and
                combines its parameters.

        Returns:
            ColumnsOperation: A new operation object representing the `!=` condition.
            The object's internal `_output` attribute is a tuple `(sql_fragment, params)`,
            where `sql_fragment` contains the comparison expression (e.g., `(table.col != ?)`)
            and `params` is the list of bound parameters.

        Example:
            >>> from your_module import Driver, Table, Column
            >>> db = Driver('example.db')
            >>> users = db.users
            >>> age_col = users.age
            >>> condition = age_col.ne(18)   # age != 18
            >>> # Use condition in a query:
            >>> users.get_row([users.name], where=condition)
        """
        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} != ?)', [value])
        return temp_ob

    def __ne__(self, value):
        """Inequality operator (`!=`) for constructing SQL WHERE conditions.

        Generates a SQL inequality comparison between this column and another expression
        or literal value. The result is a :class:`ColumnsOperation` object that can be
        combined with other conditions or used directly in a query's ``WHERE`` clause.

        Args:
            value (Union[Column, ColumnsOperation, Any]): The right-hand side of the
                comparison. Can be another :class:`Column`, a :class:`ColumnsOperation`
                (e.g., from arithmetic or string operations), or a literal value
                (e.g., ``int``, ``str``, ``float``). If a literal is provided, it will
                be used as a parameterized placeholder (``?``) in the generated SQL.

        Returns:
            ColumnsOperation: A :class:`ColumnsOperation` instance whose internal state
            represents the inequality condition ``this_column != value``. This object
            can be used in :meth:`Table.update`, :meth:`Table.delete_row`,
            :meth:`Table.get_row`, and similar methods that accept a ``where``
            parameter.

        Raises:
            TypeError: If the `value` type is not supported (e.g., not a :class:`Column`,
                :class:`ColumnsOperation`, or a literal). The method may fail when
                accessing ``_output`` or ``name`` attributes of unsupported types.

        Example:
            >>> from myorm import Driver, Table, Column
            >>> db = Driver('test.db')
            >>> users = db.users
            >>> age = users.age  # type: Column
            >>> condition = age != 30  # returns ColumnsOperation
            >>> result = users.get_row([users.name], where=condition)
            # Generated SQL: SELECT [users].[name] FROM [users] WHERE ([users].[age] != ?)
        """
        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} != ?)', [value])
        return temp_ob

    def gt(self, value):
        """Construct a SQL condition comparing this column to a value using the greater-than (>) operator.

        This method returns a :class:`ColumnsOperation` object that can be used in WHERE clauses
        or combined with other conditions using logical operators (``&``, ``|``). The comparison
        is applied to the column's stored value. If the column's datatype is not numeric, the
        comparison follows SQLite's rules for the respective type.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (int, float, str, etc.) – will be parameterized as a placeholder.
                - A :class:`Column` object – compares the current column with another column.
                - A :class:`ColumnsOperation` object – compares with a computed expression.

        Returns:
            ColumnsOperation: A new operation object representing the SQL condition
                ``(column > value)``. The object can be chained with other conditions
                or used directly in :meth:`Table.get_row`, :meth:`Table.update`,
                :meth:`Table.delete_row`, etc.

        Example:
            Assuming a table ``users`` with columns ``age`` (Column) and ``name`` (Column)::

                from your_orm import Driver, Column

                db = Driver('example.db')
                users = db.users
                age_col = users.age

                # Compare with a literal
                condition = age_col.gt(18)  # age > 18

                # Compare with another column
                condition2 = age_col.gt(users.min_age)  # age > min_age

                # Use in a query
                rows = users.get_row([users.name], where=condition)
        """
        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} > ?)', [value])
        return temp_ob

    def __gt__(self, value):
        """Create a greater-than comparison condition for the column.

        This method generates a SQL ">" expression between the column and the provided
        value. The result is a :class:`ColumnsOperation` object that can be used in
        ``WHERE`` clauses of queries, updates, or deletes. The comparison supports
        literals, other :class:`Column` objects, and :class:`ColumnsOperation`
        expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column
                is compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a
                computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            greater-than comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``Product`` table with columns ``price`` and ``discount``::

                from ormophine.Sqlite import Driver

                db = Driver('store.db')
                products = db.products
                price_col = products.price

                # Compare column to a literal
                condition = price_col > 100
                # condition._output[0] -> "(products.[price] > ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                discount_col = products.discount
                condition2 = price_col > discount_col
                # condition2._output[0] -> "(products.[price] > products.[discount])"

                # Use in a query
                rows = products.get_row([price_col], where=condition)
                # retrieves rows where price > 100
        """
        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} > ?)', [value])
        return temp_ob

    def lt(self, value):
        """Create a less-than comparison condition for the column.

        This method generates a SQL less-than expression (`<`) between the column
        and the provided value. The result is a :class:`ColumnsOperation` object
        that can be used in ``WHERE`` clauses of queries, updates, or deletes.
        The method supports comparisons with literals, other :class:`Column`
        objects, and :class:`ColumnsOperation` expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column
                is compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a
                computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            less-than comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``Product`` table with columns ``price`` and ``discount``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price_col = products.price
                discount_col = products.discount

                # Compare column to a literal value
                condition = price_col.lt(100)
                # condition._output[0] -> "(products.[price] < ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                condition2 = price_col.lt(discount_col)
                # condition2._output[0] -> "(products.[price] < products.[discount])"
                # condition2._output[1] -> []

                # Use in a query
                results = products.get_row([price_col], where=condition)
                # retrieves rows where price < 100
        """
        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} < ?)', [value])
        return temp_ob

    def __lt__(self, value):
        """Create a less-than comparison condition for the column.

        This method generates a SQL less-than expression (`<`) between the column
        and the provided value. The result is a :class:`ColumnsOperation` object
        that can be used in ``WHERE`` clauses of queries, updates, or deletes.
        The method supports comparisons with literals, other :class:`Column`
        objects, and :class:`ColumnsOperation` expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column
                is compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a
                computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            less-than comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``Product`` table with a ``price`` column::

                from ormophine.Sqlite import Driver

                db = Driver('shop.db')
                products = db.products
                price_col = products.price

                # Compare column to a literal
                condition = price_col < 100
                # condition._output[0] -> "(products.[price] < ?)"
                # condition._output[1] -> [100]

                # Compare two columns
                cost_col = products.cost
                condition2 = price_col < cost_col
                # condition2._output[0] -> "(products.[price] < products.[cost])"

                # Use in a query
                results = products.get_row([price_col], where=condition)
                # retrieves rows where price < 100
        """
        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} < ?)', [value])
        return temp_ob

    def ge(self, value):
        """Create a greater-than-or-equal-to (>=) comparison condition for the column.

        This method generates a SQL ``>=`` expression between the column and the
        provided value. The result is a :class:`ColumnsOperation` object that can
        be used in ``WHERE`` clauses of queries, updates, or deletes. The method
        supports comparisons with literals, other :class:`Column` objects, and
        :class:`ColumnsOperation` expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column
                is compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a
                computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            ``>=`` comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``User`` table with columns ``id`` and ``age``::

                from ormophine.Sqlite import Driver

                db = Driver('database.db')
                users = db.users
                age_col = users.age  # a Column instance

                # Compare column to a literal
                condition = age_col.ge(18)  # or age_col >= 18
                # condition._output[0] -> "(users.[age] >= ?)"
                # condition._output[1] -> [18]

                # Compare two columns
                id_col = users.id
                condition2 = age_col.ge(id_col)
                # condition2._output[0] -> "(users.[age] >= users.[id])"
                # condition2._output[1] -> []

                # Use in a query
                results = users.get_row([id_col], where=condition)
                # retrieves rows where age >= 18
        """
        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} >= ?)', [value])
        return temp_ob

    def __ge__(self, value):
        """Create a greater-than-or-equal-to comparison condition for the column.

        This method generates a SQL `>=` (greater than or equal) expression
        between the column and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes. The method supports comparisons with
        literals, other :class:`Column` objects, and :class:`ColumnsOperation`
        expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column
                is compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a
                computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            ``>=`` comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``Product`` table with columns ``price`` and
            ``discount``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price_col = products.price
                discount_col = products.discount

                # Compare column to a literal
                condition1 = price_col >= 100
                # condition1._output[0] -> "(products.[price] >= ?)"
                # condition1._output[1] -> [100]

                # Compare two columns
                condition2 = price_col >= discount_col
                # condition2._output[0] -> "(products.[price] >= products.[discount])"
                # condition2._output[1] -> []

                # Use in a query
                results = products.get_row([price_col, discount_col],
                                        where=condition1)
                # retrieves rows where price >= 100
        """
        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} >= ?)', [value])
        return temp_ob

    def le(self, value):
        """Create a less-than-or-equal-to comparison condition for the column.

        This method generates a SQL ``<=`` (less than or equal) comparison expression
        between the column and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses of
        queries, updates, or deletes. The method supports comparisons with literals,
        other :class:`Column` objects, and :class:`ColumnsOperation` expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column is
                compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a computed
                expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            less-than-or-equal comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``Product`` table with columns ``price`` and ``discount``::

                from ormophine.Sqlite import Driver

                db = Driver('store.db')
                products = db.products
                price_col = products.price

                # Compare column to a literal
                condition = price_col <= 100.0
                # condition._output[0] -> "(products.[price] <= ?)"
                # condition._output[1] -> [100.0]

                # Compare two columns
                discount_col = products.discount
                condition2 = price_col <= discount_col
                # condition2._output[0] -> "(products.[price] <= products.[discount])"
                # condition2._output[1] -> []

                # Use in a query
                results = products.get_row([price_col], where=condition)
                # retrieves products with price <= 100.0
        """
        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} <= ?)', [value])
        return temp_ob

    def __le__(self, value):
        """Create a less-than-or-equal-to comparison condition for the column.

        This method generates a SQL `<=` (less than or equal) expression
        between the column and the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses
        of queries, updates, or deletes. The method supports comparisons with
        literals, other :class:`Column` objects, and :class:`ColumnsOperation`
        expressions.

        Args:
            value: The value to compare against. Can be one of:
                - A literal (e.g., ``int``, ``str``, ``float``) – the column
                is compared to that literal.
                - A :class:`Column` – compares the column to another column.
                - A :class:`ColumnsOperation` – compares the column to a
                computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the
            ``<=`` comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``Product`` table with columns ``price`` and
            ``discount``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price_col = products.price
                discount_col = products.discount

                # Compare column to a literal
                condition1 = price_col <= 100
                # condition1._output[0] -> "(products.[price] <= ?)"
                # condition1._output[1] -> [100]

                # Compare two columns
                condition2 = price_col <= discount_col
                # condition2._output[0] -> "(products.[price] <= products.[discount])"
                # condition2._output[1] -> []

                # Use in a query
                results = products.get_row([price_col, discount_col],
                                        where=condition1)
                # retrieves rows where price <= 100
        """
        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} <= ?)', [value])
        return temp_ob

    def __getitem__(self, key: slice):
        """Just like python slicing, generate a SQL substring expression for slicing a text column.

        This method enables Python-style slicing on :class:`Column` objects,
        translating slice operations into SQLite ``substr()`` function calls.
        It supports both positive and negative indices, as well as open-ended
        slices. The resulting :class:`ColumnsOperation` object can be used in
        queries, updates, or as part of larger expressions.

        The slicing behavior mimics Python string slicing with SQL semantics:
        - ``column[0:5]`` → ``substr(column, 1, 5)`` (1‑based indexing)
        - ``column[2:]`` → ``substr(column, 3, length(column))``
        - ``column[:-2]`` → ``substr(column, 1, length(column)-1)`` (excludes last two chars)
        - Negative indices are converted to offsets from the end:
        ``column[-3:]`` → ``substr(column, length(column)-2, length(column))``
        - End index is exclusive: ``column[0:3]`` takes characters at positions 0,1,2.

        The method adjusts indices because SQLite ``substr()`` uses 1‑based
        indexing and inclusive end positions, whereas Python uses 0‑based and
        exclusive end. The implementation handles the conversion transparently.

        Args:
            key (slice): A slice object specifying the start and stop positions.
                Both ``start`` and ``stop`` can be ``None``, positive, or negative
                integers. Step values are ignored (not supported by SQLite).

        Returns:
            :class:`ColumnsOperation`: An operation object whose ``_output``
            attribute contains the SQL substring expression and the list of
            bound parameters (if any). The expression can be chained with
            other operations or used in conditions.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver

                db = Driver('database.db')
                users = db.users
                name_col = users.name

                # Get first 3 characters
                expr = name_col[:3]
                # expr._output[0] -> "substr(users.[name] , 1 , 3)"
                # expr._output[1] -> []

                # Get from position 2 to end
                expr2 = name_col[2:]
                # expr2._output[0] -> "substr(users.[name] , 3 , length(users.[name]))"

                # Get last 4 characters (equivalent to name[-4:])
                expr3 = name_col[-4:]
                # expr3._output[0] -> "substr(users.[name] , length(users.[name]) - 3 , length(users.[name]))"
                # expr3._output[1] -> []

                # Use in a query
                condition = name_col[:3] == 'Joh'
                results = users.get_row([name_col], where=condition)
                # retrieves users whose name starts with 'Joh'
        """

        temp_ob = ColumnsOperation(self)
        if key.start == None and key.stop ==  None:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , 0 , length({temp_ob.col_obj.name}) + 1)', [])   #
        elif key.start == None and key.stop < 0:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , 0 , length({temp_ob.col_obj.name}) - ?)', [abs(key.stop) - 1])  #
        elif key.start == None and key.stop >= 0:
             temp_ob._output = (f'substr({temp_ob.col_obj.name} , 0 , ?)', [key.stop + 1])  #  
        elif key.start >= 0 and key.stop ==  None:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , ? , length({temp_ob.col_obj.name}))', [key.start + 1])  #   
        elif key.start < 0 and key.stop == None:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , length({temp_ob.col_obj.name}) - ? , length({temp_ob.col_obj.name}))', [abs(key.start) - 1])  #
        elif key.start >= 0 and key.stop < 0:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , ? , length({temp_ob.col_obj.name}) - ?)', [key.start + 1, abs(key.stop - key.start)])  #  
        elif key.start >= 0 and key.stop > 0:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , ? , ?)', [key.start + 1, key.stop - key.start])  #
        elif key.start < 0 and key.stop < 0:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , length({temp_ob.col_obj.name}) - ? , ?)', [abs(key.start) - 1, key.stop - key.start])  #
        elif key.start < 0 and key.stop > 0:
            temp_ob._output = (f'substr({temp_ob.col_obj.name} , length({temp_ob.col_obj.name}) - ? ,  ? - (length({temp_ob.col_obj.name}) - ?))', [abs(key.start) - 1, key.stop, abs(key.start)])
        return temp_ob

    def strip(self, chars: str = ' '):
        """Just like python strip(), return a :class:`ColumnsOperation` that applies SQLite's ``trim()`` function to the column.

        The ``trim()`` function removes all characters specified in ``chars`` from both the
        beginning and end of the column's string value. By default, it strips spaces.

        This method is chainable and returns a :class:`ColumnsOperation` object that can be
        used in queries, updates, or as part of larger expressions.

        Args:
            chars (str, optional): A string of characters to remove from both ends.
                Defaults to a single space.

        Returns:
            :class:`ColumnsOperation`: An operation object representing the ``trim()``
            expression. Its ``_output`` attribute contains the SQL string and parameter list.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver

                db = Driver('my.db')
                users = db.users
                name = users.name

                # Strip spaces from both ends
                trimmed = name.strip()
                # trimmed._output[0] -> "trim(users.[name],' ')"
                # trimmed._output[1] -> []

                # Strip specific characters (e.g., underscores and dashes)
                cleaned = name.strip('_-')
                # cleaned._output[0] -> "trim(users.[name],'_-')"

                # Use in a SELECT query
                result = users.get_row([trimmed], where=name.contains('john'))
                # returns rows where the trimmed name contains 'john'
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'trim({temp_ob._output[0]},"{chars}")', temp_ob._output[1]) if temp_ob._output else (f'trim({temp_ob.col_obj.name},"{chars}")', [])
        return temp_ob

    def lstrip(self, chars: str = ' '):
        """Just like python lstrip(), remove leading characters from the column value using SQLite's LTRIM function.

        This method creates a :class:`ColumnsOperation` object that represents
        the SQL ``LTRIM()`` expression applied to the column (or to the current
        expression chain). The ``LTRIM`` function removes all occurrences of the
        specified characters from the beginning of the string. If no characters
        are specified, it removes spaces by default.

        If the column has already been part of an expression (e.g., arithmetic or
        string concatenation), the operation is applied to the existing expression
        output. Otherwise, it is applied directly to the column.

        Args:
            chars (str, optional): A string of characters to remove from the start
                of the string. Defaults to a single space ``' '``. To remove
                multiple different characters, pass them as a single string, e.g.,
                ``'_-'`` will strip underscores and hyphens.

        Returns:
            :class:`ColumnsOperation`: A new operation object representing the
            ``LTRIM`` expression. The object's ``_output`` attribute contains
            the SQL string and parameter list for use in queries.

        Example:
            Assuming a ``users`` table with a column ``username`` that may have
            leading spaces or special characters::

                from ormophine.Sqlite import Driver, Table

                db = Driver('my.db')
                users = db.users
                username = users.username

                # Remove leading spaces (default)
                trimmed = username.lstrip()
                # trimmed._output[0] -> "ltrim(users.[username],' ')"
                # trimmed._output[1] -> []

                # Remove leading underscores and hyphens
                trimmed_custom = username.lstrip('_-')
                # trimmed_custom._output[0] -> "ltrim(users.[username],'_-')"

                # Use in a query to get cleaned usernames
                results = users.get_row([trimmed], where=username != '')
                # retrieves rows with usernames trimmed on the left
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'ltrim({temp_ob._output[0]},"{chars}")', temp_ob._output[1]) if temp_ob._output else (f'ltrim({temp_ob.col_obj.name},"{chars}")', [])
        return temp_ob

    def rstrip(self, chars: str = ' '):
        """Just like python rstrip(), remove trailing characters from the column's string value.

        This method generates an SQL `rtrim` expression that strips specified
        characters from the right end of the column's string. The result is a
        :class:`ColumnsOperation` object that can be used in queries, updates,
        or as part of larger expressions.

        Args:
            chars (str, optional): A string containing the characters to remove
                from the right side of the column value. Defaults to a single
                space (``' '``).

        Returns:
            :class:`ColumnsOperation`: An operation object whose ``_output``
            attribute holds the SQL string and parameter list for the
            ``rtrim`` function call. This object can be chained with other
            operations or conditions.

        Example:
            Assuming a ``Product`` table with a ``name`` column::

                from ormophine.Sqlite import Driver

                db = Driver('store.db')
                products = db.products
                name_col = products.name

                # Remove trailing spaces
                clean_expression = name_col.rstrip()
                # clean_expression._output[0] -> "rtrim(products.[name],' ')"

                # Remove trailing hyphens and underscores
                clean_expression2 = name_col.rstrip('-_')
                # clean_expression2._output[0] -> "rtrim(products.[name],'-_')"

                # Use in an update to sanitize data
                products.update({name_col: name_col.rstrip()},
                                where=name_col.like('% '))
                # Updates rows where name ends with a space.
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'rtrim({temp_ob._output[0]},"{chars}")', temp_ob._output[1]) if temp_ob._output else (f'rtrim({temp_ob.col_obj.name},"{chars}")', [])
        return temp_ob

    def add_end(self, content):
        """Append content to the end of the column's value.

        This method generates a SQL string concatenation expression using the
        ``||`` operator, combining the column's value with the provided content
        at the end. The result is a :class:`ColumnsOperation` object that can be
        used in queries, updates, or other expressions. The content can be a
        literal value, another :class:`Column`, or a :class:`ColumnsOperation`
        expression.

        Args:
            content: The content to append. Can be one of:
                - A literal (``str``, ``int``, etc.) – the value is bound as a
                parameter.
                - A :class:`Column` – the column's value is concatenated.
                - A :class:`ColumnsOperation` – the result of that expression
                is concatenated.

        Returns:
            :class:`ColumnsOperation`: An operation object whose ``_output``
            attribute holds the SQL expression string and parameter list for
            the concatenation. This object can be further chained with other
            operations or used in conditions.

        Example:
            Assuming a ``users`` table with a ``full_name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.full_name

                # Append a suffix to the column value
                expr = name_col.add_end(' Jr.')
                # expr._output[0] -> "(users.[full_name] || ?)"
                # expr._output[1] -> [' Jr.']

                # Use in a query to retrieve concatenated value
                results = users.get_row([expr])
                # returns rows with full_name + ' Jr.'
        """
        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} || ?)', [content])
        return temp_ob

    def add_first(self, content):
        """Prepend content to the column value in a SQL string concatenation.

        This method generates a SQL expression that concatenates the given
        ``content`` before the column's value using the ``||`` operator.
        The result is a :class:`ColumnsOperation` object that can be embedded
        in ``SELECT``, ``WHERE``, or other SQL clauses. The method supports
        various input types:

        * A literal (e.g., ``str``, ``int``) – the literal is used as-is.
        * A :class:`Column` – concatenates the other column's value.
        * A :class:`ColumnsOperation` – concatenates a computed expression.

        The method is useful for building dynamic strings, such as prefixes,
        in SQL queries.

        Args:
            content: The content to prepend to the column's value. Can be one of:
                - A literal (``str``, ``int``, ``float``, etc.)
                - A :class:`Column` object
                - A :class:`ColumnsOperation` expression

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute contains the SQL string and parameter list for the
            concatenation. The SQL uses the form ``(? || column)`` for literals,
            or the appropriate column/expression references.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Prepend a literal prefix
                expr = name_col.add_first('Mr. ')
                # expr._output[0] -> "(? || users.[name])"
                # expr._output[1] -> ['Mr. ']

                # Prepend another column's value
                prefix_col = users.title
                expr2 = name_col.add_first(prefix_col)
                # expr2._output[0] -> "(users.[title] || users.[name])"

                # Use in a query
                result = users.get_row([expr], where=users.id == 1)
                # retrieves the concatenated string for the user with id=1
        """

        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'(? || {self.name})', [content])
        return temp_ob
    
    def lower(self):
        """Just like python lower(), convert the column value to lowercase in SQL.

        This method generates a SQL expression that applies the ``LOWER``
        function to the column's value. The result is a
        :class:`ColumnsOperation` object that can be used in ``SELECT``,
        ``WHERE``, or other SQL clauses to perform case‑insensitive
        comparisons or transformations.

        The returned operation can be chained with other operations (e.g.,
        :meth:`~ColumnsOperation.startswith`, :meth:`~ColumnsOperation.like`)
        or combined with logical operators (``&``, ``|``).

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute contains the SQL string for the ``LOWER`` function call
            (e.g., ``lower(users.[name])``) and an empty parameter list.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Create a condition for case‑insensitive equality
                condition = name_col.lower() == 'alice'
                # condition._output[0] -> "(lower(users.[name]) = ?)"
                # condition._output[1] -> ['alice']

                # Retrieve users whose name is 'alice' (case‑insensitive)
                results = users.get_row([name_col], where=condition)
        """
        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(), convert the column value to uppercase in SQL.

        This method generates a SQL `UPPER()` function call on the column's
        value, converting all characters to uppercase. The result is a
        :class:`ColumnsOperation` object that can be used in ``SELECT``,
        ``WHERE``, or other SQL clauses.

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute contains the SQL string and parameter list for the
            `UPPER()` function call.

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Create uppercase expression
                expr = name_col.upper()
                # expr._output[0] -> "upper(users.[name])"
                # expr._output[1] -> []

                # Use in a query
                results = users.get_row([expr], where=users.id == 1)
                # retrieves the uppercase name for the user with id=1
        """
        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(), replace occurrences of a substring within the column's value.

        This method generates a SQL expression that uses the ``replace()``
        function to substitute all occurrences of ``old`` with ``new``
        in the column's string value. The result is a
        :class:`ColumnsOperation` object that can be used in ``SELECT`` or
        other SQL clauses. The replacement is performed on the database side.

        The method automatically handles both simple column references and
        previously built expressions (e.g., after concatenation or substring
        operations) thanks to the internal state of the
        :class:`ColumnsOperation`.

        Args:
            old (str): The substring to be replaced. This is passed as a
                bound parameter (``?``) in the SQL.
            new (str): The replacement string. Also passed as a bound parameter.

        Returns:
            :class:`ColumnsOperation`: An expression object whose ``_output``
            attribute contains the SQL string and parameter list for the
            ``replace()`` call. The SQL string is either ``replace(column, ?, ?)``
            or ``replace(expression, ?, ?)`` if the operation was chained.
            The parameter list includes the ``old`` and ``new`` values.

        Example:
            Assuming a ``users`` table with a ``bio`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                bio_col = users.bio

                # Replace 'foo' with 'bar' in the bio column
                expr = bio_col.replace('foo', 'bar')
                # expr._output[0] -> "replace(users.[bio] , ? , ?)"
                # expr._output[1] -> ['foo', 'bar']

                # Chain with a substring operation
                expr2 = bio_col[0:10].replace('x', 'y')
                # expr2._output[0] -> "replace(substr(users.[bio] , ? , ?) , ? , ?)"
                # expr2._output[1] -> [1, 10, 'x', 'y']

                # Use in a SELECT query
                result = users.get_row([expr], where=users.id == 1)
                # retrieves the transformed bio for user with id=1
        """
        temp_ob = ColumnsOperation(self)
        temp_ob._output = (f'replace({temp_ob._output[0]} , ? , ?)', temp_ob._output[1] + [old, new]) if temp_ob._output else (f'replace({temp_ob.col_obj.name} , ? , ?)', [old, new])
        return temp_ob

    def like(self, value):
        """Create a SQL `LIKE` pattern-matching condition for the column.

        This method generates a SQL expression of the form ``column LIKE pattern``,
        where the pattern can be a literal string, another column, or a computed
        expression. The result is a :class:`ColumnsOperation` object suitable for
        use in ``WHERE`` clauses of queries, updates, or deletes.

        The method supports three types of input:

        * A literal (``str``, ``int``, etc.) – the literal is used as the pattern,
        with appropriate escaping and parameter binding.
        * A :class:`Column` – the pattern is taken from another column's value.
        * A :class:`ColumnsOperation` – the pattern is a computed expression.

        The SQL `LIKE` operator is case-sensitive by default in SQLite; to perform
        case-insensitive matches, use the `upper()` or `lower()` functions on both
        sides.

        Args:
            value: The pattern to match against. Can be one of:
                - A literal (e.g., ``'%john%'``) – the pattern is bound as a
                parameter.
                - A :class:`Column` – the pattern is the value of another column.
                - A :class:`ColumnsOperation` – the pattern is a computed expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the `LIKE`
            comparison. The SQL is typically of the form
            ``(column LIKE ?)`` or ``(column LIKE other_column)``.

        Example:
            Assuming a ``users`` table with columns ``name`` and ``search_pattern``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Match names containing 'john' (case-sensitive)
                condition = name_col.like('%john%')
                # condition._output[0] -> "(users.[name] like ?)"
                # condition._output[1] -> ['%john%']

                # Use another column as the pattern
                pattern_col = users.search_pattern
                condition2 = name_col.like(pattern_col)
                # condition2._output[0] -> "(users.[name] like users.[search_pattern])"

                # Combine with other conditions
                full_condition = condition & (users.id >= 100)
                results = users.get_row([name_col], where=full_condition)
        """
        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 ?', (temp_ob._output[1] + [f'{value}']) if temp_ob._output else [f'{value}'])
        return temp_ob

    def startswith(self, value):
        """Just like python startswith(), create a SQL ``LIKE`` condition to check if the column starts with a prefix.

        This method generates a ``LIKE`` expression with the pattern ``prefix || '%'``,
        where ``prefix`` is the provided value. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses.
        The method supports three types of input:

        * A literal (e.g., ``str``, ``int``) – the literal is used as the prefix.
        * A :class:`Column` – the column's value is used as the prefix.
        * A :class:`ColumnsOperation` – the computed expression is used as the prefix.

        The generated SQL uses the ``||`` concatenation operator to append the
        wildcard ``%``.

        Args:
            value: The prefix to test against. Can be one of:
                - A literal (e.g., ``'John'``) – the column value is compared
                to ``'John%'``.
                - A :class:`Column` – compares the column to the concatenation
                of that column's value and ``'%'``.
                - A :class:`ColumnsOperation` – compares the column to the
                concatenation of the expression's result and ``'%'``.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute contains the SQL string and parameter list for the
            ``LIKE`` comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Find users whose names start with 'Jo'
                condition = name_col.startswith('Jo')
                # condition._output[0] -> "(users.[name] like ? || '%')"
                # condition._output[1] -> ['Jo']

                # Use in a query
                results = users.get_row([name_col], where=condition)
                # retrieves rows where name LIKE 'Jo%'

                # Using another column as the prefix
                prefix_col = users.prefix
                condition2 = name_col.startswith(prefix_col)
                # condition2._output[0] -> "(users.[name] like users.[prefix] || '%')"
        """        
        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 ? || '%'", (temp_ob._output[1] + [f'{value}']) if temp_ob._output else [f'{value}'])
        return temp_ob

    def endswith(self, value):
        """Create a condition that checks if the column's value ends with a given suffix.

        This method generates a SQL `LIKE` expression that tests whether the column's
        text value ends with the specified suffix. The result is a
        :class:`ColumnsOperation` object that can be used in ``WHERE`` clauses.
        The suffix can be a literal string, another :class:`Column`, or a
        :class:`ColumnsOperation` expression.

        The generated SQL uses the pattern ``'%' || suffix``, which matches any
        string ending with the given suffix.

        Args:
            value: The suffix to match. Can be one of:
                - A literal (``str``, ``int``, etc.) – the suffix is bound as a parameter.
                - A :class:`Column` – the suffix is taken from another column's value.
                - A :class:`ColumnsOperation` – the suffix is computed from an expression.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute holds the SQL string and parameter list for the ``LIKE``
            comparison. This object can be combined with other conditions using
            logical operators.

        Example:
            Assuming a ``users`` table with a ``email`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                email_col = users.email

                # Check if email ends with '@example.com'
                condition = email_col.endswith('@example.com')
                # condition._output[0] -> "users.[email] like '%' || ?"
                # condition._output[1] -> ['@example.com']

                # Use in a query
                results = users.get_row([email_col], where=condition)
                # retrieves rows where email ends with '@example.com'

                # Compare with another column
                suffix_col = users.domain_suffix
                condition2 = email_col.endswith(suffix_col)
                # condition2._output[0] -> "users.[email] like '%' || users.[domain_suffix]"
        """
        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 '%' || ?", (temp_ob._output[1] + [f'{value}']) if temp_ob._output else [f'{value}'])
        return temp_ob

    def In(self, value):
        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(['?'] * len(value))})", list(value)) if isinstance(value, (list, tuple)) else (f"{self.name} = ?", [value])
        return temp_ob

    def contains(self, value):
        """Create a SQL ``LIKE`` condition to check if the column contains a substring.

        This method generates a ``LIKE`` expression with the pattern
        ``'%' || substring || '%'``, where the substring is the provided value.
        The result is a :class:`ColumnsOperation` object that can be used in
        ``WHERE`` clauses. The method supports three types of input:

        * A literal (e.g., ``str``, ``int``) – the literal is used as the substring.
        * A :class:`Column` – the column's value is used as the substring.
        * A :class:`ColumnsOperation` – the computed expression is used as the substring.

        The generated SQL uses the ``||`` concatenation operator to surround the
        substring with wildcards ``%``.

        Args:
            value: The substring to search for. Can be one of:
                - A literal (e.g., ``'John'``) – the column value must contain
                ``'John'``.
                - A :class:`Column` – compares the column to the concatenation
                of ``'%'``, that column's value, and ``'%'``.
                - A :class:`ColumnsOperation` – compares the column to the
                concatenation of ``'%'``, the expression's result, and ``'%'``.

        Returns:
            :class:`ColumnsOperation`: A condition object whose ``_output``
            attribute contains the SQL string and parameter list for the
            ``LIKE`` comparison. This object can be chained with other
            conditions using logical operators (``&``, ``|``).

        Example:
            Assuming a ``users`` table with a ``name`` column::

                from ormophine.Sqlite import Driver, Table

                db = Driver('app.db')
                users = db.users
                name_col = users.name

                # Find users whose names contain 'son'
                condition = name_col.contains('son')
                # condition._output[0] -> "(users.[name] like '%' || ? || '%')"
                # condition._output[1] -> ['son']

                # Use in a query
                results = users.get_row([name_col], where=condition)
                # retrieves rows where name LIKE '%son%'

                # Using another column as the substring
                substr_col = users.search_term
                condition2 = name_col.contains(substr_col)
                # condition2._output[0] -> "(users.[name] like '%' || users.[search_term] || '%')"
        """
        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 '%' || ? || '%'", (temp_ob._output[1] + [f'{value}']) if temp_ob._output else [f'{value}'])
        return temp_ob

    def rename(self, new_name: str) -> None:
        """Rename the column in the database schema.

        This method executes an ``ALTER TABLE RENAME COLUMN`` statement to change
        the column's name in the underlying SQLite table. After the schema change,
        the corresponding :class:`Column` attribute on the :class:`Table` object
        is replaced with a new :class:`Column` instance reflecting the updated name,
        preserving the original data type.

        The operation is performed through the thread‑safe queue mechanism of the
        ORM, and any SQL error will raise an exception.

        Args:
            new_name (str): The new name for the column. Must be a valid SQLite
                identifier (e.g., no spaces or special characters unless quoted).

        Raises:
            Exception: If the database operation fails (e.g., due to a duplicate
                column name, constraint violation, or connection error). The
                exception message is propagated from the underlying SQLite driver.

        Example:
            Assuming a ``users`` table with a column named ``user_name``::

                from ormophine.Sqlite import Driver

                db = Driver('app.db')
                users = db.users
                old_col = users.user_name

                # Rename the column from 'user_name' to 'full_name'
                old_col.rename('full_name')

                # The attribute is updated: `users.full_name` now exists
                new_col = users.full_name
                assert new_col.datatype == old_col.datatype

                # The old attribute is removed
                # users.user_name  # raises AttributeError
        """

        query = f'ALTER TABLE {self.table_obj.name_} RENAME COLUMN {self.first_name} TO [{new_name}];'
        queue_call_back = SimpleQueue()
        self.table_obj.main_queue.put(['qcb', (query,), queue_call_back])
        if not (callback := queue_call_back.get(block=True))[0]:
            raise Exception(callback[1])
        self.table_obj.__delattr__(self.first_name[1:-1])
        self.table_obj.__setattr__(new_name, Column(self.table_obj, new_name, self.datatype))

    def delete_column(self, are_you_sure: bool, are_you_really_sure: bool, for_sure: bool) -> None:
        """Permanently delete this column from the database table.

        This method executes an ``ALTER TABLE ... DROP COLUMN`` SQL statement
        to remove the column from the table. Because column deletion is a
        destructive operation that cannot be undone, the method requires three
        explicit confirmation flags to be set to ``True``. If any of them is
        ``False``, the operation is silently skipped.

        After a successful deletion, the column attribute is removed from the
        parent :class:`Table` object to keep the Python representation in sync
        with the database schema.

        Args:
            are_you_sure (bool): First confirmation flag. Must be ``True``.
            are_you_really_sure (bool): Second confirmation flag. Must be ``True``.
            for_sure (bool): Third confirmation flag. Must be ``True``.

        Returns:
            None

        Raises:
            Exception: If the SQL execution fails (e.g., due to a foreign key
                constraint or an invalid column), an exception is raised with
                the underlying database error message.

        Example:
            Assuming a ``users`` table with an ``age`` column::

                from ormophine.Sqlite import Driver

                db = Driver('myapp.db')
                users = db.users
                age_col = users.age

                # Delete the column (requires triple confirmation)
                age_col.delete_column(True, True, True)

                # Now the column is removed from the table and the attribute
                # is gone:
                # hasattr(users, 'age') -> False

                # If any flag is False, nothing happens:
                age_col.delete_column(True, False, True)  # no effect
        """
        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};'
            queue_call_back = SimpleQueue()
            self.table_obj.main_queue.put(['qcb', (query,), queue_call_back])
            if not (callback := queue_call_back.get(block=True))[0]:
                raise Exception(callback[1])
            self.table_obj.__delattr__(self.first_name[1:-1])

    def In(self, value):
        """Builds an ``IN`` condition for the column.

        Generates a :class:`ColumnsOperation` that represents an ``IN`` clause.
        If ``value`` is another :class:`ColumnsOperation`, its output is used
        directly. If ``value`` is a list or tuple, placeholders ``?`` are created
        for each element (e.g., ``col IN (?, ?, ?)``) and the values are
        collected for later binding. For a single scalar value, the method falls
        back to a simple equality condition (``col = ?``).

        Args:
            value (ColumnsOperation | list | tuple | Any): The set of values to
                test against. If a :class:`ColumnsOperation`, its SQL fragment
                and bound parameters are merged. If a list or tuple, the
                generated clause is ``IN (?, ?, ...)`` and the elements become
                bound parameters. If a single value, an equality ``= ?`` clause
                is produced.

        Returns:
            :class:`ColumnsOperation`: A new operation object that encapsulates
            the generated SQL fragment (e.g., ``[table].[col] IN (?, ?, ?)`` or
            ``[table].[col] = ?``) and the corresponding list of bound values.

        Example:
            >>> db = Driver('mydb.sqlite3')
            >>> users = db.users
            >>> # Find users with IDs 1, 2, or 3
            >>> condition = users.id.In([1, 2, 3])
            >>> rows = users.get_row([users.name], where=condition)
            >>>
            >>> # Find users whose role matches one of a set of names
            >>> roles = ['admin', 'moderator']
            >>> condition = users.role.In(roles)
            >>> rows = users.get_row([users.id], where=condition)
            >>>
            >>> # Single value falls back to equality
            >>> condition = users.id.In(10)  # equivalent to users.id == 10
            >>>
            >>> # in other tables
            >>> rows = users.get_row([users.id], where=users.name.In(ban_table.get_row([ban_table.name])))
        """
        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(['?'] * len(value))})", list(value)) if isinstance(value, (list, tuple)) else (f"{self.name} = ?", [value])
        return temp_ob


class BatchOperation:
    """
    A builder for atomic batch SQL operations.

    This class accumulates multiple SQL statements (INSERT and UPDATE)
    and executes them together as a single transaction via the
    :meth:`run` method. If any operation fails, the entire batch is
    rolled back, ensuring data consistency.

    Typical usage involves chaining :meth:`update` and :meth:`insert`
    calls, then executing with :meth:`run`. The class maintains an
    internal script list that is sent to the database thread for
    execution.

    Attributes:
        script (list): A list of SQL statement/parameter pairs
            representing the accumulated operations. Each element is a
            list in the form ``[sql_string, parameters]``.
        table_obj (Table): The :class:`Table` instance associated with
            this batch operation. Used to access the main database queue.

    Example:
        Assuming a ``users`` table with columns ``name`` and ``age``::

            from ormophine.Sqlite import Driver, Table

            db = Driver('app.db')
            users = db.users
            name_col = users.name
            age_col = users.age

            # Create a batch operation
            batch = users.batch()

            # Chain multiple operations
            batch.update({age_col: age_col + 1}, age_col >= 18)
            batch.update({name_col: name_col.upper()}, name_col.startswith('a'))
            batch.insert({name_col: 'Alice', age_col: 30})

            # Execute atomically
            batch.run()
            # All operations are committed as one transaction.
    """
    
    def __init__(self, table_object: Table):
        """Initialize a new batch operation builder for a specific table.

        The :class:`BatchOperation` class allows you to group multiple
        ``UPDATE`` and ``INSERT`` statements into a single script that
        is executed atomically in one transaction. This constructor
        creates a new batch builder associated with a given table.

        The batch is stored internally as a list of script items, each
        being a list of ``[sql, parameters]``. You can chain
        :meth:`update` and :meth:`insert` calls to build the script,
        then execute all statements with :meth:`run`.

        Args:
            table_object (Table): The table on which the batch operations
                will be performed. This table is used as the default target
                for operations unless overridden by providing a different
                table to :meth:`update` or :meth:`insert`.

        Example:
            Assuming a ``products`` table with columns ``id``, ``name``,
            and ``price``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products

                # Create a batch for products
                batch = products.batch()

                # Add multiple operations
                batch.insert({products.name: 'Laptop', products.price: 999})
                batch.update({products.price: 899}, where=products.id == 1)
                batch.insert({products.name: 'Mouse', products.price: 29})

                # Execute all at once
                batch.run()
        """
        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 script.

        This method appends a parameterized UPDATE SQL statement to the batch
        operation script. The statement updates the specified columns with new
        values for rows that match the given condition.

        The ``update`` dictionary maps :class:`Column` objects to their new values.
        Each value can be:

        * A literal (e.g., ``int``, ``str``, ``float``) – used as a parameter
        placeholder (``?``) in the SQL.
        * A :class:`Column` – the column's value is used as the source (e.g.,
        ``column1 = column2``).
        * A :class:`ColumnsOperation` – a computed expression (e.g., ``price + 5``)
        is embedded in the SQL.

        The ``where`` condition is a :class:`ColumnsOperation` that specifies which
        rows to update. If a different table should be updated (instead of the one
        associated with the batch object), it can be provided via the ``table``
        parameter.

        Args:
            update (dict[Column, Any]): A dictionary mapping columns to their new
                values. The values can be literals, :class:`Column` objects, or
                :class:`ColumnsOperation` expressions.
            where (ColumnsOperation): A condition expression that defines which
                rows should be updated.
            table (Table, optional): The table to update. If ``None``, the table
                associated with this batch operation is used.

        Returns:
            BatchOperation: The current batch operation instance (``self``),
            allowing method chaining for adding more statements.

        Example:
            Assuming a ``products`` table with columns ``price`` and ``stock``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products
                price = products.price
                stock = products.stock
                category = products.category

                # Create a batch operation
                batch = products.batch()

                # Update price for discounted products
                condition = category == 'clearance'
                batch.update(
                    update={price: price * 0.8, stock: stock - 1},
                    where=condition
                )

                # Add another update for a different table (if needed)
                # batch.update(update={...}, where=..., table=other_table)

                # Execute all statements in the batch
                batch.run()
                # This executes: UPDATE products SET price = price * 0.8, stock = stock - 1
                # WHERE category = 'clearance';
        """
        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}=?' 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 statement to the batch operation script.

        This method appends a new SQL INSERT command to the internal script
        of the batch operation. The statement inserts a single row into the
        specified table (or the table associated with this batch operation by
        default). The column–value pairs are provided as a dictionary, where
        each key is a :class:`Column` object and the corresponding value is
        the data to insert.

        The method supports values of any type; they are passed as parameters
        (``?`` placeholders) to prevent SQL injection. The batch operation can
        contain multiple statements (inserts, updates, etc.) that will be
        executed together when :meth:`run` is called.

        Args:
            insert (dict[Column, Any]): A mapping of columns to their values.
                Each key must be a :class:`Column` instance belonging to the
                target table, and the value is the data to be inserted.
            table (Table, optional): The table into which the row should be
                inserted. If not provided, the table associated with this
                :class:`BatchOperation` instance is used.

        Returns:
            BatchOperation: The current instance (``self``), allowing method
            chaining for building a multi‑statement batch.

        Example:
            Assuming a ``users`` table with columns ``name`` and ``age``::

                from ormophine.Sqlite import Driver

                db = Driver('app.db')
                users = db.users
                batch = users.batch()

                # Add an insert statement
                batch.insert({users.name: 'Alice', users.age: 30})

                # You can also specify a different table
                logs = db.logs
                batch.insert({logs.action: 'User created'}, table=logs)

                # Execute all batched statements
                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'?' 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 parameterized DELETE SQL statement to the
        internal script. The statement removes rows from the specified table
        (or the table associated with this batch operation by default) that
        match the given condition.

        The ``where`` condition is a :class:`ColumnsOperation` expression that
        defines which rows to delete. As with other batch methods, you can
        chain multiple calls to build a multi‑statement transaction that is
        executed atomically when :meth:`run` is called.

        Args:
            where (ColumnsOperation): A condition expression that specifies
                which rows should be deleted. For example,
                ``users.age < 18`` or ``products.stock == 0``.
            table (Table, optional): The table from which to delete rows.
                If ``None``, the table associated with this batch operation
                instance is used. This can be used to target a different
                table in the same batch.

        Returns:
            BatchOperation: The current batch operation instance (``self``),
            allowing method chaining for adding more statements or executing
            with :meth:`run`.

        Example:
            Assuming a ``products`` table with columns ``id``, ``stock``, and
            ``discontinued``::

                from ormophine.Sqlite import Driver

                db = Driver('store.db')
                products = db.products

                # Create a batch operation
                batch = products.batch()

                # Delete discontinued products with zero stock
                condition = (products.discontinued == True) & (products.stock == 0)
                batch.delete_row(where=condition)

                # You can also delete from another table
                logs = db.logs
                batch.delete_row(where=logs.timestamp < '2020-01-01', table=logs)

                # Execute all operations together
                batch.run()
                # This deletes matching rows in a single transaction.
        """
        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 in a single transaction.

        This method sends the accumulated script (list of SQL statements with
        their parameters) to the database connection thread via the table's
        main queue. The operations are executed as a batch, meaning they are
        committed together if all succeed, or rolled back entirely if any
        fails.

        The method blocks until the execution completes and a callback is
        received. If an error occurs during execution, an exception is raised
        with the underlying database error message.

        Returns:
            None

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

        Example:
            Assuming a ``products`` table with columns ``price`` and
            ``discount``::

                from ormophine.Sqlite import Driver, Table

                db = Driver('store.db')
                products = db.products

                # Create a batch operation
                batch = products.batch()

                # Add multiple operations
                price = products.price
                discount = products.discount
                batch.update({price: price * 1.1}, price < 100)
                batch.update({discount: discount + 5}, discount > 50)
                batch.insert({price: 200, discount: 20})

                # Execute all operations atomically
                batch.run()
                # All operations are committed together.
        """
        queue_call_back = SimpleQueue()
        self.table_obj.main_queue.put(['qsb', self.script, queue_call_back])
        if not (callback := queue_call_back.get(block=True))[0]:
            raise Exception(callback[1])


class Join:
    """
    A factory namespace for constructing SQL JOIN clauses.

    This class is not meant to be instantiated directly. Instead, it
    serves as a container for the nested classes :class:`Inner`,
    :class:`Left`, and :class:`Right`, each of which builds the
    corresponding SQL join type. Instances of these nested classes
    are passed to the :meth:`Table.join` method to perform multi‑table
    queries.

    The nested classes store their output in an ``_output`` attribute
    as a tuple ``(sql_string, parameters)``, which is consumed by
    :meth:`Table.join`.

    Example:
        Assuming a database with ``orders`` and ``customers`` tables::

            from ormophine.Sqlite import Driver, Table, Join

            db = Driver('store.db')
            orders = db.users
            customers = db.customers

            # Define the join condition
            condition = orders.customer_id == customers.id

            # Build different join types
            inner_join = Join.Inner(customers, condition)
            left_join = Join.Left(customers, condition)
            right_join = Join.Right(customers, condition)

            # Perform a query with a LEFT JOIN
            results = orders.join(
                columns=[orders.order_id, customers.name],
                joins_list=[left_join]
            )
            # Returns all orders with customer names (including orders without a customer).

    Note:
        While SQLite does not natively support ``RIGHT JOIN``, the ORM
        generates the syntax for compatibility with other database
        backends or for use with SQLite extensions.
    """

    class Inner:
        """
        Represents an INNER JOIN clause for a SQL query.

        This class is a nested class of :class:`Join` and is used to build
        the join part of a query. It stores the SQL fragment for an
        ``INNER JOIN`` along with its parameters.

        An ``INNER JOIN`` returns only rows where the join condition matches
        in both tables.

        Attributes:
            _output (tuple[str, list]): A 2‑tuple containing the SQL string
                for the join clause and the list of parameter values (if any).

        Example:
            Using ``Join.Inner`` in a query::

                from ormophine.Sqlite import Driver, Table, Join

                db = Driver('store.db')
                orders = db.users
                customers = db.customers

                # Create join condition
                condition = orders.customer_id == customers.id

                # Build INNER JOIN
                inner_join = Join.Inner(customers, condition)

                # Use in Table.join
                results = orders.join(
                    columns=[orders.order_id, customers.name],
                    joins_list=[inner_join]
                )
        """

        def __init__(self, table: Table, match_case_condition: ColumnsOperation):
            """
            Initializes an INNER JOIN clause.

            Args:
                table (Table): The table to join (the right side of the join).
                match_case_condition (ColumnsOperation): The condition
                    expression that defines how the tables are matched.
                    Typically created by comparing :class:`Column` objects.

            Example:
                >>> join_obj = Join.Inner(users, users.id == orders.user_id)
            """
            self._output = (f'INNER JOIN {table.name_} ON {match_case_condition._output[0]}', match_case_condition._output[1])


    class Left:
        """
        Represents a LEFT JOIN clause for a SQL query.

        This class is a nested class of :class:`Join` and is used to build
        the join part of a query. It stores the SQL fragment for a
        ``LEFT JOIN`` along with its parameters.

        A ``LEFT JOIN`` returns all rows from the left table, and matching
        rows from the right table. If no match exists, the right‑side columns
        contain ``NULL``.

        Attributes:
            _output (tuple[str, list]): A 2‑tuple containing the SQL string
                for the join clause and the list of parameter values (if any).

        Example:
            Using ``Join.Left`` in a query::

                from ormophine.Sqlite import Driver, Table, Join

                db = Driver('store.db')
                orders = db.users
                customers = db.customers

                condition = orders.customer_id == customers.id
                left_join = Join.Left(customers, condition)

                results = orders.join(
                    columns=[orders.order_id, customers.name],
                    joins_list=[left_join]
                )
        """

        def __init__(self, table: Table, match_case_condition: ColumnsOperation):
            """
            Initializes a LEFT JOIN clause.

            Args:
                table (Table): The table to join (the right side of the join).
                match_case_condition (ColumnsOperation): The condition
                    expression that defines how the tables are matched.
                    Typically created by comparing :class:`Column` objects.

            Example:
                >>> join_obj = Join.Left(products, products.category_id == categories.id)
            """
            self._output = (f'LEFT JOIN {table.name_} ON {match_case_condition._output[0]}', match_case_condition._output[1])


    class Right:
        """
        Represents a RIGHT JOIN clause for a SQL query.

        This class is a nested class of :class:`Join` and is used to build
        the join part of a query. It stores the SQL fragment for a
        ``RIGHT JOIN`` along with its parameters.

        A ``RIGHT JOIN`` returns all rows from the right table, and matching
        rows from the left table. If no match exists, the left‑side columns
        contain ``NULL``.

        Note that SQLite does not support ``RIGHT JOIN`` natively, but the
        ORM will generate the appropriate SQL syntax, which may be processed
        by other database engines or translated.

        Attributes:
            _output (tuple[str, list]): A 2‑tuple containing the SQL string
                for the join clause and the list of parameter values (if any).

        Example:
            Using ``Join.Right`` in a query::

                from ormophine.Sqlite import Driver, Table, Join

                db = Driver('store.db')
                orders = db.users
                customers = db.customers

                condition = orders.customer_id == customers.id
                right_join = Join.Right(customers, condition)

                results = orders.join(
                    columns=[orders.order_id, customers.name],
                    joins_list=[right_join]
                )
        """

        def __init__(self, table: Table, match_case_condition: ColumnsOperation):
            """
            Initializes a RIGHT JOIN clause.

            Args:
                table (Table): The table to join (the right side of the join).
                match_case_condition (ColumnsOperation): The condition
                    expression that defines how the tables are matched.
                    Typically created by comparing :class:`Column` objects.

            Example:
                >>> join_obj = Join.Right(employees, employees.department_id == departments.id)
            """
            self._output = (f'RIGHT JOIN {table.name_} ON {match_case_condition._output[0]}', match_case_condition._output[1])


class SetPragma:
    """
    A wrapper for executing SQLite PRAGMA commands on a database connection.

    This class provides convenient methods for setting various SQLite
    PRAGMA configurations, such as journal mode, synchronous mode, cache
    size, WAL settings, foreign key enforcement, and schema write access.
    All PRAGMA commands are executed via the main database queue, ensuring
    thread‑safe operations.

    The class is typically accessed through the :attr:`Driver.SetPragma`
    attribute, which is automatically created when a :class:`Driver`
    instance is initialized.

    Attributes:
        queue (SimpleQueue): The main queue of the :class:`Driver`
            instance used to send PRAGMA commands to the database thread.

    Example:
        Assuming a :class:`Driver` instance named ``db``::

            from ormophine.Sqlite import Driver

            db = Driver('my_database.db')

            # Set journal mode to WAL for better concurrency
            db.SetPragma.journal_mode('WAL')

            # Increase cache size to 10000 pages
            db.SetPragma.cache_size(10000)

            # Enable foreign key constraints
            db.SetPragma.foreign_keys(True)

            # Run a WAL checkpoint
            db.SetPragma.wal_checkpoint('FULL')

            # Optimize the database
            db.SetPragma.optimize()
    """
    
    def __init__(self, connector_obj):
        """Initialize a new SetPragma instance.

        This class provides methods to configure SQLite PRAGMA settings
        (e.g., journal mode, synchronous, cache size) for the database
        connection. The constructor stores a reference to the connector's
        main queue, which is used to execute PRAGMA statements in a
        thread‑safe manner via the database driver thread.

        Args:
            connector_obj: The parent object (typically an instance of
                :class:`Driver`) that holds the main queue used for
                sending commands to the database thread. The object must
                have a ``main_queue`` attribute of type :class:`queue.SimpleQueue`.

        Example:
            Assuming a ``Driver`` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')
                pragma = db.SetPragma

                # Configure the database
                pragma.journal_mode('WAL')
                pragma.synchronous('NORMAL')
                pragma.cache_size(10000)

        Note:
            The ``connector_obj`` is typically the :class:`Driver` instance
            that owns this ``SetPragma`` object. It is used internally to
            send PRAGMA commands to the database thread.
        """
        self.queue = connector_obj.main_queue

    def _exc(self, cmd: str, query: tuple):
        """Execute a database command and return the result.

        This internal method sends a command (with its query and parameters)
        to the database connection queue, blocks until the operation completes,
        and returns the result if successful, or raises an exception if the
        operation fails.

        The method is used by all public pragma‑setting methods in
        :class:`SetPragma` to centralize queue communication and error handling.

        Args:
            cmd (str): The command type to execute. Expected values are
                ``'qcb'`` (execute a single query that does not return rows),
                but other command types may be supported depending on the
                driver implementation.
            query (tuple): A tuple containing the SQL query and optional
                parameters. Typically of the form ``(sql_string,)`` or
                ``(sql_string, params)``.

        Returns:
            Any: The result returned by the database operation. For
            pragma statements, this is usually ``None`` on success, but
            may be a result set for other command types.

        Raises:
            Exception: If the callback indicates failure, the exception
                message from the callback is re‑raised.

        Example:
            >>> sp = SetPragma(driver)
            >>> sp._exc('qcb', ('PRAGMA journal_mode=WAL;',))
            >>> #OR driver.Setpragma._exc('qcb', ('PRAGMA journal_mode=WAL;',))
            None
        """
        queue_call_back = SimpleQueue()
        self.queue.put((cmd, query, queue_call_back))
        if (callback := queue_call_back.get(block=True))[0]:
            return callback[1]
        else:
            raise Exception(callback[1])

    def journal_mode(self, value: Literal["DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"]):
        """Set the journal mode for the database connection.

        This method executes the SQLite ``PRAGMA journal_mode`` command to
        change the way the database handles rollback journals. The journal
        mode affects performance, durability, and concurrency behavior.

        The available modes correspond to SQLite's standard journal modes:

        * ``DELETE`` – the default, a rollback journal is deleted after each transaction.
        * ``TRUNCATE`` – the journal is truncated to zero length instead of deleted.
        * ``PERSIST`` – the journal header is overwritten, avoiding file deletion.
        * ``MEMORY`` – the journal is stored in memory (fast but not durable).
        * ``WAL`` – Write-Ahead Logging, provides better concurrency.
        * ``OFF`` – no journaling (dangerous, can lead to corruption).

        The PRAGMA is executed synchronously, and the change takes effect
        immediately for the current database connection.

        Args:
            value (Literal["DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"]):
                The journal mode to set. Must be one of the allowed strings.

        Returns:
            None

        Raises:
            Exception: If the PRAGMA execution fails. The exception message
                will contain the underlying SQLite error.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                # Switch to WAL mode for better concurrency
                db.SetPragma.journal_mode('WAL')

                # Switch back to DELETE mode
                db.SetPragma.journal_mode('DELETE')
        """
        self._exc('qcb', (f"PRAGMA journal_mode = {value};",))

    def synchronous(self, value: Literal["OFF", "NORMAL", "FULL", "EXTRA"]):
        """Set the database synchronization mode.

        This method configures the SQLite ``synchronous`` pragma, which controls
        how aggressively the database engine writes data to disk. Higher levels
        provide better durability but may reduce performance.

        The available modes are:
            - ``OFF``: No synchronization, fastest but unsafe on power loss.
            - ``NORMAL``: Synchronizes at critical moments, a good balance.
            - ``FULL``: Maximum safety, ensures all data is written to disk
            before continuing.
            - ``EXTRA``: Even more synchronous than ``FULL`` (available in
            some SQLite versions).

        Args:
            value (Literal["OFF", "NORMAL", "FULL", "EXTRA"]): The
                synchronization level to set.

        Returns:
            None

        Raises:
            Exception: If the underlying SQLite operation fails, the exception
                is propagated from :meth:`_exc`.

        Example:
            Assuming a :class:`Driver` instance::

                from ormophine.Sqlite import Driver

                db = Driver('app.db')
                # Set to NORMAL for a balance of safety and performance
                db.SetPragma.synchronous('NORMAL')

                # Set to OFF for maximum performance (use with caution)
                db.SetPragma.synchronous('OFF')
        """
        self._exc('qcb', (f"PRAGMA synchronous = {value};",))

    def wal_autocheckpoint(self, pages: int):
        """Set the WAL autocheckpoint threshold.

        This method configures the number of pages after which SQLite
        automatically runs a checkpoint on the Write-Ahead Log (WAL). When
        the WAL file reaches the specified number of pages, SQLite will
        checkpoint the database, moving pages from the WAL file back into
        the main database file.

        Setting this value to 0 disables automatic checkpoints, leaving
        manual checkpointing via :meth:`wal_checkpoint` as the only option.

        Args:
            pages (int): The number of pages in the WAL file that trigger
                an automatic checkpoint. Must be a non-negative integer.
                A value of 0 disables automatic checkpointing.

        Raises:
            ValueError: If ``pages`` is not an integer or is negative.

        Example:
            .. code-block:: python

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')
                # Set autocheckpoint every 1000 pages
                db.SetPragma.wal_autocheckpoint(1000)

                # Disable automatic checkpoints
                db.SetPragma.wal_autocheckpoint(0)
        """
        if not isinstance(pages, int) or pages < 0:
            raise ValueError("pages must be non-negative integer")
        self._exc('qcb', (f"PRAGMA wal_autocheckpoint = {pages};",))

    def wal_checkpoint(self, mode: Literal["PASSIVE", "FULL", "RESTART", "TRUNCATE"] = "PASSIVE"):
        """Run a WAL checkpoint on the database.

        A WAL (Write-Ahead Log) checkpoint ensures that all transactions in
        the WAL file are written to the main database file. This can help
        reclaim disk space and improve performance. The mode determines how
        the checkpoint is performed:

        * ``PASSIVE`` – Checkpoint as many frames as possible without
        blocking other readers or writers. This is the default.
        * ``FULL`` – Checkpoint all frames, but may block other operations.
        * ``RESTART`` – Like FULL, but also restarts the WAL file so that
        future writes use a fresh WAL.
        * ``TRUNCATE`` – Like RESTART, but also truncates the WAL file to
        zero bytes, freeing disk space.

        This method sends a ``PRAGMA wal_checkpoint`` command to the database
        thread and waits for it to complete.

        Args:
            mode (Literal["PASSIVE", "FULL", "RESTART", "TRUNCATE"], optional):
                The checkpoint mode. Defaults to ``"PASSIVE"``.

        Returns:
            None

        Raises:
            Exception: If the checkpoint fails, an exception is raised with
                the underlying SQLite error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Run a full checkpoint
                db.SetPragma.wal_checkpoint('FULL')

                # Or use the default passive checkpoint
                db.SetPragma.wal_checkpoint()
        """
        self._exc('qcb', (f"PRAGMA wal_checkpoint({mode});",))

    def foreign_keys(self, enable: bool | Literal["ON", "OFF"]):
        """Enable or disable foreign key constraint enforcement.

        This method sets the ``PRAGMA foreign_keys`` option for the current
        database connection. When enabled (``ON``), SQLite will enforce
        foreign key constraints, rejecting operations that violate referential
        integrity. When disabled (``OFF``), foreign key constraints are
        ignored.

        The setting can be provided as a boolean (``True``/``False``) or as a
        string literal (``"ON"``/``"OFF"``). The method sends the appropriate
        PRAGMA command to the database thread and waits for execution.

        Args:
            enable (bool | Literal["ON", "OFF"]): Whether to enable foreign
                key enforcement. Accepts:
                - ``True`` or ``"ON"`` to enable.
                - ``False`` or ``"OFF"`` to disable.

        Returns:
            None

        Raises:
            Exception: If the PRAGMA command fails, an exception is raised
                with the underlying SQLite error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Enable foreign keys
                db.SetPragma.foreign_keys(True)

                # Disable foreign keys
                db.SetPragma.foreign_keys('OFF')

            Foreign key enforcement is often required for maintaining data
            integrity across related tables. Use this method to toggle the
            setting as needed for your operations.
        """
        val = "ON" if enable is True or enable == "ON" else "OFF"
        self._exc('qcb', (f"PRAGMA foreign_keys = {val};",))

    def defer_foreign_keys(self, enable: bool | Literal["ON", "OFF"]):
        """Enable or disable deferred foreign key enforcement.

        This method sets the ``PRAGMA defer_foreign_keys`` option, which controls
        whether foreign key constraints are deferred until the transaction is
        committed. When enabled (``ON``), foreign key constraints are not checked
        immediately after each statement, but only when the transaction is
        committed. This can be useful for complex operations that temporarily
        violate foreign key constraints during a transaction.

        The method accepts either a boolean (``True`` for ON, ``False`` for OFF)
        or the strings ``"ON"`` or ``"OFF"``.

        Args:
            enable (bool | Literal["ON", "OFF"]): The desired state. If ``True``
                or ``"ON"``, defer foreign key enforcement. If ``False`` or
                ``"OFF"``, enforce foreign keys immediately (default behavior).

        Returns:
            None

        Raises:
            Exception: If the pragma execution fails, an exception is raised
                with the underlying SQLite error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Enable deferred foreign keys
                db.SetPragma.defer_foreign_keys(True)

                # Perform operations that may temporarily violate foreign keys
                db.custom_execute('DELETE FROM orders WHERE customer_id = 1')
                db.custom_execute('DELETE FROM customers WHERE id = 1')

                # Commit automatically when the operation finishes,
                # and foreign key constraints are checked at commit time.

                # Disable deferred foreign keys
                db.SetPragma.defer_foreign_keys('OFF')
        """
        val = "ON" if enable is True or enable == "ON" else "OFF"
        self._exc('qcb', (f"PRAGMA defer_foreign_keys = {val};",))

    def cache_size(self, pages_or_kb: int):
        """Set the suggested maximum number of database disk pages that SQLite
        will hold in memory at one time.

        This method executes the `PRAGMA cache_size` command, which controls the
        number of pages in the page cache. A larger cache can improve performance
        for read-heavy workloads by reducing disk I/O, but consumes more memory.

        The value can be specified as either:
        - Positive integer: number of pages (default page size is usually 4096 bytes).
        - Negative integer: number of kilobytes of cache memory (e.g., -1024 means 1 MiB).

        The change is temporary and lasts only for the current database connection;
        it is not persisted across restarts.

        Args:
            pages_or_kb (int): The cache size. Positive values are interpreted as
                number of pages; negative values as kilobytes. For example,
                ``1000`` sets the cache to 1000 pages, while ``-1024`` sets it to
                1 MiB.

        Returns:
            None

        Raises:
            Exception: If the PRAGMA execution fails (e.g., due to a database error),
                an exception is raised with the underlying error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Set cache to 5000 pages
                db.SetPragma.cache_size(5000)

                # Set cache to 2 MiB (negative value means kilobytes)
                db.SetPragma.cache_size(-2048)
        """
        self._exc('qcb', (f"PRAGMA cache_size = {pages_or_kb};",))

    def mmap_size(self, bytes_size: int):
        """Set the maximum number of bytes used for memory-mapped I/O.

        This method configures the SQLite ``mmap_size`` pragma, which controls
        the maximum size of the memory-mapped I/O region for the database.
        Memory-mapped I/O can improve performance by allowing the operating
        system to cache database pages more efficiently. Setting this value
        to 0 disables memory-mapped I/O.

        The change takes effect immediately and persists until the database
        connection is closed or the pragma is set again.

        Args:
            bytes_size (int): The maximum size in bytes for the memory-mapped
                I/O region. Must be non-negative. A value of 0 disables
                memory-mapped I/O.

        Returns:
            None

        Raises:
            ValueError: If ``bytes_size`` is negative.
            Exception: If the underlying SQLite command fails (e.g., due to a
                database error), an exception with the error message is raised.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Enable memory-mapped I/O with a 256 MB limit
                db.SetPragma.mmap_size(256 * 1024 * 1024)

                # Disable memory-mapped I/O
                db.SetPragma.mmap_size(0)
        """
        if bytes_size < 0:
            raise ValueError("mmap_size cannot be negative")
        self._exc('qcb', (f"PRAGMA mmap_size = {bytes_size};",))

    def shrink_memory(self):
        """Release unused memory back to the operating system.

        This method executes the SQLite ``PRAGMA shrink_memory`` command,
        which attempts to free as much memory as possible from the database
        connection's internal caches and buffers. This can be useful in
        long‑running applications to reduce memory footprint after large
        operations.

        The command is non‑blocking and does not affect the database content.
        It is a best‑effort operation; the actual amount of memory freed
        depends on the system and SQLite's internal state.

        Returns:
            None

        Raises:
            Exception: If the pragma execution fails, an exception is raised
                with the underlying SQLite error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Perform memory‑intensive operations...
                # Then release unused memory
                db.SetPragma.shrink_memory()
        """
        self._exc('qcb', (f"PRAGMA shrink_memory;",))

    def optimize(self, mask: int = 0x10002):
        """Run the SQLite ``PRAGMA optimize`` command to optimize the database.

        This pragma triggers query planner optimizations and can improve
        performance by updating statistics and indexes. The optional
        ``mask`` parameter controls which optimizations are applied.
        The default value (``0x10002``) is recommended for general use.

        Args:
            mask (int, optional): A bitmask specifying the optimization
                settings. Defaults to ``0x10002``. Refer to SQLite
                documentation for valid mask values.

        Returns:
            None

        Raises:
            Exception: If the PRAGMA execution fails, an exception is
                raised with the underlying SQLite error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Run optimize with default mask
                db.SetPragma.optimize()

                # Run optimize with a custom mask
                db.SetPragma.optimize(mask=0x0001)
        """
        self._exc('qcb', (f"PRAGMA optimize({mask});",))

    def automatic_index(self, enable: bool | Literal["ON", "OFF"]):
        """Enable or disable automatic index creation by the SQLite query planner.

        This method sets the ``PRAGMA automatic_index``, which controls whether
        SQLite automatically creates temporary indexes to speed up queries when
        it determines they would be beneficial. Enabling this can improve
        performance for complex queries but may add overhead for index creation.

        Args:
            enable (bool | Literal["ON", "OFF"]): Whether to enable automatic
                indexing. Accepts:
                - ``True`` or ``"ON"`` to enable.
                - ``False`` or ``"OFF"`` to disable.

        Returns:
            None

        Raises:
            Exception: If the PRAGMA execution fails, an exception is raised
                with the underlying SQLite error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Enable automatic indexing
                db.SetPragma.automatic_index(True)

                # Disable it
                db.SetPragma.automatic_index('OFF')
        """
        val = "ON" if enable is True or enable == "ON" else "OFF"
        self._exc('qcb', (f"PRAGMA automatic_index = {val};",))

    def writable_schema(self, value: bool | Literal["ON", "OFF", "RESET"]):
        """Enable, disable, or reset write access to the database schema.

        This method executes the SQLite ``PRAGMA writable_schema`` command,
        which controls whether the ``sqlite_master`` table (the schema table)
        can be modified. By default, it is disabled (``OFF``). Enabling it
        allows direct modifications to the schema, which is useful for
        debugging or recovery but is extremely dangerous and should be used
        with caution. The ``RESET`` value reverts the pragma to its default
        state.

        Args:
            value (bool | Literal["ON", "OFF", "RESET"]): The desired state.
                - ``True`` or ``"ON"`` – enable writable schema.
                - ``False`` or ``"OFF"`` – disable writable schema.
                - ``"RESET"`` – reset to the default (effectively OFF).

        Returns:
            None

        Raises:
            Exception: If the PRAGMA execution fails, an exception is raised
                with the underlying SQLite error message.

        Example:
            Assuming a :class:`Driver` instance named ``db``::

                from ormophine.Sqlite import Driver

                db = Driver('my_database.db')

                # Enable writable schema (use with extreme caution!)
                db.SetPragma.writable_schema(True)

                # Perform schema modifications (e.g., manually update sqlite_master)
                # ... (dangerous operations)

                # Disable when done
                db.SetPragma.writable_schema(False)

                # Or reset to default
                db.SetPragma.writable_schema('RESET')
        """
        if value == "RESET":
            v = "RESET"
        else:
            v = "ON" if value is True or value == "ON" else "OFF"
        self._exc('qcb', (f"PRAGMA writable_schema = {v};",))


class Table:
    """Represents a database table with methods for data manipulation, schema changes, and queries.

    The `Table` class provides a high‑level, Pythonic interface to interact with a SQLite table.
    It dynamically creates `Column` attributes for each column, and offers methods for:
        - CRUD operations: `insert()`, `update()`, `get_row()`, `delete_row()`
        - Bulk operations: `bulk_insert()`, `bulk_update()`, `batch()`
        - Schema management: `add_column()`, `rename_column()`, `delete_column()`, `create_index()`
        - Joins: `join()` (using `Join` helper classes)
        - Raw SQL: `custom_execute()`, `custom_execute_many()`, `custom_execute_with_fetch()`

    All methods are **blocking** – each call waits until the operation is completed.
    Exceptions are raised immediately on failure.

    Attributes:
        name_ (str): Bracket‑wrapped table name, e.g., `'[users]'`.
        main_queue (SimpleQueue): Queue for sending commands to the writer thread.
        db_obj (Driver): Parent Driver instance.

    Examples:
        Basic CRUD:
        >>> db = Driver('company.db')
        >>> employees = db.employees  # Table object
        >>> employees.insert({employees.name: 'Alice', employees.salary: 5000})
        >>> employees.update({employees.salary: 5500}, where=employees.name == 'Alice')
        >>> rows = employees.get_row([employees.name, employees.salary], where=employees.salary > 4000)
        >>> employees.delete_row(where=employees.name == 'Alice')

        Selecting with expressions and slicing:
        >>> employees.get_row([employees.name.upper(), employees.salary * 1.1, employees.email[:-4]])

        Joining tables:
        >>> orders = db.orders
        >>> customers = db.customers
        >>> result = orders.join(
        ...     columns=[customers.name, orders.amount],
        ...     joins_list=[Join.Inner(customers, customers.id == orders.customer_id)],
        ...     where=orders.amount > 100
        ... )

        Bulk operations:
        >>> employees.bulk_insert([employees.name, employees.salary],
        ...                       [['Bob', 3000], ['Charlie', 3500]])
        >>> employees.bulk_update({employees.salary: '?'}, where=employees.name == '?',
        ...                       data_list=[[4000, 'Bob'], [4500, 'Charlie']])

        Batch (transaction):
        >>> (employees.batch()
        ...     .insert({employees.name: 'Dave', employees.salary: 2000})
        ...     .update({employees.salary: employees.salary + 500}, where=employees.name == 'Dave')
        ...     .run())

        Schema changes (dangerous, use with caution):
        >>> employees.add_column('department', str, default_value='general')
        >>> employees.rename_column(employees.department, 'dept')
        >>> employees.delete_column(employees.dept, True, True, True)

        Index management:
        >>> employees.create_index('idx_salary', [employees.salary])
        >>> employees.get_indexes()
        >>> employees.delete_index('idx_salary')
    """
    PLACE_HOLDER = '_MY_S4ULT3D_PL4C3_H0LD3R_?_'
    def __init__(self, obj: Driver, table_name: str):
        """
        Initialize a Table object representing a database table.

        This constructor fetches the table's schema information via
        :meth:`get_table_info` and dynamically creates :class:`Column`
        attributes for each column in the table. The column names become
        attributes on the Table instance, allowing access like
        ``table.column_name``. Additionally, a special attribute ``ROWID``
        is added to represent SQLite's implicit rowid.

        The table name is normalized with square brackets for safe SQL
        usage (e.g., ``[table_name]``). The Table object uses the provided
        :class:`Driver` instance to communicate with the database via its
        main queue and reader pool.

        Args:
            obj (Driver): The driver instance that manages the database
                connection and thread pool.
            table_name (str): The name of the table to represent.

        Raises:
            Exception: If the table schema cannot be retrieved (e.g., the
                table does not exist or the database is inaccessible).

        Example:
            Assuming a database with a ``users`` table::

                db = Driver('my.db')
                users = db.users #Tables are available as properties
                # Now users.name, users.age, etc., are Column objects.
                # Also users.ROWID is available.
        """
        self.name_= '['+table_name+']'
        self.main_queue: SimpleQueue= obj.main_queue
        self.db_obj= obj
        self.PLACE_HOLDER = '_MY_S4ULT3D_PL4C3_H0LD3R_?_'
        for i in self.get_table_info():
            self.__setattr__(i['name'], Column(self, i['name'], i['datatype']))
        self.__setattr__('ROWID', Column(self, 'ROWID', int))

    def _exc(self, cmd: str, query: tuple):
        """Send a command and query to the database thread and wait for the result.

        This is a low‑level internal method used by :class:`Table` to
        communicate with the main database thread via the driver's queue.
        It packages the command and query into a message, places it on the
        queue, and blocks until a response is received. If the execution
        succeeds, the result (or ``None``) is returned; otherwise, an
        exception is raised with the error message.

        Args:
            cmd (str): The command type to execute. Valid values include:
                - ``'qf'``: execute a query and fetch results.
                - ``'qcb'``: execute a query that commits changes.
                - ``'qsb'``: execute a script (batch) of statements.
                - ``'qmb'``: execute a query with multiple parameter sets.
            query (tuple): The SQL query string and optional parameters.
                Typically a 1‑ or 2‑tuple, e.g., ``(sql,)`` or ``(sql, params)``.

        Returns:
            Any: The result of the database operation if successful. For
                ``'qf'``, this is the fetched rows; for other commands,
                it is ``None``.

        Raises:
            Exception: If the database operation fails, an exception is
                raised with the underlying SQLite error message.

        Example:
            This method is used internally by other :class:`Table` methods::

                table._exc('qf', ('SELECT * FROM users WHERE id = ?', (1,)))
                # returns the fetched row(s) for the query

                table._exc('qcb', ('UPDATE users SET name = ? WHERE id = ?', ('Alice', 1)))
                # updates the user and returns None on success
        """
        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])
    
    def batch(self) -> 'BatchOperation':
        """Create a batch operation context bound to this table.

        Returns a :class:`BatchOperation` instance that allows multiple
        INSERT and UPDATE statements to be grouped into a single atomic
        transaction. The batch is executed by calling its :meth:`~BatchOperation.run`
        method, which sends all queued operations to the table's thread‑safe
        main queue and waits for the result.

        This is useful for bundling related changes that must succeed or fail
        together, avoiding intermediate commits.

        Returns:
            BatchOperation: A new batch operation object pre‑configured to use
                this table's queue and name.

        Raises:
            Exception: If any operation in the batch fails when :meth:`~BatchOperation.run`
                is called, the exception from SQLite is propagated.

        Example:
            Simple usage with literal values::

                table = db.employees
                batch = table.batch()
                batch.insert({table.name: "John", table.salary: 50000})
                batch.update({table.department: "Engineering"},
                            where=table.id.eq(42))
                batch.run()

        Example:
            Complex usage using :class:`ColumnsOperation` expressions for both
            the value to be set and the condition::

                batch = table.batch()
                # Give a 10% raise to everyone in 'Sales' earning less than 60000
                batch.update(
                    {table.salary: table.salary * 1.10},
                    where=(table.department == "Sales") & (table.salary < 60000)
                )
                # Set display_name to first_name + ' ' + last_name for a specific row
                batch.update(
                    {table.display_name: table.first_name.add_end(" ").add_end(table.last_name)},
                    where=table.id.eq(99)
                )
                batch.run()
        """
        return BatchOperation(self)
    
    def update(self, update: dict[Column, Any], where: 'ColumnsOperation') -> None:
        """Updates rows in the table that match the given condition.

        Constructs and executes an ``UPDATE`` SQL statement, setting column values
        as specified in the ``update`` dictionary for all rows where the
        ``where`` condition holds. The values can be plain Python scalars,
        other :class:`Column` objects (to copy values between columns), or
        :class:`ColumnsOperation` instances (to use SQL expressions).
        Placeholders (``?``) are automatically generated for scalar values;
        column references and operation outputs are embedded directly into the
        SQL.

        Args:
            update: A dictionary mapping :class:`Column` instances to the new
                value. The value can be:

                * A plain Python scalar (``int``, ``float``, ``str``, ``bytes``).
                It will be passed as a parameter.
                * Another :class:`Column` object – the column's value will be
                copied.
                * A :class:`ColumnsOperation` representing a SQL expression
                (e.g., arithmetic, string concatenation). Its parameters are
                merged into the query's parameter list.
            where: A :class:`ColumnsOperation` representing the ``WHERE``
                clause condition. It must be created from column comparisons
                (e.g., using :meth:`Column.eq`, ``==``, ``>``, etc.).

        Returns:
            None

        Raises:
            Exception: If the database operation fails. The exception message
                contains details from SQLite.

        Example:
            Simple update with scalar values::

                # Assume driver and table are already set up
                users = driver.users
                name_col = users.name
                age_col = users.age

                # Update age to 30 where name is 'Alice'
                users.update(
                    update={age_col: 30},
                    where=name_col == 'Alice'
                )

            Complex update with a :class:`ColumnsOperation` expression::

                # Increment the 'score' column by 10 for all rows where
                # the 'level' column is greater than 5.
                score_col = users.score
                level_col = users.level

                # Create an operation: score + 10
                score_plus_10 = score_col + 10   # returns a ColumnsOperation

                users.update(
                    update={score_col: score_plus_10},
                    where=level_col > 5
                )

            Using column-to-column copy and string concatenation::

                fullname_col = users.fullname
                first_col = users.first
                last_col = users.last

                # Concatenate first and last name into fullname
                fullname_expr = first_col.add_end(' ').add_end(last_col)
                users.update(
                    update={fullname_col: fullname_expr},
                    where=fullname_col == ''  # only empty fullnames
                )
        """
        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 = (f'UPDATE {self.name_} SET {', '.join(f'{key.first_name} = {value.first_name}' if isinstance(value , Column) else f'{key.first_name}=?' 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])
        self._exc('qcb', query)

    def get_table_info(self, from_readers_pool: bool = False):
        """Retrieves column metadata for the table from the SQLite database.

        Uses the ``PRAGMA table_info({self.name_})`` statement to fetch column
        details, including name, data type, nullability, default value, and
        primary key status. The data type is mapped to Python types: ``int``
        for ``INTEGER``, ``str`` for ``TEXT``, ``float`` for ``REAL``/
        ``NUMERIC``, ``bytes`` for ``BLOB``; otherwise defaults to ``str``.

        Args:
            from_readers_pool (bool): If ``False`` (default), the query is
                executed on the main writer connection. If ``True``, a
                connection from the reader pool is obtained, allowing
                non‑blocking reads in multi‑threaded scenarios.

        Returns:
            list[dict]: A list of column information dictionaries, each with
            the following keys:
                - ``id`` (int): column ID (position).
                - ``name`` (str): column name.
                - ``datatype`` (type): Python type inferred from SQL type.
                - ``notnull`` (int): 1 if NOT NULL, 0 otherwise.
                - ``default_value`` (Any|None): default value if set.
                - ``primary_key`` (int): 1 if part of primary key, 0 otherwise.

        Raises:
            Exception: If the database query or reader‑pool acquisition fails.

        Example:
            >>> users = driver.users
            >>> cols = users.get_table_info()
            >>> for col in cols:
            ...     print(f"{col['name']}: {col['datatype']}")
            id: <class 'int'>
            username: <class 'str'>
            ...
            >>> # Using a reader‑pool connection:
            >>> cols_async = users.get_table_info(from_readers_pool=True)
        """
        query = f'PRAGMA table_info({self.name_})'
        if not from_readers_pool:
            columns = self._exc('qf', (query,))
        else:
            queueCallBack = SimpleQueue()
            connection_queue = self.db_obj.pool_holder.get(block=True)
            connection_queue.put(['qf', (query,), queueCallBack])
            if (callback := queueCallBack.get(block=True))[0]:
                columns = callback[1]
            else:
                raise Exception(callback[1])
            self.db_obj.pool_holder.put(connection_queue)
        
        return [{'id':i[0], 'name':i[1], 'datatype':int if 'INTEGER' in i[2]  else str if 'TEXT' in i[2] else float if 'REAL' in i[2] else bytes if 'BLOB' in i[2] else float if 'NUMERIC' in i[2] else str, 'notnull': i[3], 'default_value':i[4], 'primary_key':i[5]}for i in columns]

    def get_columns_name(self, from_readers_pool: bool = False) -> list[str]:
        """Retrieve the names of all columns in the table.

        Fetches column metadata using ``PRAGMA table_info`` and returns a list
        containing only the column name strings.  This is a convenience wrapper
        around :meth:`get_table_info` that discards other column details.

        The operation can be performed on either the main writer connection
        (default) or on a dedicated reader thread from the connection pool.
        When ``from_readers_pool`` is ``True``, the method acquires a reader
        queue from :attr:`Driver.pool_holder`, executes the query there, and
        returns the queue back to the pool.  This avoids blocking the writer
        thread and is suitable for read-heavy workloads.

        Args:
            from_readers_pool (bool): If ``True``, use a read-only connection
                from :attr:`Driver.pool_holder`.  Defaults to ``False``, which
                sends the query through the main writer connection.

        Returns:
            list[str]: A list of column names as plain strings (without brackets
            or escaping).

        Raises:
            Exception: Propagated from the underlying execution if the query
                fails or the reader callback reports an error.

        Example:
            >>> table = db.users
            >>> cols = table.get_columns_name()
            >>> print(cols)
            ['id', 'name', 'email']
        """        
        query = f'PRAGMA table_info({self.name_})'
        
        if not from_readers_pool:
            columns = self._exc('qf', (query,))
        else:
            queueCallBack = SimpleQueue()
            connection_queue = self.db_obj.pool_holder.get(block=True)
            connection_queue.put(['qf', (query,), queueCallBack])
            if (callback := queueCallBack.get(block=True))[0]:
                columns = callback[1]
            else:
                raise Exception(callback[1])
            self.db_obj.pool_holder.put(connection_queue)
        return [i[1] for i in columns]

    def get_row(
        self,
        which_columns: list['Column' | 'ColumnsOperation'],
        where: 'ColumnsOperation' = None,
        order_by: 'Column' = None,
        from_readers_pool: bool = False
    ) -> list[Any] | list[tuple]:
        """Fetch rows from the table with optional filtering, ordering, and expression columns.

        Builds and executes a ``SELECT`` query on the table. The columns to retrieve can be
        plain :class:`Column` objects or complex :class:`ColumnsOperation` expressions.
        If a single column/operation is requested, a flat list of values is returned;
        otherwise a list of tuples (one per selected column) is returned.

        Args:
            which_columns (list): List of columns or operations to select. Each element can be a
                :class:`Column` instance (retrieves its raw value) or a
                :class:`ColumnsOperation` object (evaluates the expression in SQL).
            where (:class:`ColumnsOperation`, optional): Filtering condition. Only rows for which
                the condition evaluates to true are included. Defaults to ``None`` (all rows).
            order_by (:class:`Column`, optional): Column to order results by. If omitted, ordering
                falls back to the ``ROWID`` pseudo-column.
            from_readers_pool (bool, optional): If ``True``, the query is dispatched to a dedicated
                reader thread from the connection pool, which can improve concurrency for
                read‑heavy workloads. Defaults to ``False``.

        Returns:
            list: If ``which_columns`` contains a single element, a flat list of column values
            (e.g., ``['Alice', 'Bob']``). Otherwise a list of tuples, each tuple containing
            the selected column values in the same order as ``which_columns``.

        Raises:
            Exception: If the underlying SQL execution fails. The exception message contains
                the database error details.

        Example:
            Simple retrieval of a single column:

            >>> names = my_table.get_row([my_table.name])
            >>> print(names)
            ['Alice', 'Bob']

            Retrieving multiple columns:

            >>> rows = my_table.get_row([my_table.name, my_table.age])
            >>> for name, age in rows:
            ...     print(f"{name} is {age} years old")

            Adding a filter and ordering:

            >>> adults = my_table.get_row(
            ...     [my_table.name],
            ...     where=my_table.age > 18,
            ...     order_by=my_table.name
            ... )
            >>> print(adults)
            ['Charlie', 'Diana']

            Using a :class:`ColumnsOperation` expression (arithmetic, concatenation, etc.):

            >>> full_name = my_table.first_name + ' ' + my_table.last_name  # __add__ on Column creates ColumnsOperation
            >>> # Filtering on the computed column
            >>> condition = full_name.contains('John')
            >>> results = my_table.get_row([full_name, my_table.age], where=condition)
            >>> for name, age in results:
            ...     print(f"Full name: {name}, Age: {age}")

            Slicing and string operations:

            >>> first_initial = my_table.name[:1]  # substring just like python
            >>> initials_and_ages = my_table.get_row([first_initial, my_table.age], where=my_table.age >= 30)
        """

        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]
        
        query = (f'SELECT {', '.join(wc)} FROM {self.name_} WHERE {where._output[0]} ORDER BY {order_by.first_name if order_by else 'ROWID'};', tl+where._output[1]) if where else (f'SELECT {', '.join(wc)} FROM {self.name_} ORDER BY {order_by.first_name if order_by else 'ROWID'};',tl) if tl else (f'SELECT {', '.join(wc)} FROM {self.name_} ORDER BY {order_by.first_name if order_by else 'ROWID'};',)
        if not from_readers_pool:
            return [row[0] for row in self._exc('qf', query)] if len(which_columns) == 1 else self._exc('qf', query)
        else:
            queueCallBack = SimpleQueue()
            connection_queue = self.db_obj.pool_holder.get(block=True)
            connection_queue.put(['qf', query, queueCallBack])
            self.db_obj.pool_holder.put(connection_queue)
            if (callback := queueCallBack.get(block=True))[0]:
                return [row[0] for row in callback[1]] if len(which_columns) == 1 else callback[1]
            else:
                raise Exception(callback[1])

    def insert(self, insert: dict['Column', Any]) -> None:
        """Insert a single row into the table.

        Builds and executes an ``INSERT INTO`` statement using the provided mapping
        of :class:`Column` objects to their values. All values are parameterised to
        prevent SQL injection.

        Args:
            insert (dict[:class:`Column`, Any]): A dictionary where each key is a
                :class:`Column` instance belonging to this table, and the corresponding
                value is the Python data to store in that column.

        Returns:
            None

        Raises:
            Exception: If the underlying database operation fails (e.g., constraint
                violation, syntax error). The exception message contains the database
                error details.

        Example:
            Simple insertion of a single row with literal values::

                db = Driver('my_db.sqlite')
                user_table = db.user_table  # User table already created

                user_table.insert({
                    user_table.name: 'Alice',
                    user_table.age: 30,
                    user_table.email: 'alice@example.com'
                })

            Insert a row and then verify it using a :class:`ColumnsOperation` in a
            subsequent ``get_row`` call::

                # Insert a product
                products_table.insert({
                    products_table.name: 'Widget',
                    products_table.price: 19.99,
                    products_table.quantity: 150
                })

                # Retrieve products with low stock using an operation
                low_stock = products_table.quantity < 50
                result = products_table.get_row(
                    [products_table.name],
                    where=low_stock
                )
                print(result)  # [] because quantity is 150

                # Now insert a product that meets the low-stock condition
                products_table.insert({
                    products_table.name: 'Gadget',
                    products_table.price: 9.99,
                    products_table.quantity: 30
                })

                result = products_table.get_row(
                    [products_table.name],
                    where=low_stock
                )
                print(result)  # ['Gadget']
        """
        query = (f'INSERT INTO {self.name_} ({', '.join(i.first_name for i in list(insert.keys()))}) VALUES ({', '.join(f'?' for k in insert)})', [v for v in list(insert.values())])
        self._exc('qcb', query)
 
    def custom_execute(self, query: str, params: list = None) -> None:
        """Executes a raw SQL statement on the table's database connection.

        Sends the query to the writer queue and waits for completion. The statement
        is executed immediately and committed on success; on failure a rollback is
        performed and an exception is raised. This method is intended for data
        manipulation statements (INSERT, UPDATE, DELETE, DDL, etc.) that do not
        return rows.

        Args:
            query (str): The SQL statement to execute. Placeholders (``?``) are
                allowed for parameterised queries.
            params (list, optional): A list of parameters to bind to the
                placeholders in `query`. Defaults to None, in which case the
                statement is executed without parameters.

        Returns:
            None

        Raises:
            Exception: If the database operation fails. The original exception
                from sqlite3 is propagated.

        Example:
            >>> users_table.custom_execute(
            ...     "INSERT INTO [users] (name, age) VALUES (?, ?)",
            ...     ["Alice", 30]
            ... )
            >>> # Simple DDL
            >>> users_table.custom_execute("CREATE INDEX idx_name ON [users](name)")
        """        
        self._exc('qcb', (query, params)) if params else self._exc('qcb', (query,))
            
    def custom_execute_many(self, query: str, params: list = None) -> None:
        """Executes a single SQL statement against multiple parameter sets.

        This method sends a ``qmb`` (query many batch) command to the
        underlying driver thread, which uses ``cursor.executemany()`` to
        efficiently process the same statement with different bound values.
        It is ideal for bulk inserts, updates, or deletes where the SQL
        structure remains identical but the data changes.

        Note:
            This is a non‑fetching operation; no result set is returned.

        Args:
            query (str): The SQL statement to execute. Use ``?`` placeholders
                for parameter binding.
            params (list, optional): An iterable of parameter sequences (e.g.,
                list of tuples or lists). Each item must match the number of
                placeholders in ``query``. If ``None``, the statement is
                executed once without parameters (though ``executemany`` with
                no parameters is equivalent to a single ``execute``).

        Returns:
            None

        Raises:
            Exception: If the database operation fails (e.g., constraint
                violation, malformed SQL). The original exception from the
                driver thread is propagated.

        Example:
            Batch‑insert users using a custom statement::

                users = db.users
                users.custom_execute_many(
                    "INSERT INTO users (username, score) VALUES (?, ?)",
                    [
                        ("alice", 95),
                        ("bob", 87),
                        ("carol", 92)
                    ]
                )

            This is equivalent to, but often more convenient than, building
            a :class:`BatchOperation` manually.
        """
        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 a custom SQL query and return fetched results.

        Sends the provided SQL query and optional parameters to the database
        connection. If ``from_readers_pool`` is ``False`` (default), the query
        is executed on the main writer connection; otherwise a connection from
        the non‑blocking reader pool is used. The result is the same as
        ``cursor.fetchall()`` – a list of tuples, each representing a row.

        Args:
            query (str): The SQL statement to execute. Placeholders ``?`` can
                be used for parameterized queries.
            params (list, optional): A list of values to bind to the
                placeholders in ``query``. Defaults to ``None``.
            from_readers_pool (bool): If ``True``, the query is run against a
                reader‑pool connection (non‑blocking). Defaults to ``False``.

        Returns:
            Any: The rows returned by the query as a list of tuples. For
            example, ``[(val1, val2), ...]``.

        Raises:
            Exception: If the database operation fails or a reader‑pool
                connection cannot be acquired.

        Example:
            Assuming ``users`` is a :class:`Table` instance:

            >>> rows = users.custom_execute_with_fetch(
            ...     "SELECT id, username FROM users WHERE age > ?",
            ...     params=[18]
            ... )
            >>> for row in rows:
            ...     print(row)
            (1, 'alice')
            (2, 'bob')

            To use a reader pool connection for better concurrency:

            >>> rows = users.custom_execute_with_fetch(
            ...     "SELECT COUNT(*) FROM users",
            ...     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.db_obj.pool_holder.get(block=True)
            connection_queue.put(['qf', (query, params), queueCallBack]) if params else connection_queue.put(['qf', (query,), queueCallBack])
            self.db_obj.pool_holder.put(connection_queue)
            if (callback := queueCallBack.get(block=True))[0]:
                return callback[1]
            else:
                raise Exception(callback[1])

    def delete_row(self, where: 'ColumnsOperation') -> None:
        """Deletes all rows from the table that match the given condition.

        Constructs a ``DELETE FROM ... WHERE ...`` SQL statement using the
        provided :class:`ColumnsOperation` expression. The operation is sent
        to the writer queue and executed atomically in a thread‑safe manner.

        Args:
            where (ColumnsOperation): A condition expression built from
                :class:`Column` objects and comparison methods (e.g.,
                :meth:`Column.eq`, :meth:`Column.gt`). The
                :attr:`ColumnsOperation._output` attribute contains the SQL
                fragment and the corresponding bind parameters.

        Returns:
            None: This method does not return a value.

        Raises:
            Exception: If the underlying SQL execution fails (e.g., constraint
                violation, syntax error in the condition). The exception
                message includes details from the database driver.

        Example:
            >>> # Assume `db` is a Driver instance and `users` is a Table.
            >>> users = db.users
            >>> # Delete all users with an age less than 18.
            >>> condition = users.age < 18
            >>> users.delete_row(condition)
        """
        query = (f'DELETE FROM {self.name_} WHERE {where._output[0]};', where._output[1])
        self._exc('qcb', query)

    def delete_table(self, are_you_sure: bool, are_you_really_sure: bool, for_sure: bool) -> None:
        """Drops the table from the database and removes it from the driver.

        This method executes a ``DROP TABLE`` statement, permanently deleting
        the table and all its data. As a safety measure, all three boolean
        flags must be ``True`` for the operation to proceed. After successful
        deletion, the table's attribute is removed from the parent
        :class:`Driver` instance, making it inaccessible through the ORM.

        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 mutates the database and the driver state, but
            returns nothing.

        Raises:
            Exception: If any of the confirmation flags are not ``True``, or
                if the database operation fails (e.g., the table does not exist).

        Example:
            >>> db = Driver('mydb.sqlite')
            >>> # assuming a table 'temp_logs' exists
            >>> temp_logs = db.table_object('temp_logs')
            >>> temp_logs.delete_table(True, True, True)
            >>> # Now the table is dropped and 'temp_logs' attribute is gone
            >>> 'temp_logs' in dir(db)
            False
        """
        if are_you_sure and are_you_really_sure and for_sure:
            query = f'DROP TABLE {self.name_};'
            self._exc('qcb', (query,))
            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:
        """Drops a column from the table permanently.

        Executes an ``ALTER TABLE ... DROP COLUMN`` statement on the database
        and removes the corresponding attribute from the :class:`Table` object.
        The operation is gated by three explicit boolean flags that must all be
        ``True`` to proceed – this is a safety mechanism to prevent accidental
        column deletion.

        Args:
            column (Column): The column object to be deleted. Must belong to
                this table.
            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 column is dropped from the schema and the attribute is
            removed from the :class:`Table` instance.

        Raises:
            Exception: If the database operation fails (e.g., the column does
                not exist, or the table is locked). The original error from the
                writer thread is re‑raised.

        Note:
            After successful deletion, the :class:`Column` object passed as
            ``column`` is no longer valid for queries because its underlying
            database column no longer exists. The attribute is also removed
            from the table object, so accessing it later will raise an
            :class:`AttributeError`.

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> users = db.users
            >>> # Delete the "age" column with triple confirmation
            >>> age_col = users.age  # Column instance
            >>> users.delete_column(
            ...     age_col,
            ...     are_you_sure=True,
            ...     are_you_really_sure=True,
            ...     for_sure=True
            ... )
            >>> # Now users.age will raise AttributeError
        """
        if are_you_sure and are_you_really_sure and for_sure:
            query = f'ALTER TABLE {self.name_} DROP COLUMN {column.first_name};'
            self._exc('qcb', (query,))
            self.__delattr__(column.first_name[1:-1])

    def add_column(self, column_name: str, datatype: int|str|float|bytes, default_value=None, not_null: bool=None) -> None:
        """Adds a new column to the table.

        Executes an ``ALTER TABLE ... ADD COLUMN`` statement using the provided
        ``datatype`` string. The string is expected to come from one of the
        :class:`DataTypes` static methods (e.g., ``DataTypes.INTEGER()``) and
        contains the placeholder ``my_saulted_x``, which is automatically
        replaced with the actual column name. After the database operation
        succeeds, a corresponding :class:`Column` attribute is set on the
        :class:`Table` instance, making it immediately available for queries.

        Args:
            column_name (str): The name of the new column.
            datatype (int | str | float | bytes): The SQL column definition
                string, typically obtained from a :meth:`DataTypes` method.
                The placeholder ``my_saulted_x`` inside this string will be
                replaced with ``column_name``.
            default_value: The default value for the column. If the value is a
                string, it is automatically quoted in the SQL. Defaults to
                ``None`` (no DEFAULT clause).
            not_null (bool): If ``True``, a ``NOT NULL`` constraint is added
                to the column definition. Defaults to ``None``.

        Returns:
            None: The column is added to the database schema and the attribute
            is created on the :class:`Table` object.

        Raises:
            Exception: If the database operation fails (e.g., the column
                already exists, the table is locked, or the ``datatype``
                string is invalid). The original error from the writer thread
                is re‑raised.

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> users = db.users
            >>> # Add a TEXT column with a default value
            >>> users.add_column(
            ...     "status",
            ...     DataTypes.TEXT(max_length=20),
            ...     default_value="active",
            ...     not_null=True
            ... )
            >>> # Now users.status is a Column object
            >>> print(users.status.first_name)
            [status]
        """
        query = f'ALTER TABLE {self.name_} ADD COLUMN {datatype.replace('my_saulted_x',column_name)}{' NOT NULL' if not_null else ''}{f' DEFAULT {f"'{default_value}'" if type(default_value) == str else default_value}' if default_value else ''}'
        self._exc('qcb', (query,))
        self.__setattr__(column_name, Column(self, column_name, int if 'INTEGER' in datatype  else str if 'TEXT' in datatype else float if 'REAL' in datatype else bytes if 'BLOB' in datatype else float if 'NUMERIC' in datatype else str))

    def rename_table(self, new_name: str) -> None:
        """Renames the table in the database and updates all associated objects.

        Executes an ``ALTER TABLE ... RENAME TO`` statement to change the
        table's name. On success, the old attribute on the :class:`Driver`
        object is removed, a new :class:`Table` attribute with the new name is
        added, and this instance's :attr:`name_` is updated to reflect the new
        name.

        Args:
            new_name (str): The new name for the table (without brackets;
                they will be added automatically).

        Returns:
            None: The table is renamed in place; the :class:`Table` instance
            itself is mutated and the :class:`Driver` attribute is updated.

        Raises:
            Exception: If the ``ALTER TABLE`` command fails (e.g., a table
                with the new name already exists, or the database is locked).
                The original error from the writer thread is re‑raised.

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> old_users = db.users  # original Table object
            >>> old_users.rename_table("clients")
            >>> # Now db.clients exists, db.users no longer does
            >>> # The old_users variable still refers to the same Table
            >>> # instance, but its name_ attribute is now '[clients]'
            >>> print(db.clients.name_)
            [clients]
        """
        query = f'ALTER TABLE {self.name_} RENAME TO {new_name};'
        self._exc('qcb', (query,))
        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:
        """Renames an existing column in the table.

        Executes an ``ALTER TABLE ... RENAME COLUMN`` statement to change the
        column name in the database schema. After a successful rename, the
        original :class:`Column` attribute is removed from the :class:`Table`
        instance and a new :class:`Column` attribute with the same datatype
        is added under the new name.

        Args:
            column (Column): The column object to rename. Must be an existing
                column of this table.
            new_name (str): The new name for the column (without brackets).

        Returns:
            None: The table schema and the object's internal attribute
            mapping are updated in place.

        Raises:
            Exception: If the database operation fails (e.g., column does not
                exist, table is locked, or the new name conflicts). The error
                is propagated from the writer thread.

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> users = db.users
            >>> # Rename the column currently named "age" to "years"
            >>> users.rename_column(users.age, "years")
            >>> # Now users.years exists and users.age raises AttributeError
            >>> print(users.years.first_name)
            '[years]'
        """
        query = f'ALTER TABLE {self.name_} RENAME COLUMN {column.first_name} TO {new_name};'
        self._exc('qcb', (query,))
        self.__delattr__(column.first_name[1:-1])
        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:
        """Creates a new index on the table.

        Builds and executes a ``CREATE INDEX`` (or ``CREATE UNIQUE INDEX``)
        statement. Supports specifying a partial index via a ``WHERE``
        condition built from a :class:`ColumnsOperation` object.

        The parameters inside the ``WHERE`` clause are directly
        interpolated into the SQL string as literals (not using bound
        parameters), so this is safe for trusted data only. The final query
        is executed through the thread‑safe writer queue.

        Args:
            index_name (str): The name of the index to create. Must be
                unique within the database.
            columns (list[Column]): A list of :class:`Column` objects that
                define the indexed columns.
            unique (bool): If ``True``, creates a ``UNIQUE`` index that
                enforces uniqueness of the indexed column combination.
                Defaults to ``False``.
            where (ColumnsOperation, optional): A column operation
                representing the ``WHERE`` clause for a partial index. The
                expression and its parameter values are baked into the SQL.
                Defaults to ``None``.

        Returns:
            None: The operation mutates the database schema; it does not
            return any value.

        Raises:
            Exception: Propagated from the writer thread if the index
                creation fails (e.g., duplicate name, invalid column).

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> users = db.users
            >>> # Simple index on username
            >>> users.create_index("idx_username", [users.username])
            >>> # Unique composite index
            >>> users.create_index(
            ...     "idx_email_status",
            ...     [users.email, users.status],
            ...     unique=True
            ... )
            >>> # Partial index: only active users
            >>> users.create_index(
            ...     "idx_active_names",
            ...     [users.name],
            ...     where=users.status == 'active'
            ... )
        """
        if where:
            wr = f'WHERE {where._output[0]}'
            for i in where._output[1]:
                wr=wr.replace('?',i if isinstance(i,str) else str(i),1)
        
        query = (f'CREATE {'UNIQUE ' if unique else ''}INDEX {index_name} ON {self.name_} ({','.join(i.first_name for i in columns)}) {wr if where else ''}',[])
        self._exc('qcb', query)

    def delete_index(self, index_name: str) -> None:
        """Drops a database index by its name.

        Sends a ``DROP INDEX`` statement to the writer thread, which
        immediately removes the index from the SQLite schema. This operation
        cannot be undone.

        Args:
            index_name (str): The name of the index to drop. This is the
                identifier used when the index was created (see
                :meth:`create_index`).

        Returns:
            None: The index is removed from the database.

        Raises:
            Exception: If the writer thread encounters an error (e.g., the
                index does not exist, or the database is locked). The original
                error message from SQLite is re‑raised.

        Example:
            >>> db = Driver("app.db")
            >>> users = db.users
            >>> # Create an index for demonstration
            >>> users.create_index("idx_username", [users.username])
            >>> # Later, drop it
            >>> users.delete_index("idx_username")
        """
        query = (f'DROP INDEX {index_name}',)
        self._exc('qcb', query)

    def reindex(self, index_name: str) -> None:
        """Rebuilds a specific index from scratch.

        Issues a ``REINDEX`` command on the given index name to recreate it,
        which can be useful after bulk data changes or to recover from index
        corruption. The operation is executed on the writer connection and
        blocks until complete.

        Args:
            index_name (str): The name of the index to rebuild. Must already
                exist on this table.

        Returns:
            None: The method returns ``None`` after the index has been
            successfully rebuilt.

        Raises:
            Exception: If the ``REINDEX`` command fails (e.g., the index
                does not exist, or the database is locked). The original
                error from the writer thread is re‑raised.

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> users = db.users
            >>> # Rebuild the "idx_username" index after bulk updates
            >>> users.reindex("idx_username")
        """
        query = (f'REINDEX {index_name}',)
        self._exc('qcb', query)

    def get_indexes(self, from_readers_pool: bool = False) -> Any:
        """Retrieves a list of all index names defined on the table.

        Executes the ``PRAGMA index_list({self.name_})`` statement to obtain
        the names of every index associated with the table. The operation can
        optionally use a reader‑pool connection for non‑blocking reads.

        Args:
            from_readers_pool (bool): If ``False`` (default), the query runs
                on the main writer connection. If ``True``, a connection from
                the reader pool is obtained, allowing concurrent reads without
                blocking write operations.

        Returns:
            list[str]: A list of index name strings. For example,
            ``["idx_username", "idx_email"]``.

        Raises:
            Exception: If the pragma query fails or (when
                ``from_readers_pool=True``) a reader connection cannot be
                acquired. The original exception from the database thread is
                re‑raised.

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> users = db.users
            >>> indexes = users.get_indexes()
            >>> print(indexes)
            ['idx_username', 'idx_email']
            >>> # Use reader pool for a non‑blocking call
            >>> idx_from_reader = users.get_indexes(from_readers_pool=True)
        """
        query = (f'PRAGMA index_list({self.name_});',)
        if not from_readers_pool: 
            return [i[1] for i in  self._exc('qf', query)]
        else:
            queueCallBack= SimpleQueue()  
            connection_queue = self.db_obj.pool_holder.get(block=True)
            connection_queue.put(['qf', query, queueCallBack])
            self.db_obj.pool_holder.put(connection_queue)
            if (callback := queueCallBack.get(block=True))[0]:
                return [i[1] for i in callback[1]]
            else:
                raise Exception(callback[1])

    def get_index_info(self, index_name: str, from_readers_pool: bool = False) -> Any:
        """Retrieves detailed information about a specific index.

        Executes ``PRAGMA index_info({index_name})`` to obtain the list of
        columns that the index covers. The result is returned as a dictionary
        containing the index name and the names of the indexed columns.

        Args:
            index_name (str): The name of the index to inspect. Must already
                exist on this table.
            from_readers_pool (bool): If ``False`` (default), the query runs
                on the main writer connection. If ``True``, a connection from
                the reader pool is used, allowing non‑blocking reads.

        Returns:
            dict: A dictionary with two keys:
                - ``'name'`` (str): the index name (same as *index_name*).
                - ``'indexed_columns'`` (list[str]): the column names that
                are part of the index, in the order they appear in the
                index definition.

        Raises:
            Exception: If the database query fails (e.g., the index does not
                exist) or the reader‑pool connection cannot be acquired. The
                original error from the driver thread is re‑raised.

        Example:
            >>> db = Driver("mydb.sqlite3")
            >>> users = db.users
            >>> info = users.get_index_info("idx_email")
            >>> print(info['name'])
            idx_email
            >>> print(info['indexed_columns'])
            ['email']

            Using a reader‑pool connection:
            >>> info = users.get_index_info("idx_email", from_readers_pool=True)
        """
        query = (f'PRAGMA index_info({index_name});',)
        if not from_readers_pool:
            return {'name':index_name, 'indexed_columns':[i[2] for i in self._exc('qf', query)]}
        else:
            queueCallBack= SimpleQueue() 
            connection_queue = self.db_obj.pool_holder.get(block=True)
            connection_queue.put(['qf', query, queueCallBack])
            self.db_obj.pool_holder.put(connection_queue)
            if (callback := queueCallBack.get(block=True))[0]:
                return {'name':index_name, 'indexed_columns':[i[2] for i in callback[1]]}
            else:
                raise Exception(callback[1])

    def bulk_insert(self, columns: list['Column'], data_list: list) -> None:
        """Inserts multiple rows into the table in a single batch operation.

        Builds a parameterized ``INSERT`` statement with placeholders for the
        specified columns and uses ``executemany`` to execute all rows at once,
        which is significantly faster than individual inserts in a loop.
        The operation runs on the writer connection and commits automatically
        on success.

        Args:
            columns (list[Column]): A list of :class:`Column` objects defining
                the columns to be populated. The order must match the values
                provided in each element of ``data_list``.
            data_list (list[list | tuple]): A list of rows, where each row is
                an iterable (e.g., list or tuple) of values corresponding to
                ``columns``. All rows must have the same number of elements.

        Returns:
            None: The method returns ``None`` after all rows have been
            successfully inserted.

        Raises:
            Exception: If the database operation fails (e.g., constraint
                violation, type mismatch, or connection error). The original
                exception from the writer thread is re‑raised.

        Example:
            Simple usage – insert two rows into the "users" table.

            >>> db = Driver("app.db")
            >>> users = db.users
            >>> users.bulk_insert(
            ...     [users.name, users.age],
            ...     [("Alice", 30), ("Bob", 25)]
            ... )

        """
        query = f'INSERT INTO {self.name_} ({', '.join(i.first_name for i in columns)}) VALUES ({', '.join('?' for i in columns)});'
        self._exc('qmb', (query, data_list))

    def bulk_update(self, update: dict['Column', Any], where: 'ColumnsOperation', data_list: list) -> None:
        """Performs a batch UPDATE of multiple rows with varying values in one transaction.

        Constructs an ``UPDATE`` statement with ``?`` placeholders and executes it using
        SQLite's ``executemany``. The ``update`` dictionary maps :class:`Column` objects
        to the new values or expressions. Literal values become ``?`` in the template;
        :class:`Column` references are used directly; :class:`ColumnsOperation`
        expressions are inserted as SQL. The ``where`` condition is built from a
        :class:`ColumnsOperation`.

        Because ``executemany`` also relies on ``?``, the method temporarily replaces all
        ``?`` in the template with :attr:`Table.PLACE_HOLDER` (default
        ``'_MY_S4ULT3D_PL4C3_H0LD3R_?_'``) to avoid interference. After building the
        query string, the placeholder is swapped back to ``?`` before execution. The
        order of values in each tuple of ``data_list`` must match the order of ``?``
        placeholders that appear in the final query (including those from the ``where``
        clause if it contains bindings).

        Args:
            update (dict[:class:`Column`, Any]): A dictionary where each key is a
                :class:`Column` to update. Values can be:
                - a literal (int, float, str, bytes, etc.) – becomes ``?``,
                - a :class:`Column` – references another column,
                - a :class:`ColumnsOperation` – an SQL expression.
            where (:class:`ColumnsOperation`): The condition selecting which rows to
                update (e.g., ``table.id == some_value``).
            data_list (list[tuple]): A list of tuples, each containing the values that
                replace all ``?`` placeholders in the generated query, in the order they
                appear. The length of each tuple must exactly match the number of ``?``
                markers.

        Returns:
            None: The updates are committed on the writer thread.

        Raises:
            Exception: If the database operation fails. A special descriptive error is
                raised when the number of bindings in a ``data_list`` element does not
                match the number of ``?`` placeholders, possibly because the literal
                string :attr:`Table.PLACE_HOLDER` appeared in the data.

        Example:
            **Simple example**: update the ``age`` column for multiple users.

            >>> db = Driver('mydb.sqlite3')
            >>> users = db.users
            >>> # Update ages: user id 1 → 30, id 2 → 25, id 3 → 35
            >>> users.bulk_update(
            ...     update={users.age: users.PLACE_HOLDER},
            ...     where=users.id == users.PLACE_HOLDER,
            ...     data_list=[
            ...         (30, 1),
            ...         (25, 2),
            ...         (35, 3)
            ...     ]
            ... )

            **Complex example**: increase salary by a bonus that varies per user, but only
            for those whose department name matches a given value.

            >>> dept = db.departments
            >>> employees = db.employees
            >>> # update: salary = salary + bonus, where department name = ?
            >>> employees.bulk_update(
            ...     update={employees.salary: employees.salary + db.PLACE_HOLDER},
            ...     where=(employees.dept_id == dept.id) & (dept.name == db.PLACE_HOLDER),
            ...     data_list=[
            ...         (100.0, 'Engineering'),
            ...         (200.0, 'Sales'),
            ...         (150.0, 'Engineering')
            ...     ]
            ... )
        """
        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('?', self.PLACE_HOLDER)}' for key , value in list(update.items()))} WHERE {where._output[0].replace('?', 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._exc('qmb', (query.replace(self.PLACE_HOLDER, '?'), 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,
        from_readers_pool: bool = False
        ) -> Any:
        """Executes a SELECT query with one or more JOIN clauses on this table.

        Builds and runs a SQL query that selects the given columns from this
        table, joining other tables as specified. Column expressions can be
        raw :class:`Column` instances or :class:`ColumnsOperation` objects
        (e.g., the result of arithmetic or string operations). The method
        automatically generates aliases to avoid name collisions.

        Args:
            columns: A list of columns to retrieve. Each element can be a
                :class:`Column` (which will be aliased as
                ``{tablename}_{columnname}``) or a :class:`ColumnsOperation`
                (the expression is used directly, aliased similarly).
            joins_list: A list of join objects created with :class:`Join.Inner`,
                :class:`Join.Left`, or :class:`Join.Right`. Each specifies the
                table and the join condition (a :class:`ColumnsOperation`).
            where: An optional :class:`ColumnsOperation` representing the
                ``WHERE`` clause. Defaults to ``None`` (no filter).
            order_by: An optional :class:`Column` by which to sort the results.
                Defaults to ``None`` (no explicit ordering).
            from_readers_pool: If ``True``, the query is executed on one of the
                reader‑pool connections, allowing concurrent reads without
                blocking the writer. Defaults to ``False`` (uses the main
                writer connection).

        Returns:
            list[tuple]: The fetched rows as tuples. Each tuple corresponds to
            the order of ``columns``. If an error occurs, an exception is
            raised rather than returning a value.

        Raises:
            Exception: If the query fails (syntax error, constraint violation,
                reader‑pool exhaustion, etc.). The original error is re‑raised.

        Examples:
            Simple join between two tables:

            >>> db = Driver("store.db")
            >>> users = db.users
            >>> orders = db.orders
            >>> # INNER JOIN users with orders on user_id
            >>> result = users.join(
            ...     columns=[users.name, orders.total],
            ...     joins_list=[Join.Inner(orders, users.id == orders.user_id)]
            ... )
            >>> for row in result:
            ...     print(row)
            ('Alice', 150.0)
            ('Bob', 200.0)

            Complex example with multiple joins, expressions, and filtering:

            >>> # Using column operations (string concatenation) and LEFT JOIN
            >>> full_name = users.first_name + ' ' + users.last_name
            >>> condition = (orders.total > 100) & (orders.status == 'active')
            >>> result = users.join(
            ...     columns=[full_name, orders.total, products.name],
            ...     joins_list=[
            ...         Join.Inner(orders, users.id == orders.user_id),
            ...         Join.Left(products, orders.product_id == products.id)
            ...     ],
            ...     where=condition,
            ...     order_by=orders.total
            ... )
            >>> for row in result:
            ...     print(row)
            ('Alice Smith', 150.0, 'Widget')
            ('Bob Johnson', 200.0, 'Gadget')
        """
        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]
        query= (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 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 (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 for i in joins_list)} {f'ORDER BY {order_by.name}' if order_by else ''}', tl) if tl else (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 for i in joins_list)} {f'ORDER BY {order_by.name}' if order_by else ''}',)
        # The above line is approximately 1000 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.
        if not from_readers_pool:
            return self._exc('qf', query)
        else:
            queueCallBack= SimpleQueue()
            connection_queue = self.db_obj.pool_holder.get(block=True)
            connection_queue.put(['qf', query, queueCallBack])
            self.db_obj.pool_holder.put(connection_queue)
            if (callback := queueCallBack.get(block=True))[0]:
                return callback[1]
            else:
                raise Exception(callback[1])


class DataTypes:
    """
    Factory for SQLite column definitions with built‑in CHECK constraints.

    This class provides static methods that generate SQL column definition
    strings for various data types. Each method returns a string that
    includes the column name placeholder ``'my_saulted_x'``, the SQLite
    type, and optional ``CHECK`` constraints to enforce limits, ranges,
    or allowed values.

    The placeholder ``'my_saulted_x'`` is a special marker that is
    replaced with the actual column name when the definition is used in
    a :class:`TableStructure` (specifically by :meth:`TableStructure.add_column`).
    This design allows the same definition to be reused for multiple
    columns.

    All methods are static and are meant to be called directly on the
    class, e.g., ``DataTypes.INTEGER(unsigned=True)``.

    Supported data types include:
      - Integer types: :meth:`INTEGER`, :meth:`INT`, :meth:`BIGINT`,
        :meth:`TINYINT`, :meth:`SMALLINT`, :meth:`MEDIUMINT`
      - Floating‑point types: :meth:`REAL`, :meth:`FLOAT`, :meth:`DOUBLE`,
        :meth:`DECIMAL`, :meth:`NUMERIC`
      - Text types: :meth:`TEXT`, :meth:`VARCHAR`, :meth:`CHAR`
      - Binary: :meth:`BLOB`
      - Boolean: :meth:`BOOLEAN`
      - Enum: :meth:`ENUM`
      - Custom: :meth:`CUSTOM`
      - Other: :meth:`NULL`

    Each method accepts optional parameters to define constraints such as
    minimum/maximum values, unsigned ranges, length limits, or allowed
    enumerations. These constraints are translated into SQLite ``CHECK``
    clauses.

    Example:
        Using :class:`DataTypes` with :class:`TableStructure` to define
        a table::

            from ormophine.Sqlite import DataTypes, TableStructure

            # Define a table structure
            structure = TableStructure('products', strict=True)

            # Add columns using DataTypes
            structure.add_column(
                column_name='id',
                datatype=DataTypes.INTEGER(unsigned=True, min_val=1)
            )
            structure.add_column(
                column_name='name',
                datatype=DataTypes.VARCHAR(max_length=100)
            )
            structure.add_column(
                column_name='price',
                datatype=DataTypes.DECIMAL(precision=10, scale=2, unsigned=True)
            )
            structure.add_column(
                column_name='status',
                datatype=DataTypes.ENUM('active', 'inactive', 'pending')
            )
            structure.add_column(
                column_name='is_active',
                datatype=DataTypes.BOOLEAN()
            )

            # Create the table using Driver
            db = Driver('store.db')
            db.create_table(structure)
    """

    @staticmethod
    def INTEGER(min_val: int = None, max_val: int = None, unsigned: bool = False) -> str:
        """Generate a SQLite column definition for an INTEGER type with optional constraints.

        This method returns a string that can be used as a column definition in a
        ``CREATE TABLE`` or ``ALTER TABLE ADD COLUMN`` statement. The placeholder
        ``my_saulted_x`` is used for the column name and will be replaced by the
        actual column name when used (e.g., in :class:`TableStructure.add_column`).

        The method supports range constraints via ``min_val`` and ``max_val``, and
        an unsigned constraint. If multiple constraints are given, they are combined
        with ``AND`` in a ``CHECK`` clause. If no constraints are specified, the
        definition is simply ``my_saulted_x INTEGER``.

        Args:
            min_val (int, optional): Minimum allowed value (inclusive). If provided,
                adds a ``CHECK(my_saulted_x >= min_val)`` constraint.
            max_val (int, optional): Maximum allowed value (inclusive). If provided,
                adds a ``CHECK(my_saulted_x <= max_val)`` constraint.
            unsigned (bool, optional): If ``True``, adds a
                ``CHECK(my_saulted_x >= 0)`` constraint. This is applied in addition
                to any min/max constraints (but does not override them). Defaults to
                ``False``.

        Returns:
            str: A SQL column definition string. Examples:
                - ``"my_saulted_x INTEGER"``
                - ``"my_saulted_x INTEGER CHECK(my_saulted_x >= 0)"``
                - ``"my_saulted_x INTEGER CHECK(my_saulted_x >= 1 AND my_saulted_x <= 100)"``

        Example:
            Using with :class:`TableStructure`:

            >>> from ormophine.Sqlite import DataTypes, TableStructure
            >>> table = TableStructure('products')
            >>> table.add_column('id', DataTypes.INTEGER(unsigned=True))
            >>> table.add_column('price', DataTypes.INTEGER(min_val=0, max_val=9999))
            >>> print(table.get_structure())
            CREATE TABLE [products] ( [id] INTEGER CHECK([id] >= 0), [price] INTEGER CHECK([price] >= 0 AND [price] <= 9999),) ;
        """
        checks = []
        if unsigned and min_val is None:
            checks.append("my_saulted_x >= 0")
        if min_val is not None:
            checks.append(f"my_saulted_x >= {min_val}")
        if max_val is not None:
            checks.append(f"my_saulted_x <= {max_val}")
        if checks:
            return f"my_saulted_x INTEGER CHECK({' AND '.join(checks)})"
        return f"my_saulted_x INTEGER"

    @staticmethod
    def REAL(min_val: float = None, max_val: float = None, unsigned: bool = False) -> str:
        """Generate a column definition string for a floating‑point number (REAL).

        This method returns a SQLite column definition for a REAL (8‑byte float)
        column, optionally with CHECK constraints for range and/or unsigned
        values. The placeholder ``my_saulted_x`` in the returned string will
        be replaced by the actual column name when used in table creation or
        alteration.

        If ``unsigned`` is ``True``, a constraint ``my_saulted_x >= 0`` is
        added. If ``min_val`` and/or ``max_val`` are provided, additional
        range constraints are generated. All constraints are combined with
        AND.

        Args:
            min_val (float, optional): The minimum allowed value (inclusive).
                If provided, a ``>=`` constraint is added.
            max_val (float, optional): The maximum allowed value (inclusive).
                If provided, a ``<=`` constraint is added.
            unsigned (bool, optional): If ``True``, restricts values to
                non‑negative (``>= 0``). Defaults to ``False``.

        Returns:
            str: A SQL column definition string, e.g.,
            ``"my_saulted_x REAL CHECK(my_saulted_x >= 0 AND my_saulted_x <= 100)"``.

        Example:
            Using the method in a table structure::

                from ormophine.Sqlite import DataTypes, TableStructure

                table = TableStructure('products')
                table.add_column('price', DataTypes.REAL(min_val=0.0, max_val=999.99))
                # The column definition will be:
                # my_saulted_x REAL CHECK(my_saulted_x >= 0.0 AND my_saulted_x <= 999.99)
                # which is then replaced with the column name 'price' to produce:
                # [price] REAL CHECK([price] >= 0.0 AND [price] <= 999.99)
        """
        checks = []
        if unsigned:
            checks.append("my_saulted_x >= 0")
        if min_val is not None:
            checks.append(f"my_saulted_x >= {min_val}")
        if max_val is not None:
            checks.append(f"my_saulted_x <= {max_val}")
        if checks:
            return f"my_saulted_x REAL CHECK({' AND '.join(checks)})"
        return f"my_saulted_x REAL"

    @staticmethod
    def FLOAT(min_val: float = None, max_val: float = None, unsigned: bool = False) -> str:
        """Define a floating-point column with optional range and unsigned constraints.

        This method is a synonym for :meth:`REAL` and generates a SQL column
        definition of type ``REAL`` (8‑byte floating‑point number). It supports
        the same validation options: an ``unsigned`` flag to enforce non‑negative
        values, and optional ``min_val``/``max_val`` to enforce a value range.

        The placeholder ``my_saulted_x`` in the generated SQL will be replaced
        with the actual column name by the :class:`TableStructure` builder.

        Args:
            min_val (float, optional): Minimum allowed value (inclusive). If
                provided, adds a ``CHECK(my_saulted_x >= min_val)`` constraint.
            max_val (float, optional): Maximum allowed value (inclusive). If
                provided, adds a ``CHECK(my_saulted_x <= max_val)`` constraint.
            unsigned (bool, optional): If ``True``, enforces that the value is
                non‑negative by adding ``CHECK(my_saulted_x >= 0)``. Defaults
                to ``False``.

        Returns:
            str: A SQL column definition string with the appropriate ``REAL``
            type and optional ``CHECK`` constraints. The placeholder
            ``my_saulted_x`` is used for the column name.

        Example:
            Creating a table with a floating‑point price column that must be
            between 0 and 999.99::

                from ormophine.Sqlite import TableStructure, DataTypes

                table = TableStructure('products')
                table.add_column(
                    column_name='price',
                    datatype=DataTypes.FLOAT(min_val=0.0, max_val=999.99)
                )
                # The generated column definition will be:
                # my_saulted_x REAL CHECK(my_saulted_x >= 0.0 AND my_saulted_x <= 999.99)
                # (after replacing my_saulted_x with [price])
        """
        return DataTypes.REAL(min_val, max_val, unsigned)

    @staticmethod
    def DOUBLE(min_val: float = None, max_val: float = None, unsigned: bool = False) -> str:
        """Synonym for :meth:`REAL` for compatibility with other databases.

        This method is an alias for the :meth:`REAL` data type, providing a
        name that is commonly used in other SQL databases (e.g., MySQL,
        PostgreSQL). It returns the same column definition string, including
        optional range and unsigned checks, as the REAL type.

        The returned string uses the placeholder ``my_saulted_x``, which
        will be replaced by the actual column name when used in
        :meth:`TableStructure.add_column` or :meth:`Table.add_column`.

        Args:
            min_val (float, optional): Minimum allowed value for the column.
                If provided, a ``CHECK(my_saulted_x >= min_val)`` constraint
                is added. Defaults to ``None``.
            max_val (float, optional): Maximum allowed value for the column.
                If provided, a ``CHECK(my_saulted_x <= max_val)`` constraint
                is added. Defaults to ``None``.
            unsigned (bool, optional): If ``True``, adds a
                ``CHECK(my_saulted_x >= 0)`` constraint (unless a custom
                ``min_val`` is also given). Defaults to ``False``.

        Returns:
            str: A column definition string in the format
            ``"my_saulted_x REAL"`` with optional ``CHECK`` constraints.

        Example:
            Creating a table with a DOUBLE column::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('measurements')
                structure.add_column('temperature', DataTypes.DOUBLE(
                    min_val=-273.15, max_val=1000.0
                ))
                # Adds column: my_saulted_x REAL CHECK(my_saulted_x >= -273.15 AND my_saulted_x <= 1000.0)
        """
        return DataTypes.REAL(min_val, max_val, unsigned)

    @staticmethod
    def DECIMAL(precision: int = None, scale: int = None,
                min_val: float = None, max_val: float = None,
                unsigned: bool = False) -> str:
        """
        Generate a column definition string for a fixed‑point decimal type.

        This method returns a SQLite column definition for a decimal number,
        stored as a ``REAL`` type, with optional ``CHECK`` constraints for
        range validation. The placeholder ``my_saulted_x`` will be replaced
        by the actual column name when the definition is used in a table
        creation or addition.

        The method infers default minimum and maximum values from the
        ``precision`` and ``scale`` parameters if they are provided.
        - If both ``precision`` and ``scale`` are given, the default range
        is ``[0, 10^(precision-scale) - 10^(-scale)]`` for unsigned,
        or ``[-default_max, default_max]`` for signed.
        - If only ``precision`` is given, the default range is
        ``[0, 10^precision - 1]`` for unsigned, or symmetric around zero
        for signed.
        - If neither is given, no default range is applied, but custom
        ``min_val`` and ``max_val`` are still respected.

        The generated string can be used directly in a ``CREATE TABLE`` or
        ``ALTER TABLE ADD COLUMN`` statement.

        Args:
            precision (int, optional): The total number of digits (including
                fractional part). Used only to compute default range bounds.
            scale (int, optional): The number of digits after the decimal
                point. Used together with ``precision`` to compute the
                default maximum value. If not given, default range is
                computed as if scale = 0.
            min_val (float, optional): Custom minimum allowed value.
                Overrides any default inferred from precision/scale.
            max_val (float, optional): Custom maximum allowed value.
                Overrides any default inferred from precision/scale.
            unsigned (bool, optional): If ``True``, the default range is
                non‑negative (minimum = 0). This is ignored if a custom
                ``min_val`` is provided. Defaults to ``False``.

        Returns:
            str: A complete column definition string, e.g.,
            ``"my_saulted_x REAL CHECK(my_saulted_x BETWEEN 0 AND 99.99)"``.
            The placeholder ``my_saulted_x`` must be replaced with the actual
            column name before use.

        Raises:
            ValueError: If both ``precision`` and ``scale`` are provided and
                ``scale`` is greater than ``precision`` (impossible to represent).

        Example:
            Using ``DECIMAL`` in a table definition::

                from ormophine.Sqlite import DataTypes, TableStructure

                ts = TableStructure('products')
                ts.add_column('price', DataTypes.DECIMAL(precision=10, scale=2))
                # Produces: "my_saulted_x REAL CHECK(my_saulted_x BETWEEN -99999999.99 AND 99999999.99)"

                # Custom constraints
                ts.add_column('rating', DataTypes.DECIMAL(min_val=0, max_val=5))
                # Produces: "my_saulted_x REAL CHECK(my_saulted_x >= 0 AND my_saulted_x <= 5)"

                # Unsigned with precision/scale
                ts.add_column('score', DataTypes.DECIMAL(precision=5, scale=2, unsigned=True))
                # Produces: "my_saulted_x REAL CHECK(my_saulted_x >= 0 AND my_saulted_x BETWEEN 0 AND 999.99)"
        """
        checks = []
        if unsigned:
            checks.append("my_saulted_x >= 0")
        # Compute default range from precision/scale if provided and no custom bounds
        if precision is not None and scale is not None:
            default_max = 10 ** (precision - scale) - 10 ** (-scale)
            default_min = 0 if unsigned else -default_max
            actual_min = min_val if min_val is not None else default_min
            actual_max = max_val if max_val is not None else default_max
            checks.append(f"my_saulted_x BETWEEN {actual_min} AND {actual_max}")
        elif precision is not None and scale is None:
            default_max = 10 ** precision - 1
            default_min = 0 if unsigned else -default_max
            actual_min = min_val if min_val is not None else default_min
            actual_max = max_val if max_val is not None else default_max
            checks.append(f"my_saulted_x BETWEEN {actual_min} AND {actual_max}")
        else:
            # No precision/scale: apply custom min/max if given
            if min_val is not None:
                checks.append(f"my_saulted_x >= {min_val}")
            if max_val is not None:
                checks.append(f"my_saulted_x <= {max_val}")
        if checks:
            return f"my_saulted_x REAL CHECK({' AND '.join(checks)})"
        return f"my_saulted_x REAL"

    @staticmethod
    def NUMERIC(precision: int = None, scale: int = None,
                min_val: float = None, max_val: float = None,
                unsigned: bool = False) -> str:
        """Synonym for :meth:`DECIMAL`.

        This method is an alias for :meth:`DECIMAL` and returns a fixed-point
        decimal column definition for SQLite. The generated SQL uses the
        placeholder ``my_saulted_x`` and is later replaced with the actual
        column name during table creation.

        When both ``precision`` and ``scale`` are provided, a default numeric
        range is inferred from those values. If only ``precision`` is given,
        the default maximum becomes ``10^precision - 1``. Custom bounds supplied
        through ``min_val`` and ``max_val`` override the inferred defaults.

        Args:
            precision (int, optional): Total number of digits, including the
                fractional part.
            scale (int, optional): Number of digits after the decimal point.
            min_val (float, optional): Minimum allowed value.
            max_val (float, optional): Maximum allowed value.
            unsigned (bool, optional): If ``True``, add a non-negative check.
                Defaults to ``False``.

        Returns:
            str: A column definition fragment such as
            ``'my_saulted_x REAL CHECK(...)'``.

        Example:
            Using ``NUMERIC`` in a :class:`TableStructure`::

                from Ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('products')
                structure.add_column(
                    column_name='price',
                    datatype=DataTypes.NUMERIC(precision=10, scale=2, unsigned=True)
                )
        """
        return DataTypes.DECIMAL(precision, scale, min_val, max_val, unsigned)

    @staticmethod
    def TEXT(min_length: int = None, max_length: int = None) -> str:
        """
        Generate a column definition for a TEXT type with optional length constraints.

        This method returns a SQLite column definition string for a ``TEXT`` column.
        If ``min_length`` and/or ``max_length`` are provided, the definition includes
        a ``CHECK`` constraint that enforces the length range using the SQLite
        ``LENGTH()`` function. The placeholder ``my_saulted_x`` in the returned
        string is replaced with the actual column name when the table is created.

        Args:
            min_length (int, optional): Minimum allowed length (in characters) for
                the text value. If provided, adds a check like
                ``LENGTH(my_saulted_x) >= min_length``.
            max_length (int, optional): Maximum allowed length (in characters) for
                the text value. If provided, adds a check like
                ``LENGTH(my_saulted_x) <= max_length``.

        Returns:
            str: A column definition fragment that can be used inside a
            :class:`TableStructure` definition. For example:
            ``'my_saulted_x TEXT CHECK(LENGTH(my_saulted_x) >= 1 AND LENGTH(my_saulted_x) <= 100)'``.

        Example:
            Using ``TEXT`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column(
                    column_name='username',
                    datatype=DataTypes.TEXT(min_length=3, max_length=20)
                )
                # Generated SQL fragment:
                # my_saulted_x TEXT CHECK(LENGTH(my_saulted_x) >= 3 AND LENGTH(my_saulted_x) <= 20)

                # No length constraints:
                structure.add_column(
                    column_name='bio',
                    datatype=DataTypes.TEXT()
                )
                # Returns: my_saulted_x TEXT
        """
        checks = []
        if min_length is not None:
            checks.append(f"LENGTH(my_saulted_x) >= {min_length}")
        if max_length is not None:
            checks.append(f"LENGTH(my_saulted_x) <= {max_length}")
        if checks:
            return f"my_saulted_x TEXT CHECK({' AND '.join(checks)})"
        return f"my_saulted_x TEXT"

    def BLOB() -> str:
        """Generate a column definition for binary large object (BLOB) data.

        This method returns a column definition string for storing binary data
        (e.g., images, files, serialized objects) in SQLite. The generated
        string contains the placeholder ``'my_saulted_x'`` which is replaced
        with the actual column name when used in a :class:`TableStructure`
        definition.

        SQLite's ``BLOB`` type stores data exactly as provided, without any
        character set conversion. It is suitable for any binary content.

        Returns:
            str: A column definition fragment (e.g., ``'my_saulted_x BLOB'``).

        Example:
            Using ``BLOB`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('files')
                structure.add_column(
                    column_name='file_data',
                    datatype=DataTypes.BLOB()
                )
                # Generated column: my_saulted_x BLOB
        """
        return f"my_saulted_x BLOB"

    @staticmethod
    def NULL() -> str:
        """Generate a column definition for a NULL‑type column.

        This method returns a SQL column definition fragment using SQLite's
        ``NULL`` type. In SQLite, ``NULL`` is a valid type affinity, but it is
        rarely used explicitly; columns without a specified type affinity
        default to ``NUMERIC``. This method is provided primarily for
        completeness and compatibility.

        The returned string contains the placeholder ``'my_saulted_x'``, which
        is replaced with the actual column name at table creation time (e.g.,
        by :class:`TableStructure.add_column`).

        Returns:
            str: A column definition fragment in the form
            ``'my_saulted_x NULL'``.

        Example:
            Using ``NULL`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('example')
                structure.add_column(
                    column_name='nullable_col',
                    datatype=DataTypes.NULL()
                )
                # Generated column: my_saulted_x NULL

        Note:
            This type is rarely needed; consider using other affinities
            (e.g., ``TEXT``, ``INTEGER``) for most use cases.
        """
        return f"my_saulted_x NULL"

    @staticmethod
    def VARCHAR(min_length: int = None, max_length: int = None) -> str:
        """Create a column definition for a variable-length string (VARCHAR).

        This method is a synonym for :meth:`TEXT`. It generates a column
        definition fragment for a string column with optional length
        constraints, enforced by ``CHECK`` clauses using the ``LENGTH()``
        function. The placeholder ``'my_saulted_x'`` is used in the generated
        string and is replaced by the actual column name at table creation time.

        If ``min_length`` is provided, a constraint ``LENGTH(my_saulted_x) >= min_length``
        is added. If ``max_length`` is provided, a constraint
        ``LENGTH(my_saulted_x) <= max_length`` is added. If neither is given,
        no constraints are imposed and the column is simply of type ``TEXT``.

        Args:
            min_length (int, optional): Minimum allowed number of characters.
            max_length (int, optional): Maximum allowed number of characters.

        Returns:
            str: A column definition fragment (e.g.,
            ``'my_saulted_x TEXT CHECK(LENGTH(my_saulted_x) <= 255)'``) that
            can be used in a :class:`TableStructure` definition.

        Example:
            Using ``VARCHAR`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column(
                    column_name='username',
                    datatype=DataTypes.VARCHAR(max_length=50)
                )
                # Generated column: my_saulted_x TEXT CHECK(LENGTH(my_saulted_x) <= 50)

                # With both min and max length
                structure.add_column(
                    column_name='code',
                    datatype=DataTypes.VARCHAR(min_length=3, max_length=10)
                )
        """
        return DataTypes.TEXT(min_length, max_length)

    @staticmethod
    def TINYINT(min_val: int = None, max_val: int = None, unsigned: bool = False) -> str:
        """
        Generate a column definition for a tiny integer with optional range checks.

        SQLite does not have a native TINYINT type, but this method returns an
        ``INTEGER`` column definition with a ``CHECK`` constraint that enforces
        the range typical for a tiny integer. By default, the signed range is
        -128 to 127, and the unsigned range is 0 to 255. These defaults can be
        overridden by providing custom ``min_val`` and/or ``max_val``.

        The generated string contains the placeholder ``'my_saulted_x'``, which
        will be replaced by the actual column name when used in a
        :class:`TableStructure` definition.

        Args:
            min_val (int, optional): The minimum allowed value. If not provided,
                uses the default for the chosen signed/unsigned mode.
            max_val (int, optional): The maximum allowed value. If not provided,
                uses the default for the chosen signed/unsigned mode.
            unsigned (bool, optional): If ``True``, the default range is 0–255;
                otherwise, the default range is -128–127. Defaults to ``False``.

        Returns:
            str: A column definition fragment, e.g.,
            ``'my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN -128 AND 127)'``.
            If no constraints are needed, returns ``'my_saulted_x INTEGER'``.

        Example:
            Creating a table with a TINYINT column::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column(
                    column_name='age',
                    datatype=DataTypes.TINYINT(unsigned=True)  # 0–255
                )
                # Generated fragment: my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN 0 AND 255)

                # Custom range: values between 10 and 20
                structure.add_column(
                    column_name='score',
                    datatype=DataTypes.TINYINT(min_val=10, max_val=20)
                )
                # Generated fragment: my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN 10 AND 20)
        """
        if unsigned:
            default_min, default_max = 0, 255
        else:
            default_min, default_max = -128, 127
        actual_min = min_val if min_val is not None else default_min
        actual_max = max_val if max_val is not None else default_max
        if actual_min is not None or actual_max is not None:
            return f"my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN {actual_min} AND {actual_max})"
        return f"my_saulted_x INTEGER"

    @staticmethod
    def SMALLINT(min_val: int = None, max_val: int = None, unsigned: bool = False) -> str:
        """Generate a column definition for a SMALLINT with optional CHECK constraints.

        This method returns a SQLite column definition string suitable for a
        small integer type. The default range is -32,768 to 32,767 when
        ``unsigned=False``, and 0 to 65,535 when ``unsigned=True``.
        Custom minimum and maximum values can be specified to override the
        defaults, and the resulting ``CHECK`` clause ensures that the column
        values stay within the defined bounds.

        The placeholder ``'my_saulted_x'`` in the returned string is replaced
        with the actual column name during table creation (typically by the
        :meth:`TableStructure.add_column` method).

        Args:
            min_val (int, optional): The minimum allowed value. If provided,
                overrides the default minimum for the chosen signed/unsigned
                mode.
            max_val (int, optional): The maximum allowed value. If provided,
                overrides the default maximum for the chosen signed/unsigned
                mode.
            unsigned (bool, optional): If ``True``, the default range is
                0 to 65,535. If ``False`` (the default), the default range is
                -32,768 to 32,767.

        Returns:
            str: A column definition fragment like
            ``'my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN -32768 AND 32767)'``
            or a plain ``'my_saulted_x INTEGER'`` if no constraints apply.

        Example:
            Using ``SMALLINT`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('scores')
                structure.add_column(
                    column_name='score',
                    datatype=DataTypes.SMALLINT(unsigned=True)  # 0..65535
                )
                structure.add_column(
                    column_name='temperature',
                    datatype=DataTypes.SMALLINT(min_val=-100, max_val=100)
                )
        """
        if unsigned:
            default_min, default_max = 0, 65535
        else:
            default_min, default_max = -32768, 32767
        actual_min = min_val if min_val is not None else default_min
        actual_max = max_val if max_val is not None else default_max
        if actual_min is not None or actual_max is not None:
            return f"my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN {actual_min} AND {actual_max})"
        return f"my_saulted_x INTEGER"

    @staticmethod
    def MEDIUMINT(min_val: int = None, max_val: int = None, unsigned: bool = False) -> str:
        """Generate a column definition for a MEDIUMINT with optional CHECK constraints.

        This method returns a SQLite column definition string for a medium‑sized
        integer type. The default range is -8,388,608 to 8,388,607 when
        ``unsigned=False``, and 0 to 16,777,215 when ``unsigned=True``.
        Custom minimum and maximum values can be specified to override the
        defaults, and the resulting ``CHECK`` clause ensures that the column
        values stay within the defined bounds.

        The placeholder ``'my_saulted_x'`` in the returned string is replaced
        with the actual column name during table creation (typically by the
        :meth:`TableStructure.add_column` method).

        Args:
            min_val (int, optional): The minimum allowed value. If provided,
                overrides the default minimum for the chosen signed/unsigned
                mode.
            max_val (int, optional): The maximum allowed value. If provided,
                overrides the default maximum for the chosen signed/unsigned
                mode.
            unsigned (bool, optional): If ``True``, the default range is
                0 to 16,777,215. If ``False`` (the default), the default range
                is -8,388,608 to 8,388,607.

        Returns:
            str: A column definition fragment like
            ``'my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN -8388608 AND 8388607)'``
            or a plain ``'my_saulted_x INTEGER'`` if no constraints apply.

        Example:
            Using ``MEDIUMINT`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('events')
                structure.add_column(
                    column_name='counter',
                    datatype=DataTypes.MEDIUMINT(unsigned=True)  # 0..16777215
                )
                structure.add_column(
                    column_name='temperature',
                    datatype=DataTypes.MEDIUMINT(min_val=-1000, max_val=1000)
                )
        """
        if unsigned:
            default_min, default_max = 0, 16777215
        else:
            default_min, default_max = -8388608, 8388607
        actual_min = min_val if min_val is not None else default_min
        actual_max = max_val if max_val is not None else default_max
        if actual_min is not None or actual_max is not None:
            return f"my_saulted_x INTEGER CHECK(my_saulted_x BETWEEN {actual_min} AND {actual_max})"
        return f"my_saulted_x INTEGER"

    @staticmethod
    def INT(min_val: int = None, max_val: int = None, unsigned: bool = False) -> str:
        """Generate a column definition for an integer with optional CHECK constraints.

        This method returns a SQLite column definition string for an ``INTEGER``
        type (64‑bit signed). It supports optional range constraints via
        ``min_val`` and ``max_val``, and an ``unsigned`` flag that adds a
        ``CHECK(my_saulted_x >= 0)`` unless a custom ``min_val`` is explicitly
        provided. All constraints are combined into a single ``CHECK`` clause.

        The placeholder ``'my_saulted_x'`` in the returned string is replaced
        with the actual column name during table creation (typically by the
        :meth:`TableStructure.add_column` method).

        Args:
            min_val (int, optional): Minimum allowed value. If provided, adds
                ``CHECK(my_saulted_x >= min_val)``.
            max_val (int, optional): Maximum allowed value. If provided, adds
                ``CHECK(my_saulted_x <= max_val)``.
            unsigned (bool, optional): If ``True`` and no explicit ``min_val``
                is given, adds ``CHECK(my_saulted_x >= 0)``. Defaults to
                ``False``.

        Returns:
            str: A column definition fragment. If constraints are present, the
            string includes a ``CHECK`` clause, e.g.:
            ``'my_saulted_x INTEGER CHECK(my_saulted_x >= 0 AND my_saulted_x <= 100)'``.
            If no constraints, returns ``'my_saulted_x INTEGER'``.

        Example:
            Using ``INT`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('products')
                structure.add_column(
                    column_name='quantity',
                    datatype=DataTypes.INT(min_val=0, max_val=9999)
                )
                # Generated column: my_saulted_x INTEGER CHECK(my_saulted_x >= 0 AND my_saulted_x <= 9999)

                structure.add_column(
                    column_name='score',
                    datatype=DataTypes.INT(unsigned=True)
                )
                # Generated column: my_saulted_x INTEGER CHECK(my_saulted_x >= 0)
        """
        checks = []
        if unsigned and min_val is None:
            checks.append("my_saulted_x >= 0")
        if min_val is not None:
            checks.append(f"my_saulted_x >= {min_val}")
        if max_val is not None:
            checks.append(f"my_saulted_x <= {max_val}")
        if checks:
            return f"my_saulted_x INTEGER CHECK({' AND '.join(checks)})"
        return f"my_saulted_x INTEGER"

    @staticmethod
    def BIGINT(min_val: int = None, max_val: int = None, unsigned: bool = False) -> str:
        """Generate a column definition for a BIGINT (64‑bit integer) with optional constraints.

        This method is an alias for :meth:`INT` and returns a column definition
        string for a 64‑bit integer. In SQLite, all integer types are stored
        as 64‑bit, so this is functionally identical to ``INT``. It provides
        the same optional range and unsigned checks via ``CHECK`` constraints.

        The placeholder ``'my_saulted_x'`` in the returned string is replaced
        with the actual column name during table creation (typically by the
        :meth:`TableStructure.add_column` method).

        Args:
            min_val (int, optional): The minimum allowed value. If provided,
                adds a ``CHECK(my_saulted_x >= min_val)`` constraint.
            max_val (int, optional): The maximum allowed value. If provided,
                adds a ``CHECK(my_saulted_x <= max_val)`` constraint.
            unsigned (bool, optional): If ``True``, adds a ``CHECK`` that the
                value is >= 0 (unless a custom ``min_val`` overrides it).
                Defaults to ``False``.

        Returns:
            str: A column definition fragment like
            ``'my_saulted_x INTEGER CHECK(...)'`` or a plain
            ``'my_saulted_x INTEGER'`` if no constraints are needed.

        Example:
            Using ``BIGINT`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('logs')
                structure.add_column(
                    column_name='timestamp',
                    datatype=DataTypes.BIGINT(unsigned=True)  # only non‑negative timestamps
                )
                structure.add_column(
                    column_name='score',
                    datatype=DataTypes.BIGINT(min_val=-1000, max_val=1000)
                )
        """
        return DataTypes.INT(min_val, max_val, unsigned)

    @staticmethod
    def CHAR(min_length: int = None, max_length: int = None) -> str:
        """Generate a column definition for a fixed‑length character type.

        This method is a synonym for :meth:`TEXT` and returns a SQLite column
        definition string of type ``TEXT`` with optional length constraints
        enforced via ``CHECK``. It is named ``CHAR`` to mirror SQL fixed‑length
        semantics, but SQLite does not natively support fixed‑length storage;
        the constraints are implemented using ``LENGTH()`` checks.

        If both ``min_length`` and ``max_length`` are provided, the column
        will have a ``CHECK`` that the length is between them. If only one is
        given, only that bound is enforced. To enforce an exact length, pass
        the same value for both.

        The placeholder ``'my_saulted_x'`` in the returned string is replaced
        with the actual column name during table creation.

        Args:
            min_length (int, optional): The minimum allowed length (inclusive).
                If provided, adds a ``CHECK(LENGTH(my_saulted_x) >= min_length)``.
            max_length (int, optional): The maximum allowed length (inclusive).
                If provided, adds a ``CHECK(LENGTH(my_saulted_x) <= max_length)``.

        Returns:
            str: A column definition fragment such as
            ``'my_saulted_x TEXT CHECK(LENGTH(my_saulted_x) BETWEEN 5 AND 10)'``
            or ``'my_saulted_x TEXT'`` if no constraints are specified.

        Example:
            Using ``CHAR`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column(
                    column_name='country_code',
                    datatype=DataTypes.CHAR(min_length=2, max_length=2)  # exactly 2 chars
                )
                structure.add_column(
                    column_name='status',
                    datatype=DataTypes.CHAR(max_length=20)  # at most 20 chars
                )
        """
        return DataTypes.TEXT(min_length, max_length)

    @staticmethod
    def ENUM(*values: str) -> str:
        """
        Generate a column definition for an enumeration of allowed string values.

        This method returns a SQLite column definition string that restricts
        the column's values to a predefined list of strings. The constraint
        is enforced by a ``CHECK`` clause using the ``IN`` operator, and the
        column is defined as type ``TEXT``.

        The placeholder ``'my_saulted_x'`` in the returned string is replaced
        with the actual column name when the column is added to a table (e.g.,
        by :meth:`TableStructure.add_column`).

        Args:
            *values (str): A variable number of string values that are
                permitted in the column. Each value will be quoted in the
                resulting SQL.

        Returns:
            str: A column definition fragment like
            ``'my_saulted_x TEXT CHECK(my_saulted_x IN ('value1', 'value2'))'``.

        Example:
            Using ``ENUM`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column(
                    column_name='status',
                    datatype=DataTypes.ENUM('active', 'inactive', 'pending')
                )
                # Generates:
                # my_saulted_x TEXT CHECK(my_saulted_x IN ('active', 'inactive', 'pending'))

                structure.add_column(
                    column_name='role',
                    datatype=DataTypes.ENUM('admin', 'user', 'guest')
                )
                # Generates a separate CHECK constraint for the role column.
        """
        quoted = ", ".join(f"'{v}'" for v in values)
        return f"my_saulted_x TEXT CHECK(my_saulted_x IN ({quoted}))"

    @staticmethod
    def BOOLEAN() -> str:
        """Generate a column definition for a boolean type stored as INTEGER 0/1.

        This method returns a SQLite column definition string that enforces
        boolean values (0 for false, 1 for true) using a ``CHECK`` constraint.
        The placeholder ``'my_saulted_x'`` in the returned string is replaced
        with the actual column name during table creation (e.g., by
        :meth:`TableStructure.add_column`).

        The generated definition ensures that only the integers 0 or 1 can be
        inserted into the column, providing a simple boolean representation.

        Returns:
            str: A column definition fragment like
            ``'my_saulted_x INTEGER CHECK(my_saulted_x IN (0, 1))'``.

        Example:
            Using ``BOOLEAN`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column(
                    column_name='is_active',
                    datatype=DataTypes.BOOLEAN()
                )
                # Creates a column definition:
                # my_saulted_x INTEGER CHECK(my_saulted_x IN (0, 1))
        """
        return f"my_saulted_x INTEGER CHECK(my_saulted_x IN (0, 1))"

    @staticmethod
    def CUSTOM(type_name: str, check: str = None) -> str:
        """Generate a custom column definition with an optional CHECK constraint.

        This method allows you to define a column with a custom SQLite data type
        name (e.g., when strict mode is disabled) and optionally add a CHECK
        constraint. The placeholder ``'my_saulted_x'`` in the returned string
        is replaced with the actual column name during table creation (e.g., by
        :meth:`TableStructure.add_column`).

        This is useful for using database‑specific types (like ``GEOMETRY``,
        ``JSON``, or user‑defined types) that are not standard in SQLite, or for
        adding custom validation logic.

        Args:
            type_name (str): The custom SQLite type name to use for the column.
                This will be inserted directly into the column definition.
            check (str, optional): A CHECK constraint expression to enforce on
                the column. The expression should use the placeholder
                ``'my_saulted_x'`` to refer to the column's value. If provided,
                it is wrapped in a ``CHECK(...)`` clause. Defaults to ``None``.

        Returns:
            str: A column definition fragment like
            ``'my_saulted_x GEOMETRY CHECK(my_saulted_x IS NOT NULL)'``, or
            ``'my_saulted_x JSON'`` if no check is given.

        Example:
            Using ``CUSTOM`` in a :class:`TableStructure`::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('shapes')
                # Add a geometry column with a NOT NULL check
                structure.add_column(
                    column_name='shape',
                    datatype=DataTypes.CUSTOM(
                        'GEOMETRY',
                        check='my_saulted_x IS NOT NULL'
                    )
                )
                # Generated column: my_saulted_x GEOMETRY CHECK(my_saulted_x IS NOT NULL)

                # Add a JSON column without any check
                structure.add_column(
                    column_name='metadata',
                    datatype=DataTypes.CUSTOM('JSON')
                )
                # Generated column: my_saulted_x JSON
        """
        if check:
            return f"my_saulted_x {type_name} CHECK({check})"
        return f"my_saulted_x {type_name}"


class TableStructure:
    """
    A builder for defining SQLite table schemas programmatically.

    This class provides a fluent interface for constructing a table
    definition, including columns, data types with constraints, primary
    keys, foreign keys, and the optional ``STRICT`` mode. The final
    ``CREATE TABLE`` SQL statement is generated by calling
    :meth:`get_structure` and can be executed via
    :meth:`Driver.create_table`.

    The class maintains an internal representation of the table schema,
    storing column definitions (with their types, defaults, uniqueness,
    nullability, and primary key status), foreign key constraints, and
    primary key configuration. It supports adding and removing columns
    before the table is created.

    **Usage pattern:**

    1. Instantiate ``TableStructure`` with the desired table name and
       optional ``STRICT`` mode.
    2. Add columns using :meth:`add_column` (each call returns ``self``
       for chaining).
    3. Optionally add foreign keys using :meth:`foreign_key`.
    4. Call :meth:`get_structure` to obtain the SQL string.
    5. Pass the structure to :meth:`Driver.create_table` to create the
       actual table in the database.

    **Placeholder replacement:**

    The class relies on the placeholder ``'my_saulted_x'`` in data type
    definitions (from :class:`DataTypes`). When a column is added, the
    placeholder is automatically replaced with the actual column name
    (wrapped in square brackets) to generate the correct column definition.

    Attributes:
        ON_CONFLICT (Literal): Type alias for conflict resolution options
            (``'ABORT'``, ``'ROLLBACK'``, ``'FAIL'``, ``'IGNORE'``,
            ``'REPLACE'``).
        ON_ACTION (Literal): Type alias for foreign key actions
            (``'CASCADE'``, ``'SET NULL'``, ``'SET DEFAULT'``,
            ``'RESTRICT'``, ``'NO ACTION'``).
        ON_INIT (Literal): Type alias for deferral initialization
            (``'DEFERRED'``, ``'IMMEDIATE'``).

    Example:
        Defining a simple table with primary key and foreign key::

            from ormophine.Sqlite import TableStructure, DataTypes, Driver

            # Build the structure
            structure = TableStructure('orders', strict=True)
            structure.add_column('id', DataTypes.INTEGER(), primary_key=True)
            structure.add_column('customer_id', DataTypes.INTEGER(), not_null=True)
            structure.add_column('total', DataTypes.DECIMAL(precision=10, scale=2))
            structure.add_column('status', DataTypes.ENUM('pending', 'shipped', 'delivered'))

            # Assume customers table already exists
            customers = db.table_object('customers')
            structure.foreign_key(
                column='customer_id',
                refrences_table=customers,
                refrences_column=customers.id,
                on_delete='CASCADE',
                on_update='RESTRICT'
            )

            # Create the table
            db = Driver('store.db')
            orders_table = db.create_table(structure)
    """
    ON_CONFLICT= Literal['ABORT', 'ROLLBACK', 'FAIL', 'IGNORE', 'REPLACE']
    ON_ACTION= Literal['CASCADE', 'SET NULL', 'SET DEFAULT', 'RESTRICT', 'NO ACTION']
    ON_INIT= Literal['DEFERRED', 'IMMEDIATE']

    def __init__(self, table_name: str, strict: bool = False, primarykey_on_conflict: ON_CONFLICT = 'ABORT'):
        """Initialize a new table structure definition.

        This class is used to build the complete definition of a database table
        before creation. It collects column definitions, primary keys, and foreign
        key constraints, and can generate the final ``CREATE TABLE`` SQL statement.
        The definition is mutable and supports adding/removing columns.

        Args:
            table_name (str): The name of the table to be created.
            strict (bool, optional): If ``True``, adds the ``STRICT`` keyword to
                the table definition, enforcing strict type checking in SQLite.
                Defaults to ``False``.
            primarykey_on_conflict (ON_CONFLICT, optional): The conflict resolution
                algorithm to use for the primary key when a constraint violation
                occurs. Must be one of ``'ABORT'``, ``'ROLLBACK'``, ``'FAIL'``,
                ``'IGNORE'``, or ``'REPLACE'``. Defaults to ``'ABORT'``.

        Attributes:
            strict (bool): Whether strict mode is enabled.
            table_query (str): The accumulated column definition string.
            primary_keys (list): List of column names that are part of the primary key.
            items (dict): Internal dictionary storing column metadata.
            name (str): The table name.
            foreigns (list): List of foreign key constraint strings.
            pkonc (str): The primary key conflict resolution.

        Example:
            Defining a table structure::

                from ormophine.Sqlite import DataTypes, TableStructure

                # Create a new table structure for 'users'
                structure = TableStructure('users', strict=True)

                # Add columns
                structure.add_column('id', DataTypes.INTEGER(primary_key=True))
                structure.add_column('username', DataTypes.VARCHAR(max_length=50))
                structure.add_column('age', DataTypes.TINYINT(unsigned=True))

                # Generate the CREATE TABLE statement
                create_sql = structure.get_structure()
                # CREATE TABLE [users] ( [id] INTEGER, [username] TEXT CHECK(LENGTH([username]) <= 50), [age] INTEGER CHECK([age] BETWEEN 0 AND 255), PRIMARY KEY([id]) ON CONFLICT ABORT ) STRICT;
        """
        self.strict= strict
        self.table_query= ''
        self.primary_keys= []
        self.items= {}
        self.name= table_name
        self.foreigns= []
        self.pkonc = primarykey_on_conflict

    def add_column(self, column_name: str, datatype: DataTypes,
                default_value=None, unique: bool = None,
                unique_on_conflict: ON_CONFLICT = 'ABORT',
                not_null: bool = None,
                not_null_on_conflict: ON_CONFLICT = 'ABORT',
                primary_key: bool = None):
        """Add a column definition to the table structure.

        This method appends a column definition to the internal query string
        used to generate the final ``CREATE TABLE`` statement. It validates
        that the column name is not already defined, and raises an exception
        if a duplicate is found. The method also stores column metadata in
        the ``items`` dictionary for later retrieval (e.g., via
        :meth:`get_columns`).

        The ``datatype`` parameter should be one of the type strings returned
        by the :class:`DataTypes` factory methods (e.g.,
        ``DataTypes.INTEGER()``, ``DataTypes.TEXT(max_length=50)``, etc.).
        These strings contain the placeholder ``'my_saulted_x'`` which is
        replaced with the actual column name (properly bracketed) in this
        method.

        Args:
            column_name (str): The name of the column to add.
            datatype (DataTypes): A column definition string from
                :class:`DataTypes` (e.g., ``DataTypes.INTEGER()``). The
                placeholder ``'my_saulted_x'`` will be replaced with the
                column name.
            default_value (optional): The default value for the column.
                Cannot be a ``bytes`` object. If provided, it is added as
                ``DEFAULT <value>``; strings are quoted, others are used
                as‑is.
            unique (bool, optional): If ``True``, adds a ``UNIQUE``
                constraint. Defaults to ``None``.
            unique_on_conflict (ON_CONFLICT, optional): Conflict resolution
                for the UNIQUE constraint (e.g., ``'ABORT'``, ``'IGNORE'``,
                ``'REPLACE'``). Defaults to ``'ABORT'``.
            not_null (bool, optional): If ``True``, adds a ``NOT NULL``
                constraint. Defaults to ``None``.
            not_null_on_conflict (ON_CONFLICT, optional): Conflict resolution
                for the NOT NULL constraint. Defaults to ``'ABORT'``.
            primary_key (bool, optional): If ``True``, marks this column as
                part of the primary key. The column name is added to the
                ``primary_keys`` list. Defaults to ``None``.

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

        Raises:
            Exception: If a column with the same name has already been added.
            Exception: If ``default_value`` is of type ``bytes`` (not
                supported).

        Example:
            Building a table structure::

                from ormophine.Sqlite import TableStructure, DataTypes

                structure = TableStructure('users')
                structure.add_column(
                    'id',
                    DataTypes.INTEGER(unsigned=True),
                    primary_key=True
                ).add_column(
                    'username',
                    DataTypes.VARCHAR(max_length=50),
                    unique=True,
                    not_null=True
                ).add_column(
                    'age',
                    DataTypes.TINYINT(min_val=0, max_val=150),
                    default_value=18
                )

                # The structure can then be used to create a table:
                # db.create_table(structure)
        """
        for item in self.table_query.split(','):
            if column_name in item:
                raise Exception('You have added this column befor\nif you wanna modify this column , delete this column and then add a new one with desired options') if item.split(' ')[0] == column_name else None
        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, unique_on_conflict, not_null, not_null_on_conflict, primary_key]
        self.table_query = self.table_query + f' {datatype.replace('my_saulted_x' , f'[{column_name.strip()}]')}{f' UNIQUE ON CONFLICT {unique_on_conflict}' if unique else ''}{f' NOT NULL ON CONFLICT {not_null_on_conflict}' if not_null else ''}{f' DEFAULT {f"'{default_value}'" if type(default_value) == str else default_value}' if default_value else ''},'
        return self

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

        This method deletes a column that was previously added via
        :meth:`add_column`. It updates the internal SQL query fragment and
        the column metadata dictionary. If the column does not exist, an
        exception is raised.

        The method is typically used when building a table structure
        dynamically before creation. After deletion, the column will not
        appear in the generated ``CREATE TABLE`` statement.

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

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

        Raises:
            Exception: If no column with the given name exists in the
                structure.

        Example:
            Building a table structure and removing a column::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column('id', DataTypes.INTEGER())
                structure.add_column('name', DataTypes.VARCHAR(50))
                structure.add_column('age', DataTypes.TINYINT())

                # Remove the 'age' column
                structure.delete_column('age')
                # The 'age' column will not appear in the final CREATE TABLE.

                # Create the table without the 'age' column
                db.create_table(structure)
        """
        query_list = self.table_query.split(',')
        self.items.pop(column_name)
        for item in query_list:
            if item.startswith(column_name):
                query_list.remove(item)
                self.table_query = ','.join(query_list)
                return self
        raise Exception(f'No column found with name ({column_name})')

    def get_columns(self):
        """Retrieve metadata for all columns defined in the table structure.

        This method returns a list of dictionaries, each containing detailed
        information about a column that has been added to this table structure
        via :meth:`add_column`. The metadata includes the column name, data type,
        default value, uniqueness constraints, nullability, conflict handling
        settings, and primary key status.

        The returned dictionaries have the following keys:

        * ``name`` (str): The column name.
        * ``datatype`` (str): The column definition string (including CHECK
        constraints) as returned by a :class:`DataTypes` method.
        * ``default_value`` (Any): The default value for the column, or ``None``.
        * ``unique`` (bool): Whether the column has a UNIQUE constraint.
        * ``unique_on_conflict`` (str): The ON CONFLICT clause for the UNIQUE
        constraint (e.g., 'ABORT', 'REPLACE').
        * ``not_null`` (bool): Whether the column has a NOT NULL constraint.
        * ``not_null_on_conflict`` (str): The ON CONFLICT clause for the NOT NULL
        constraint.
        * ``primary_key`` (bool): Whether the column is part of the primary key.
        (Note: the key is intentionally misspelled to match the implementation.)

        Returns:
            list[dict]: A list of dictionaries, one per column, in the order
            they were added. The list is empty if no columns have been defined.

        Example:
            Using ``get_columns`` to inspect a table structure::

                from ormophine.Sqlite import DataTypes, TableStructure

                structure = TableStructure('users')
                structure.add_column('id', DataTypes.INTEGER(), primary_key=True)
                structure.add_column('name', DataTypes.VARCHAR(max_length=50), unique=True)
                structure.add_column('age', DataTypes.TINYINT(min_val=0, max_val=150))

                columns = structure.get_columns()
                for col in columns:
                    print(f"{col['name']}: unique={col['unique']}, pk={col['primari_key']}")
                # Output:
                # id: unique=False, pk=True
                # name: unique=True, pk=False
                # age: unique=False, 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['unique_on_conflict'] = values[3]
            items_dict['not_null'] = True if values[4] else False
            items_dict['not_null_on_conflict'] = values[5]
            items_dict['primary_key'] = True if values[6] 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, deferrable: bool = True,
                    initially: ON_INIT = 'DEFERRED'):
        """Add a foreign key constraint to the table structure.

        This method appends a ``FOREIGN KEY`` definition to the internal list
        of foreign keys, which will be included in the final ``CREATE TABLE``
        statement. The method supports specifying actions for ``ON DELETE``
        and ``ON UPDATE``, as well as deferrability and initialization timing.

        The foreign key references a column in another table, enforcing
        referential integrity at the database level.

        Args:
            column (str): The name of the column in the current table that
                acts as the foreign key.
            refrences_table (Table): The referenced table object (the parent
                table). Note: This is a forward reference to a :class:`Table`
                instance.
            refrences_column (Column): The referenced column object in the
                parent table.
            on_delete (ON_ACTION, optional): Action to take when the referenced
                row is deleted. Valid values are from the ``ON_ACTION`` type:
                ``'CASCADE'``, ``'SET NULL'``, ``'SET DEFAULT'``,
                ``'RESTRICT'``, or ``'NO ACTION'``. Defaults to ``None``
                (no action specified).
            on_update (ON_ACTION, optional): Action to take when the referenced
                column is updated. Same options as ``on_delete``. Defaults to
                ``None``.
            deferrable (bool, optional): If ``True``, the constraint can be
                deferred until the transaction commits. If ``False``, it is
                checked immediately. Defaults to ``True``.
            initially (ON_INIT, optional): Defines the initial deferral state.
                Can be ``'DEFERRED'`` (default) or ``'IMMEDIATE'``. Only
                relevant if ``deferrable`` is ``True``.

        Returns:
            TableStructure: The current instance (``self``), allowing method
            chaining for building the table structure.

        Example:
            Assuming two tables ``orders`` and ``customers``::

                from ormophine.Sqlite import Driver, Table, TableStructure, DataTypes

                db = Driver('store.db')

                # Build customers table first
                customers_structure = TableStructure('customers')
                customers_structure.add_column('id', DataTypes.INTEGER(primary_key=True))
                customers_structure.add_column('name', DataTypes.VARCHAR(max_length=50))

                # Build orders table with a foreign key
                orders_structure = TableStructure('orders')
                orders_structure.add_column('id', DataTypes.INTEGER(primary_key=True))
                orders_structure.add_column('customer_id', DataTypes.INTEGER())
                orders_structure.add_column('total', DataTypes.DECIMAL(10,2))

                # Add foreign key referencing customers.id
                # First create the Table objects (or use existing ones)
                customers_table = db.create_table(customers_structure)

                # Add foreign key to orders_structure before creation
                orders_structure.foreign_key(
                    column='customer_id',
                    refrences_table=customers_table,
                    refrences_column=customers_table.id,
                    on_delete='CASCADE',
                    on_update='RESTRICT',
                    deferrable=False
                )

                # Then create orders table
                db.create_table(orders_structure)
                # This ensures referential integrity between orders and customers.
        """
        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 ''}{' DEFERRABLE' if deferrable else ' NOT DEFERRABLE'}{f' INITIALLY {initially}' if initially else ''}')
        return self

    def get_structure(self):
        """Generate the complete SQL `CREATE TABLE` statement for the table structure.

        This method constructs and returns the full SQL `CREATE TABLE` command
        based on the columns, constraints, foreign keys, primary keys, and
        strict mode settings that have been added to this `TableStructure`
        instance. The returned string includes column definitions with data
        types, `CHECK` constraints, `UNIQUE`, `NOT NULL`, `DEFAULT` values,
        primary key definitions, foreign key constraints, and the optional
        `STRICT` mode.

        The generated SQL uses the table name as provided in the constructor,
        and wraps column names in square brackets (`[]`) for safety.

        Returns:
            str: A complete SQL `CREATE TABLE` statement that can be executed
            to create the table in the database.

        Example:
            Creating a `TableStructure` and getting its SQL::

                from ormophine.Sqlite import TableStructure, DataTypes

                structure = TableStructure('users', strict=True)
                structure.add_column('id', DataTypes.INTEGER(), primary_key=True)
                structure.add_column('name', DataTypes.VARCHAR(max_length=50), not_null=True)
                structure.add_column('age', DataTypes.TINYINT(min_val=0, max_val=150))

                sql = structure.get_structure()
                # sql will be something like:
                # CREATE TABLE [users] (
                #   [id] INTEGER,
                #   [name] TEXT CHECK(LENGTH([name]) <= 50) NOT NULL ON CONFLICT ABORT,
                #   [age] INTEGER CHECK([age] BETWEEN 0 AND 150),
                #   PRIMARY KEY([id]) ON CONFLICT ABORT
                # ) STRICT;
        """
        return f'CREATE TABLE [{self.name}] ({self.table_query[:-1]}{',' if self.primary_keys else ''}{f'PRIMARY KEY({', '.join(self.primary_keys)}) ON CONFLICT {self.pkonc}' if self.primary_keys else ''}{',' if self.foreigns else ''}{','.join(self.foreigns) if self.foreigns else ''}) {'STRICT' if self.strict else ''};'

