=============
Batch Queries
=============

cqlengine supports batch queries using the BatchQuery class. Batch queries can be started and stopped manually, or within a context manager. To add queries to the batch object, you just need to precede the create/save/delete call with a call to batch, and pass in the batch object.


Batch Query General Use Pattern
===============================

You can only create, update, and delete rows with a batch query, attempting to read rows out of the database with a batch query will fail.

.. code-block:: python

    from cassandra.cqlengine.query import BatchQuery

    #using a context manager
    with BatchQuery() as b:
        now = datetime.now()
        em1 = ExampleModel.batch(b).create(example_type=0, description="1", created_at=now)
        em2 = ExampleModel.batch(b).create(example_type=0, description="2", created_at=now)
        em3 = ExampleModel.batch(b).create(example_type=0, description="3", created_at=now)

    # -- or --

    #manually
    b = BatchQuery()
    now = datetime.now()
    em1 = ExampleModel.batch(b).create(example_type=0, description="1", created_at=now)
    em2 = ExampleModel.batch(b).create(example_type=0, description="2", created_at=now)
    em3 = ExampleModel.batch(b).create(example_type=0, description="3", created_at=now)
    b.execute()

    # updating in a batch

    b = BatchQuery()
    em1.description = "new description"
    em1.batch(b).save()
    em2.description = "another new description"
    em2.batch(b).save()
    b.execute()

    # deleting in a batch
    b = BatchQuery()
    ExampleModel.objects(id=some_id).batch(b).delete()
    ExampleModel.objects(id=some_id2).batch(b).delete()
    b.execute()


Typically you will not want the block to execute if an exception occurs inside the `with` block.  However, in the case that this is desirable, it's achievable by using the following syntax:

.. code-block:: python

    with BatchQuery(execute_on_exception=True) as b:
        LogEntry.batch(b).create(k=1, v=1)
        mystery_function() # exception thrown in here
        LogEntry.batch(b).create(k=1, v=2) # this code is never reached due to the exception, but anything leading up to here will execute in the batch.

If an exception is thrown somewhere in the block, any statements that have been added to the batch will still be executed.  This is useful for some logging situations.

Batch Query Execution Callbacks
===============================

In order to allow secondary tasks to be chained to the end of batch, BatchQuery instances allow callbacks to be
registered with the batch, to be executed immediately after the batch executes.

Multiple callbacks can be attached to same BatchQuery instance, they are executed in the same order that they
are added to the batch.

The callbacks attached to a given batch instance are executed only if the batch executes. If the batch is used as a
context manager and an exception is raised, the queued up callbacks will not be run.

.. code-block:: python

    def my_callback(*args, **kwargs):
        pass

    batch = BatchQuery()

    batch.add_callback(my_callback)
    batch.add_callback(my_callback, 'positional arg', named_arg='named arg value')

    # if you need reference to the batch within the callback,
    # just trap it in the arguments to be passed to the callback:
    batch.add_callback(my_callback, cqlengine_batch=batch)

    # once the batch executes...
    batch.execute()

    # the effect of the above scheduled callbacks will be similar to
    my_callback()
    my_callback('positional arg', named_arg='named arg value')
    my_callback(cqlengine_batch=batch)

Failure in any of the callbacks does not affect the batch's execution, as the callbacks are started after the execution
of the batch is complete.

Logged vs Unlogged Batches
---------------------------
By default, queries in cqlengine are LOGGED, which carries additional overhead from UNLOGGED.  To explicitly state which batch type to use, simply:


.. code-block:: python

    from cassandra.cqlengine.query import BatchType
    with BatchQuery(batch_type=BatchType.Unlogged) as b:
        LogEntry.batch(b).create(k=1, v=1)
        LogEntry.batch(b).create(k=1, v=2)
