Cockroach Database

CockroachDB (CRDB) is well supported by peewee.

  1. from playhouse.cockroachdb import CockroachDatabase
  2. db = CockroachDatabase('my_app', user='root', host='10.1.0.8')

The playhouse.cockroachdb extension module provides the following classes and helpers:

Special field-types that may be useful when using CRDB:

  • UUIDKeyField - a primary-key field implementation that uses CRDB’s UUID type with a default randomly-generated UUID.
  • RowIDField - a primary-key field implementation that uses CRDB’s INT type with a default unique_rowid().
  • JSONField - same as the Postgres BinaryJSONField, as CRDB treats JSON as JSONB.
  • ArrayField - same as the Postgres extension (but does not support multi-dimensional arrays).

CRDB is compatible with Postgres’ wire protocol and exposes a very similar SQL interface, so it is possible (though not recommended) to use PostgresqlDatabase with CRDB:

  1. CRDB does not support nested transactions (savepoints), so the atomic() method has been implemented to enforce this when using CockroachDatabase. For more info CRDB Transactions.
  2. CRDB may have subtle differences in field-types, date functions and introspection from Postgres.
  3. CRDB-specific features are exposed by the CockroachDatabase, such as specifying a transaction priority or the AS OF SYSTEM TIME clause.

CRDB Transactions

CRDB does not support nested transactions (savepoints), so the atomic() method on the CockroachDatabase has been modified to raise an exception if an invalid nesting is encountered. If you would like to be able to nest transactional code, you can use the transaction() method, which will ensure that the outer-most block will manage the transaction (e.g., exiting a nested-block will not cause an early commit).

Example:

  1. @db.transaction()
  2. def create_user(username):
  3. return User.create(username=username)
  4. def some_other_function():
  5. with db.transaction() as txn:
  6. # do some stuff...
  7. # This function is wrapped in a transaction, but the nested
  8. # transaction will be ignored and folded into the outer
  9. # transaction, as we are already in a wrapped-block (via the
  10. # context manager).
  11. create_user('some_user@example.com')
  12. # do other stuff.
  13. # At this point we have exited the outer-most block and the transaction
  14. # will be committed.
  15. return

CRDB provides client-side transaction retries, which are available using a special run_transaction() helper. This helper method accepts a callable, which is responsible for executing any transactional statements that may need to be retried.

Simplest possible example of run_transaction():

  1. def create_user(email):
  2. # Callable that accepts a single argument (the database instance) and
  3. # which is responsible for executing the transactional SQL.
  4. def callback(db_ref):
  5. return User.create(email=email)
  6. return db.run_transaction(callback, max_attempts=10)
  7. huey = create_user('huey@example.com')

Note

The cockroachdb.ExceededMaxAttempts exception will be raised if the transaction cannot be committed after the given number of attempts. If the SQL is mal-formed, violates a constraint, etc., then the function will raise the exception to the caller.

Example of using run_transaction() to implement client-side retries for a transaction that transfers an amount from one account to another:

  1. from playhouse.cockroachdb import CockroachDatabase
  2. db = CockroachDatabase('my_app')
  3. def transfer_funds(from_id, to_id, amt):
  4. """
  5. Returns a 3-tuple of (success?, from balance, to balance). If there are
  6. not sufficient funds, then the original balances are returned.
  7. """
  8. def thunk(db_ref):
  9. src, dest = (Account
  10. .select()
  11. .where(Account.id.in_([from_id, to_id])))
  12. if src.id != from_id:
  13. src, dest = dest, src # Swap order.
  14. # Cannot perform transfer, insufficient funds!
  15. if src.balance < amt:
  16. return False, src.balance, dest.balance
  17. # Update each account, returning the new balance.
  18. src, = (Account
  19. .update(balance=Account.balance - amt)
  20. .where(Account.id == from_id)
  21. .returning(Account.balance)
  22. .execute())
  23. dest, = (Account
  24. .update(balance=Account.balance + amt)
  25. .where(Account.id == to_id)
  26. .returning(Account.balance)
  27. .execute())
  28. return True, src.balance, dest.balance
  29. # Perform the queries that comprise a logical transaction. In the
  30. # event the transaction fails due to contention, it will be auto-
  31. # matically retried (up to 10 times).
  32. return db.run_transaction(thunk, max_attempts=10)

CRDB APIs

class CockroachDatabase(database[, \*kwargs*])

CockroachDB implementation, based on the PostgresqlDatabase and using the psycopg2 driver.

Additional keyword arguments are passed to the psycopg2 connection constructor, and may be used to specify the database user, port, etc.

  • run_transaction(callback[, max_attempts=None[, system_time=None[, priority=None]]])

    Parameters:
    • callback – callable that accepts a single db parameter (which will be the database instance this method is called from).
    • max_attempts (int) – max number of times to try before giving up.
    • system_time (datetime) – execute the transaction AS OF SYSTEM TIME with respect to the given value.
    • priority (str) – either “low”, “normal” or “high”.
    Returns:

    returns the value returned by the callback.

    Raises:

    ExceededMaxAttempts if max_attempts is exceeded.

    Run SQL in a transaction with automatic client-side retries.

    User-provided callback:

    • Must accept one parameter, the db instance representing the connection the transaction is running under.
    • Must not attempt to commit, rollback or otherwise manage the transaction.
    • May be called more than one time.
    • Should ideally only contain SQL operations.

    Additionally, the database must not have any open transactions at the time this function is called, as CRDB does not support nested transactions. Attempting to do so will raise a NotImplementedError.

    Simplest possible example:

    1. def create_user(email):
    2. def callback(db_ref):
    3. return User.create(email=email)
    4. return db.run_transaction(callback, max_attempts=10)
    5. user = create_user('huey@example.com')

class PooledCockroachDatabase(database[, \*kwargs*])

CockroachDB connection-pooling implementation, based on PooledPostgresqlDatabase. Implements the same APIs as CockroachDatabase, but will do client-side connection pooling.

run_transaction(db, callback[, max_attempts=None[, system_time=None[, priority=None]]])

Run SQL in a transaction with automatic client-side retries. See CockroachDatabase.run_transaction() for details.

Parameters:
  • db (CockroachDatabase) – database instance.
  • callback – callable that accepts a single db parameter (which will be the same as the value passed above).

Note

This function is equivalent to the identically-named method on the CockroachDatabase class.

class UUIDKeyField

UUID primary-key field that uses the CRDB gen_random_uuid() function to automatically populate the initial value.

class RowIDField

Auto-incrementing integer primary-key field that uses the CRDB unique_rowid() function to automatically populate the initial value.

See also:

  • BinaryJSONField from the Postgresql extension (available in the cockroachdb extension module, and aliased to JSONField).
  • ArrayField from the Postgresql extension.