Batching Modifications
######################

A :class:`~google.cloud.spanner.batch.Batch` represents a set of data
modification operations to be performed on tables in a database.  Use of a
``Batch`` does not require creating an explicit
:class:`~google.cloud.spanner.snapshot.Snapshot` or
:class:`~google.cloud.spanner.transaction.Transaction`.  Until
:meth:`~google.cloud.spanner.batch.Batch.commit` is called on a ``Batch``,
no changes are propagated to the back-end.


Starting a Batch
----------------

Construct a :class:`~google.cloud.spanner.batch.Batch` object from a :class:`~google.cloud.spanner.database.Database` object:

.. code:: python

    from google.cloud import spanner

    client = spanner.Client()
    instance = client.instance(INSTANCE_NAME)
    database = instance.database(DATABASE_NAME)

    batch = database.batch()


Inserting records using a Batch
-------------------------------

:meth:`Batch.insert` adds one or more new records to a table.  Fails if
any of the records already exists.

.. code:: python

    batch.insert(
        'citizens', columns=['email', 'first_name', 'last_name', 'age'],
        values=[
            ['phred@exammple.com', 'Phred', 'Phlyntstone', 32],
            ['bharney@example.com', 'Bharney', 'Rhubble', 31],
        ])

.. note::

    Ensure that data being sent for ``STRING`` columns uses a text string
    (``str`` in Python 3; ``unicode`` in Python 2).

    Additionally, if you are writing data intended for a ``BYTES`` column, you
    must base64 encode it.


Update records using a Batch
-------------------------------

:meth:`Batch.update` updates one or more existing records in a table.  Fails
if any of the records does not already exist.

.. code:: python

    batch.update(
        'citizens', columns=['email', 'age'],
        values=[
            ['phred@exammple.com', 33],
            ['bharney@example.com', 32],
        ])

.. note::

    Ensure that data being sent for ``STRING`` columns uses a text string
    (``str`` in Python 3; ``unicode`` in Python 2).

    Additionally, if you are writing data intended for a ``BYTES`` column, you
    must base64 encode it.


Insert or update records using a Batch
--------------------------------------

:meth:`Batch.insert_or_update` inserts *or* updates one or more records in a
table.  Existing rows have values for the supplied columns overwritten;  other
column values are preserved.

.. code:: python

    batch.insert_or_update(
        'citizens', columns=['email', 'first_name', 'last_name', 'age'],
        values=[
            ['phred@exammple.com', 'Phred', 'Phlyntstone', 31],
            ['wylma@example.com', 'Wylma', 'Phlyntstone', 29],
        ])

.. note::

    Ensure that data being sent for ``STRING`` columns uses a text string
    (``str`` in Python 3; ``unicode`` in Python 2).

    Additionally, if you are writing data intended for a ``BYTES`` column, you
    must base64 encode it.


Replace records using a Batch
-----------------------------

:meth:`Batch.replace` inserts *or* updates one or more records in a
table.  Existing rows have values for the supplied columns overwritten;  other
column values are set to null.

.. code:: python

    batch.replace(
        'citizens', columns=['email', 'first_name', 'last_name', 'age'],
        values=[
            ['bharney@example.com', 'Bharney', 'Rhubble', 30],
            ['bhettye@example.com', 'Bhettye', 'Rhubble', 30],
        ])

.. note::

    Ensure that data being sent for ``STRING`` columns uses a text string
    (``str`` in Python 3; ``unicode`` in Python 2).

    Additionally, if you are writing data intended for a ``BYTES`` column, you
    must base64 encode it.


Delete records using a Batch
----------------------------

:meth:`Batch.delete` removes one or more records from a table.  Non-existent
rows do not cause errors.

.. code:: python

    from google.cloud.spanner.keyset import KeySet

    to_delete = KeySet(keys=[
        ('bharney@example.com',)
        ('nonesuch@example.com',)
    ])

    batch.delete('citizens', to_delete)


Commit changes for a Batch
--------------------------

After describing the modifications to be made to table data via the
:meth:`Batch.insert`, :meth:`Batch.update`, :meth:`Batch.insert_or_update`,
:meth:`Batch.replace`, and :meth:`Batch.delete` methods above, send them to
the back-end by calling :meth:`Batch.commit`, which makes the ``Commit``
API call.

.. code:: python

    batch.commit()


Use a Batch as a Context Manager
--------------------------------

Rather than calling :meth:`Batch.commit` manually, you can use the
:class:`Batch` instance as a context manager, and have it called automatically
if the ``with`` block exits without raising an exception.

.. code:: python

    from google.cloud.spanner.keyset import KeySet

    client = spanner.Client()
    instance = client.instance(INSTANCE_NAME)
    database = instance.database(DATABASE_NAME)

    to_delete = KeySet(keys=[
        ('bharney@example.com',)
        ('nonesuch@example.com',)
    ])

    with database.batch() as batch:

        batch.insert(
            'citizens', columns=['email', 'first_name', 'last_name', 'age'],
            values=[
                ['phred@exammple.com', 'Phred', 'Phlyntstone', 32],
                ['bharney@example.com', 'Bharney', 'Rhubble', 31],
            ])

        batch.update(
            'citizens', columns=['email', 'age'],
            values=[
                ['phred@exammple.com', 33],
                ['bharney@example.com', 32],
            ])

        ...

        batch.delete('citizens', to_delete)


Next Step
---------

Next, learn about :doc:`snapshot-usage`.
