© Charles Bell 2018
Charles BellIntroducing the MySQL 8 Document Storehttps://doi.org/10.1007/978-1-4842-2725-1_5

5. X Developer API

Charles Bell1 
(1)
Warsaw, Virginia, USA
 

The X Developer Application Programming Interface, or X DevAPI, is a library of classes and methods that implement a new NoSQL interface for MySQL. To be specific, the X DevAPI is designed to allow easy interaction with JSON documents and relational data. The X DevAPI has classes devoted to supporting both concepts allowing developers to use either (or both) in their applications. The X DevAPI together with the X Protocol, X Plugin, and clients written to expose the X DevAPI, forms the new MySQL 8 Document Store feature.

As we will see, there are many aspects to working with the X DevAPI. However, once you master the basics of connecting and requesting object instances, forming expressions, and working with the JSON documents, the X DevAPI is very easy to learn and is efficient for writing document store or relational data applications.

We have already seen several examples of the X DevAPI in action throughout this book for relational data as most database administrators are familiar with that form of database interaction. However, we have not seen a comprehensive list of the classes and methods provided for the document store. This chapter contains nearly all the public classes and methods available in the X DevAPI (some lesser used classes are omitted for brevity).

Although all the X DevAPI client connectors support all the classes, there are some minor differences in how each of the clients implements the X DevAPI. In particular, the names of classes and methods vary slightly to match the development practices for the language. For example, the accepted style guide for a language may discourage camelCase names whereas the style guide for another may suggest the use of underscores and no capitalization.

When learning to use the X DevAPI, it can be helpful to review examples from other languages. Although the naming schemes may differ and the syntax may be quite different, the basic classes and methods are similar enough that you can still learn what methods to use. This is the major reason I use Python examples. You can use the Python examples to see how to use the classes and although the methods may have slightly different naming schemes, the methods and practices are the same from one language to another. Plus, Python is easy to read and you do not need large, complicated development tools (e.g., a C++ or .Net compiler). All you need is a Python interpreter and it is available for almost all platforms.

Although this chapter contains some similar information from other chapters, it uses a stepwise approach to demonstrate the X DevAPI via a series of code examples. A set of tables describing the major classes and their methods is included as a reference to use as a guide when writing your own code for a document store application.

I begin with a comprehensive overview of the characteristics of the X DevAPI and then I move on to a detailed reference of the major classes and methods. Along the way I give many examples using the X DevAPI. We will not see every possible class or method that is part of the X DevAPI, but we will see the major components (classes and methods) that you will need to master to write document store applications. If you need additional information for the less frequently used classes and methods, see the "For More Information" section for references to developer documentation.

Overview

There are several powerful features in the X DevAPI. We have seen most of these in action in previous chapters but now we will see the features that the X DevAPI provides. Recall that these features are realized through the clients that support the X Protocol as well as the X DevAPI. The features included in the X DevAPI include the following. We will see these features and how they are realized later in this chapter.
  • MySQLX: A module used to get a session object resulting from an X Protocol connection to a MySQL server.

  • Sessions: A connection to a MySQL server.

  • Collections: An organizational abstraction for storing JSON documents.

  • Documents: JSON documents are the primary storage mechanism for data in collections.

  • CRUD operations: Simple methods for create, read, update, and delete operations. Read operations are simple and easy to understand.

  • Relational data: Implements CRUD operations for traditional relational data including SQL statement execution and results processing.

  • Expressions: Use modern practices and syntax styles are used to get away from traditional SQL-String-Building for finding things in your collections and documents.

  • Parallel execution: Nonblocking, asynchronous calls follow common host language patterns.

  • Method chaining: The API is built so that methods that create or retrieve (get) an object return an instance of that object. This allows us to combine several methods together (called method chaining). Although method chaining is neither a new concept nor unique to the X DevAPI, it is a very powerful mechanism for making our code more expressive and easier to read.

Note

The X DevAPI is only available when using the X Plugin. You cannot use the X DevAPI without the X Plugin installed and then only through an X Protocol enabled client or database connector.

Clients

The X DevAPI is only available through one of the clients that implement the X Protocol. Furthermore, to use any of these clients, you also must have the X Plugin installed and configured for use on your server. In particular with any of the following:

Note

Some of the database connector versions are not yet generally available (GA) releases. In those cases, you can find the correct version by clicking on the Development Releases tab on the download page. As long as you’re not using them in production, using a DMR release should be fine. Be sure to contact your MySQL sales representative for assistance if you do not see a GA release of a component you want to use.

Target Language Conformity

When you encounter a new API such as the X DevAPI, it is often the case that you would expect the names of classes and methods to be the same from one language to another. That is, a class with a method named getSomething() would be spelled the same from one language to another. However, it is a common (and some would say preferred) practice to obey platform- and language-specific naming conventions sacrifice commonality in the API to ensure continued compliance for the language naming standard. If you work with different programming languages as I do, you will find this is common place and thus one knows to expect some variations from one language to another for the same API.

The X DevAPI subscribes to this practice and the clients that implement the API conform to their platform and language standards. In most cases, this may be only a change in the use of capital letters in the name but may also result in the addition (or omission) of underscores. We have already discovered that Connector/Python (C/Py) uses underscores in the names and does not use capital letters. Connector/Java (C/J), Connector/Node.js (C/Node.js), Connector/.Net (C/Net), and Connector/C++ (C/C++) use slightly different capitalization.

It is not just the method names that have different spellings. There also may be subtle differences in how you work with the results from methods or interact with objects. That is, the clients conform to the normal practices for the language for common constructs and concepts such as iteration. For example, if the language has a concept of a list for returning multiple items (say versus an array), the methods will return a list. Although this may seem strange as you read more about the X DevAPI, it does pay benefits. That is, the resulting code you write is compliant with your choice of language standards.

To demonstrate the differences, Table 5-1 shows an example of the minor differences in languages for the MySQL X package method names. Note that even the package name is spelled differently from one language to another. A Python developer would see the Python naming scheme and not think it unusual but the Java example may seem strange.
Table 5-1

MySQL X Module

Returns

Name

Method

Language

Parameters

Session Object

MysqlxSessionFactory

getSession()

Java

Connection URI or connection properties

mysqlx

getSession()

Node.js

Connection URI or connection properties

MySQLX

GetSession()

DotNet

Connection URI or connection data object

mysqlx

get_session()

Python

Connection dictionary

There is one other difference you may notice when exploring the X DevAPI. The clients that implement the API have some very different mechanisms for how to work with data. In some cases, such as C/Net, everything is a class and it is common to use a class to contain data but in C/Py, the use of lists and dictionaries are preferred. Thus, the clients (specifically the database connectors) may implement some of the mechanisms for iteration, retrieval, and encapsulation differently. However, as with the naming conventions, the differences are for the benefit of the developer so that the X DevAPI “works” the way it should in the target language.

Let’s look at one more example of the differences. Table 5-2 shows the method available for working with schemas. I’ve included the four languages for the four database connectors (but group Java and JavaScript) as well as a short description of the task, parameter, and return type for each method.
Table 5-2

Session—Create Schema Method

Description

Returns

Language

Method

Parameters

Create a new schema

Schema object

Java/Node.js

createSchema()

String—schema name

DotNet

CreateSchema()

String—schema name

Python

create_schema()

String—schema name

In the next section, we examine the major code module, named mysqlx, for the X DevAPI.

Note

The code examples in this chapter are written in Python as scripts that use the Connector/Python database connector. Thus, you will need the connector installed to use these examples. Finally, to run the examples, you execute them with the python command like this: python ./script1.py.

The mysqlx module (sometimes called a package) works with a Session (X Protocol). There is also a module for working with InnoDB Cluster (named dba), and several common classes including those for columns, rows, and so forth.

Note

This chapter contains a lot of information about objects and classes. Objects are an instance of a code class (at execution) and a class is simply the code construct.

MySQL X Module

The mysqlx module is the entry point for writing your document store applications and communicating with the X DevAPI. We use this module to pass connection information to the server in the form of a connection string or a language-specific construct (e.g., a dictionary in Python) to pass the connection parameters either as a URI or a connection dictionary as the parameter (not both). Recall that a uniform resource identifier (URI) a special string coded uses the following format:

ConnectURI ::= ' 'user_id' ':' 'user_password' '@' 'hostname' ':' 'port_number' '/' 'default_schema_name' '

Note that the password, port, and schema are optional but the user and host are required. Schema in this case is the default schema (database) that you want to use when connecting. The method to get a session object is shown in the following.

get_session(<URI or connection dictionary>)

The following shows examples of getting a session object instance using a dictionary of connection options and getting a session object instance using a connection string (URI) .

import mysqlx
mysqlx_session1 = mysqlx.get_session({'host': 'localhost', 'port': 33060, 'user': 'root', 'password': 'secret'})
mysqlx_session2 = mysqlx.get_session('root:secret@localhost:33060')

The resulting variable will point to an object instance should the connection succeed. If it fails, you could get an error or an uninitialized connection as the result. We will see more about checking errors in a later section.

In the next section, we begin our exploration of the classes and methods (components) in the X DevAPI.

Classes and Methods

The following sections examine each of the major classes and their methods (features) for the mysqlx module. These classes are only accessible from a Session object—the same returned from the get_session( ) method. Because this book is about the document store, we focus on those classes for the mysqlx module.

We will discover the methods including classes and for working with schemas (databases), managing transactions, and checking or closing the connection. The material presented includes the most frequently used classes and methods grouped by use or application rather than a strict hierarchy. This allows for a shorter overview that follows a more logical path exploring the API. If you want to see all the details of the modules and classes as well as the raw Doxygen documentation for the code, see the “For More Information” section at the end of the chapter for links to the API documentation for each database connector. I include examples in this chapter that illustrates many of the methods presented.

Let’s begin with a brief overview of the mysqlx module. Table 5-3 shows the objects available in the module. Use this table as a quick reference guide to the X DevAPI.
Table 5-3

Objects in the mysqlx Module

Area

Method

Description

Connection

Session

Enables interaction with an X Protocol enabled MySQL Product

CRUD

Schema

A client-side representation of a database schema; provides access to the schema contents

Collection

Represents a collection of documents on a schema

Table

Represents a database table on a schema

View

Represents a database view on a schema

Result

ColumnMetaData

Returns metadata on the columns

Row

Represents a row element returned from a SELECT query

Result

Allows retrieving information about nonquery operations performed on the database

BufferingResult

Provides base functionality for buffering result objects

RowResult

Allows traversing the Row objects returned by a Table.select operation

SqlResult

Represents a result from a SQL statement

Statement

DbDoc

Represents a generic document in JSON format

Statement

Provides base functionality for statement objects

FilterableStatement

A statement to be used with filterable statements

SqlStatement

A statement for SQL execution

FindStatement

A statement document selection on a collection

AddStatement

A statement for document addition on a collection

RemoveStatement

A statement for document removal from a collection

ModifyStatement

A statement for document update operations on a collection

SelectStatement

A statement for record retrieval operations on a table

InsertStatement

A statement for insert operations on table

DeleteStatement

A statement that drops a table

UpdateStatement

A statement for record update operations on a table

CreateCollectionIndexStatement

A statement that creates an index on a collection

ReadStatement

Provide base functionality for read operations

WriteStatement

Provide common write operation attributes

Errors

DataError

Exception for errors reporting problems with processed data

DatabaseError

Exception for errors related to the database

Error

Exception that is base class for all other error exceptions

IntegrityError

Exception for errors regarding relational integrity

InterfaceError

Exception for errors related with the interface

InternalError

Exception for internal database errors

NotSupportedError

Exception for errors when an unsupported database feature was used

OperationalError

Exception for errors related to a database operation

PoolError

Exception for errors relating to connection pooling

ProgrammingError

Exception for errors programming errors

Let’s begin our tour of the X DevAPI with the Session class.

Session Class

The Session class is the major class we will use to begin working with a document store. Once we have a connection, the next step is to get the session object. From there, we can begin working with the document store. The following is a tour of the classes and methods grouped by area and application. We start with the schema methods.

Schema Methods

The X DevAPI uses the term schema to refer to a set of collections; the collections are a collection of documents. However, when working with relational data, we use “database” to refer to a collection of tables and similar objects. One may be tempted to conclude “schema” is synonymous with “database” and for older versions of MySQL that is true. However, when working with the document store and the X DevAPI, you should use “schema” and when you refer to relational data, you should use “database.”

Schema or Database: Does It Matter?

Since MySQL 5.0.2, the two terms have been synonyms via the CREATE DATABASE and CREATE SCHEMA SQL commands. However, other database systems make a distinction. That is, some state a schema is a collection of tables and a database is a collection of schemas. Others state a schema is what defines the structure of data. If you use other database systems, be sure to check the definitions so that you use the terms correctly.

When starting work with a document store, the first item you will need to do is either select (get) an existing schema, delete an existing schema, or create a new one. You also may want to list the schemas on the server. The Session class provides several methods for performing these operations. Table 5-4 lists the methods, parameters, and return values for the methods concerning schemas.
Table 5-4

Session Class—Schema Methods

Method

Returns

Description

create_schema(str name)

Schema

Creates a schema on the database and returns the corresponding object

get_schema(str name)

Schema

Retrieves a schema object from the current session through its name

get_default_schema()

Schema

Retrieves the schema configured as default for the session

drop_schema(str name)

None

Drops the schema with the specified name

Listing 5-1 shows an example of how you can work with session objects to create a schema object. Once again, we will expand on this example as we examine more classes and methods. In this case, we use the session object to work with a schema.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Get an unknown schema
schema1 = mysqlx_session.get_schema("not_there!")
# Does it exist?
print("Does not_there! exist? {0}".format(schema1.exists_in_database()))
# Create the schema
schema = mysqlx_session.create_schema("test_schema")
# Does it exist?
print("Does test_schema exist? {0}".format(schema.exists_in_database()))
mysqlx_session.close()
Listing 5-1

Working with Schemas

Note the code to retrieve a schema that doesn’t exist. I use a method of the schema object to check to see if it exists then print out the result. Assuming the schema not_there! doesn't exist, the code will print “False.” Finally, I create the schema test_schema at the end of the code. We will see the schema class in more detail in a later section as well as a better way to check to see if a schema exists. If you save this code to a file named listing5-1.py and execute it, you will see output like the following.

$ python ./listing5-1.py
Does not_there! exist? False
Does test_schema exist? True

Let us now look at the transactional methods for performing ACID compliant transactions.

Transaction Methods

Transactions provide a mechanism that permits a set of operations to execute as a single atomic operation. For example, if a database were built for a banking institution, the macro operations of transferring money from one account to another would preferably be executed completely (money removed from one account and placed in another) without interruption.

Transactions permit these operations to be encased in an atomic operation that will back out any changes should an error occur before all operations are complete, thus avoiding data being removed from one table and never making it to the next table. A sample set of operations in the form of SQL statements encased in transactional commands is the following:

START TRANSACTION;
UPDATE SavingsAccount SET Balance = Balance – 100
WHERE AccountNum = 123;
UPDATE CheckingAccount SET Balance = Balance + 100
WHERE AccountNum = 345;
COMMIT;

MySQL’s InnoDB storage engine (the default storage engine) supports ACID transactions that ensure data integrity with the ability to only commit (save) the resulting changes if all operations succeed or rollback (undo) the changes if any one of the operations fail.

What is Acid?

ACID stands for atomicity, consistency, isolation, and durability. Perhaps one of the most important concepts in database theory, it defines the behavior that database systems must exhibit to be considered reliable for transaction processing.

Atomicity means that the database must allow modifications of data on an “all or nothing” basis for transactions that contain multiple commands. That is, each transaction is atomic. If a command fails, the entire transaction fails, and all changes up to that point in the transaction are discarded. This is especially important for systems that operate in highly transactional environments, such as the financial market. Consider for a moment the ramifications of a money transfer. Typically, multiple steps are involved in debiting one account and crediting another. If the transaction fails after the debit step and doesn’t credit the money back to the first account, the owner of that account will be very angry. In this case, the entire transaction from debit to credit must succeed, or none of it does.

Consistency means that only valid data will be stored in the database. That is, if a command in a transaction violates one of the consistency rules, the entire transaction is discarded, and the data is returned to the state they were in before the transaction began. On the other hand, if a transaction completes successfully, it will alter the data in a manner that obeys the database consistency rules.

Isolation means that multiple transactions executing at the same time will not interfere with one another. This is where the true challenge of concurrency is most evident. Database systems must handle situations in which transactions cannot violate the data (alter, delete, etc.) being used in another transaction. There are many ways to handle this. Most systems use a mechanism called locking that keeps the data from being used by another transaction until the first one is done. Although the isolation property does not dictate which transaction is executed first, it does ensure they will not interfere with one another.

Durability means that no transaction will result in lost data nor will any data created or altered during the transaction be lost. Durability is usually provided by robust backup-and-restore maintenance functions. Some database systems use logging to ensure that any uncommitted data can be recovered on restart.

The Session classes implement methods for transaction processing that mirror the SQL commands shown previously. Table 5-5 lists the transaction methods.
Table 5-5

Transaction Methods

Method

Returns

Description

start_transaction()

None

Starts a transaction context on the server

commit()

None

Commits all the operations executed after a call to startTransaction()

rollback()

None

Discards all the operations executed after a call to startTransaction()

set_savepoint(str name="")

str

Creates or replaces a transaction savepoint with the given name

release_savepoint(str name)

None

Removes a savepoint defined on a transaction

rollback_to(str name)

None

Rolls back the transaction to the named savepoint without terminating the transaction

Note that the last three methods allow you to create a named transaction savepoint, which is an advanced form of transaction processing. See the online MySQL reference manual for more information about savepoints and transactions.

We will see an example of transactions later in this chapter. Now, let’s look at the methods that concern the connection to the server.

Connection Methods

There are two methods for the underlining connection. One to check to see if the connection is open and another to close the connection. Table 5-6 shows the remaining utility methods available in the Session class.
Table 5-6

Connection Methods

Method

Returns

Description

close()

None

Closes the session

is_open()

Bool

Returns true if session is known to be open

The following shows how to use these methods if you want to check the connection as an extra step in your application.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
else:
    print("Connection succeeded.")
# Close the connection
mysqlx_session.close()
Listing 5-2

Working with Sessions

If you save this code to a file named listing5-2.py and execute it, you will see output like the following.

$ python ./listing5-2.py
Connection succeeded.

Miscellaneous Methods

There are also several utility methods in the Session class. Table 5-7 lists the additional functions. See the online X DevAPI reference for more information about these methods.
Table 5-7

Miscellaneous Methods

Method

Returns

Description

Is_open()

Bool

True if the connection is open and active

sql(str sql)

SqlStatement

Creates a SqlStatement object to allow running the received SQL statement on the target MySQL server

CRUD Operations

The X DevAPI implements a create, read, update, and delete (CRUD) model for working with the objects that are contained in a schema. A schema can contain any number of collections, documents, tables, views, and other relational data objects (i.e., triggers). In this section, we see an overview of the schema, collection, tables (relational data), and data sets. The CRUD model is implemented for all objects in the schema that can contain data for both document store and relational data.

Most of the examples in the book up to this point have used relational data for demonstration because most readers are familiar with working with SQL. This chapter continues the discussion from Chapter 3 to complete the introduction to working with the X DevAPI to build document store applications.

Document store data CRUD operations use the verbs add, find, modify, and remove whereas relational data uses terms that match the equivalent SQL command. Table 5-8 provides a quick look at how the methods are named as well as a brief description of each. Furthermore, we use the Collection class for document store data and the Table class for relational data.
Table 5-8

CRUD Operations for Document Store and Relational Data

CRUD Operation

Description

Document Store

Relational Data

Create

Add a new item/object

collection.add()

table.insert()

Read

Retrieve/search for data

collection.find()

table.select()

Update

Modify data

collection.modify()

table.update()

Delete

Remove item/object

collection.remove()

table.delete()

We will see the methods specific to each class (Schema, Collection, Table, and View) in the following sections. Let’s begin with a look at the details of the Schema class.

Schema Class

The schema is a container for the objects that store your data. Recall that this can be a collection for document store data or a table or view for relational data. Much like the old days working with relational data, you must select (or use) a schema for storing data in either a collection, table, or view.

Although you can mix the use of document store data (collections) and relational data (tables, views), to keep things easy to remember, we will examine the Schema class methods as they pertain to each in turn starting with the document store methods.

The document store methods of the Schema class include methods for creating collections, using, and finding collections. Table 5-9 shows the document store methods for working with collections and tables. Note the create and get methods return an instance of an object. For example, the get_collection() method returns a Collection object. This is another example of how you can use the X DevAPI to combine several operations into a single statement.
Table 5-9

Schema Class—Document Store and Table Methods

Method

Returns

Description

get_tables()

List

Returns a list of tables for this schema

get_collections()

List

Returns a list of collections for this schema

get_table(str name)

Table

Returns the table of the given name for this schema

get_collection(str name)

Collection

Returns the collection of the given name for this schema

get_collection_as_table(str name)

Table

Returns a Table object representing a collection on the database

create_collection(str name)

Collection

Creates in the current schema a new collection with the specified name and retrieves an object representing the new collection created

Now, let’s continue our example and show some of the Schema methods for working with collections in action. Listing 5-3 shows how to create a schema and create several collections then list the collections in the schema. Note that I use the name property of the collection object.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(-1)
# Get the schema
schema = mysqlx_session.create_schema("test_schema")
# Create a new collection
testCol = schema.create_collection('test_collection1', True)
# Create a new collection
testCol = schema.create_collection('test_collection2', True)
# Show the collections.
collections = schema.get_collections()
for col in collections:
    print(col.name)
mysqlx_session.close()
Listing 5-3

Collection Methods

If you save this code to a file named listing5-3.py and execute it, you will see output like the following.

$ python ./listing5-3.py
test_collection1
test_collection2

Note that in the table there is a method to retrieve a document as a relational table. This method, get_collection_as_table() allows developers who want to store standard SQL columns with documents can convert (cast) a collection to a table. That is, the collection can be fetched as a table object, which then behaves as a normal relational table. Accessing data in the table object using CRUD operations use the following syntax.

doc->'$.field_name'

This syntax is supported by most connectors.1 You can form complex document paths (like those we saw in Chapter 3) as well.

doc->'$.something_else.field_name.like[1].other_thing'

The reason we need this syntax is because a collection returned as a table results in a table with only two fields: doc and _id, where doc is where the document is store and _id is the document id. Listing 5-4 shows how to use this syntax.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Get the schema
schema = mysqlx_session.create_schema("test_schema")
# Create a new collection
pets = schema.create_collection("pets_json")
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Fetch collection as Table
pets_tbl = schema.get_collection_as_table('pets_json')
# Now do a find operation to retrieve the inserted document
result = pets_tbl.select(["doc->'$.name'", "doc->'$.age'"]).execute()
record = result.fetch_one()
# Print the first row
print("Name : {0}, Age: {1}".format(record[0], record[1]))
# Drop the collection
schema.drop_collection("pets_json")
# Close the session
mysqlx_session.close()
Listing 5-4

Collection as Table Example

If you save this code to a file named listing5-4.py and execute it, you will see output like the following.

$ python ./listing5-4.py
Name : "Violet", Age: 6

Collection Class

The Collection class is used to store documents (data). You can consider it the same organizational concept as a table in relational data. The Collection class therefore implements the CRUD operations for documents as well as a few utility methods such as those for creating an index or counting the documents in the collection. Table 5-10 shows the methods for the collection class.
Table 5-10

Collection Class

Method

Returns

Description

add(*values)

AddStatement

Inserts one or more documents into a collection

find(str search_condition)

FindStatement

Retrieves documents from a collection, matching a specified criterion

remove(str search_condition)

RemoveStatement

Creates a document deletion handler

modify(str search_condition)

ModifyStatement

Modifies documents matching a specified criterion

drop_index(str name)

None

Drops an index from a collection

replace_one(str id, document doc)

Result

Replaces an existing document with a new document

add_or_replace_one(str id, document doc)

Result

Replaces or adds a document in a collection

remove_one(str id)

Result

Removes document with the given _id value

get_one(str id)

Document

Fetches the document with the given _id from the collection

Note one thing about this table that each of the CRUD operations returns an object instance for the operation. For example, the find() method returns a FindStatement object. As you may surmise, this means that the resulting object instance has methods we can use to do more with the statement. We will see those classes and methods next. For now, let’s see an example using the base CRUD operations.

Now that we have enough knowledge about the X DevAPI, we can start reviewing examples that are more complete. That is, examples that do something with data. Listing 5-5 shows a complete Python script that demonstrates how to work with a collection. I include the session code and connection error handling as we’ve seen previously. The example is a simple document store for recoding information about pets.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json", True)
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Do a find on the collection - find the fish
mydoc = pets.find("type = 'fish'").execute()
print(mydoc.fetch_one())
# Drop the collection
mysqlx_session.drop_schema("animals")
# Close the connection
mysqlx_session.close()
Listing 5-5

CRUD Example Using a Collection

The script creates a new schema then creates a new collection named animals and within the schema a collection named pets_json. The script then adds several documents (pets) to the collection. To demonstrate the find operation, the script calls the find() method on the pets collection looking for all of the fish. That is, a document that has a type equal to ‘fish’. We will see more about expressions you can use in the find() method in a later section.

If you save this code to a file named listing5-5.py and execute it, you will see output like the following. We found the fish!

$ python ./listing5-5.py
{"breed": "koi", "age": 7, "_id": "7c3c0201f5e24bd99f586e772aad0369", "type": "fish", "name": "Spot"}

Rather than issue a separate add() method for each document, you can add more than one document at the same time by combining the data in a list (array). This is like using a bulk insert option for relational data. The following code is equivalent to the five add() method calls above.

# Insert some documents
pets.add([{'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'},
  {'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'},
  {'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'},
  {'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'},
  {'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}]).execute()

Note the syntax used inside the add() method. This is a special notation that all document store class methods use for specifying JSON documents and listing expressions. In this example, the syntax is optional syntax and is normally used to specify multiple documents. That is, you enclose the documents inside [] comma-separated as in the following. In this case, I am adding two documents with one method call. Thus, for one document, the [] are optional.

pets.add([
    {'name': 'whizzy', 'age': 2, 'breed':'carp', 'type':'fish'},
    {'name': 'blobby', 'age': 3, 'breed': 'carp', 'type': 'fish'},
]).execute()

Although this streamlines the code a bit, there may be a reason you would want to add a document at a time. For example, if you need to use the resulting object returned from the add() method to get more information or check warnings, you may want to add one document at a time.

Recall from Table 5-10 that the CRUD methods each return an object instance for a class. These classes have several methods that you can use to work with the statements appropriate for the operation. Table 5-11 shows the classes and their methods.
Table 5-11

Classes for CRUD Operations for Document Store Data

Class

Method

Returns

Description

AddStatement

A statement for document addition on a collection

add(*values)

AddStatement

Adds a list of documents into a collection

execute()

Result

Executes the statement

get_values()

list

Returns the list of values

is_doc_based()

bool

Checks if it is document based

is_upsert()

bool

Returns true if it’s an upsert

schema

Schema

The Schema object

target

object

The database object target

upsert(val=True)

 

Sets the upset flag to the boolean of the value provided

FindStatement

Find documents in a collection

bind(*args)

FilterableStatement

Binds a value to a specific placeholder

execute()

Result

Executes the statement

fields(*fields)

FindStatement

Sets a document field filter

get_binding_map()

dict

Returns the binding map dictionary

get_bindings()

list

Returns the bindings list

get_grouping()

list

Returns the grouping expression list

get_having()

object

Returns the having expression

get_limit_offset()

int

Returns the limit offset

get_limit_row_count()

int

Returns the limit row count

get_projection_expr()

object

Returns the projection expression

get_sort_expr()

object

Returns the sort expression

get_where_expr()

object

Returns the where expression

group_by(*fields)

ReadStatement

Sets a grouping criterion for the resultset

having(condition)

ReadStatement

Sets a condition for records to be considered in aggregate function operations

 

is_doc_based()

bool

Checks if it is document based

is_lock_exclusive()

bool

Returns true if is EXCLUSIVE LOCK

is_lock_shared()

bool

Returns true if is SHARED LOCK

limit(row_count, offset=0)

FilterableStatement

Sets the maximum number of records or documents to be returned

lock_exclusive()

ReadStatement

Executes a read operation with EXCLUSIVE LOCK; only one lock can be active at a time

lock_shared()

ReadStatement

Executes a read operation with SHARED LOCK; only one lock can be active at a time

schema

Schema

The Schema object

sort(*sort_clauses)

FilterableStatement

Sets the sorting criteria

target

object

The database object target

where(condition)

FilterableStatement

Sets the search condition to filter

ModifyStatement

Modify documents in a collection

array_append(doc_path, value)

ModifyStatement

Inserts a value into a specific position in an array attribute in documents of a collection

array_insert(field, value)

ModifyStatement

Inserts a value into the specified array in documents of a collection

bind(*args)

FilterableStatement

Binds a value to a specific placeholder

change(doc_path, value)

ModifyStatement

Adds an update to the statement setting the field, if it exists at the document path, to the given value

execute()

Result

Executes the statement.

get_binding_map()

dict

Returns the binding map dictionary

get_bindings()

list

Returns the bindings list

get_grouping()

list

Returns the grouping expression list

get_having()

object

Returns the having expression

get_limit_offset()

int

Returns the limit offset

get_limit_row_count()

int

Returns the limit row count

get_projection_expr()

object

Returns the projection expression

get_sort_expr()

object

Returns the sort expression

 

get_update_ops()

list

Returns the list of update operations

get_where_expr()

object

Returns the where expression

is_doc_based()

bool

Checks if it is document based

limit(row_count, offset=0)

FilterableStatement

Sets the maximum number of records or documents to be returned

patch(doc)

ModifyStatement

Inserts a value into a specific position in an array attribute in documents of a collection

schema

Schema

The Schema object

set(doc_path, value)

ModifyStatement

Sets or updates attributes on documents in a collection.

sort(*sort_clauses)

FilterableStatement

Sets the sorting criteria.

target

object

The database object target

unset(*doc_paths)

ModifyStatement

Removes attributes from documents in a collection

where(condition)

FilterableStatement

Sets the search condition to filter

RemoveStatement

Remove documents from a collection

bind(*args)

FilterableStatement

Binds a value to a specific placeholder

execute()

Result

Executes the statement

get_binding_map()

dict

Returns the binding map dictionary

get_bindings()

list

Returns the bindings list

get_grouping()

list

Returns the grouping expression list

get_having()

object

Returns the having expression

get_limit_offset()

int

Returns the limit offset

get_limit_row_count()

int

Returns the limit row count

get_projection_expr()

object

Returns the projection expression

get_sort_expr()

object

Returns the sort expression

get_where_expr()

object

Returns the where expression

is_doc_based()

bool

Checks if it is document based

limit(row_count, offset=0)

FilterableStatement

Sets the maximum number of records or documents to be returned

schema

Schema

The Schema object

sort(*sort_clauses)

FilterableStatement

Sets the sorting criteria

target

object

The database object target

where(condition)

FilterableStatement

Sets the search condition to filter

Note that we now see there is more you can do than simply call the add(), find(), modify(), and remove() methods. Because each of these returns an object instance of another class, we can either use a variable to store the object instance and then if you need to specify additional information for the operation, we can call the appropriate method of the new object.

In fact, many of the objects returned have the capability to chain other methods to help filter or modify the search. Table 5-12 lists some of the common methods available for searching documents. Optional methods are shown in []. Also shown are those methods where they can be used.
Table 5-12

Common Methods for Searching Documents

Method

Description

Used By

[.fields(...)]

This function sets the fields to be retrieved from each document matching the criteria on this find operation.

find(),

[.group_by(...)[.having(searchCondition)]]

Sets a grouping criteria for the result set. The having clause sets a condition for records to be considered in aggregate function operations.

find(),

[.sort(...)]

If used, the operation will return the records sorted with the defined criteria.

find(), remove(), modify()

[.limit(numberOfRows)

If used, the operation will return at most numberOfRows documents.

find(), remove(), modify()

[.bind(placeHolder, value)[.bind(...)]]

Binds a value to a specific placeholder used on this object

find(), remove(), modify()

execute()

Executes the operation with all the configured options

add(), find(), remove(), modify()

[.set(...)]

Adds an operation into the modify handler to set an attribute on the documents that were included on the selection filter and limit

modify()

[.unset(String attribute)]

Removes attributes from documents in a collection

modify()

[.patch(...)]

Performs modifications on a document based on a patch JSON object

modify()

[.array_insert(...)]

Adds an operation into the modify handler to insert a value into an array attribute on the documents that were included on the selection filter and limit

modify()

[.array_append(...)]

Adds an operation into the modify handler to append a value into an array attribute on the documents that were included on the selection filter and limit

modify()

For example, suppose we want to limit the fields for the find() call in the example code used in Listing 5-5. That is, we only want the name and breed of the pet that meets the criteria. We can use the fields() method of the FindStatement class to project the correct fields. Listing 5-6 shows the code to do this.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json", True)
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Do a find on the collection - find the fish
find = pets.find("type = 'fish'")
filterable = find.fields(['name','type'])
mydoc = filterable.execute()
print(mydoc.fetch_one())
Listing 5-6

Demonstration of the FindStatement Class

Note the find() method, here we see once again the use of [] to specify a list. In this case, it is a list of fields for the operations. This is a common syntax that you will see in many of the CRUD methods.

If you save this code to a file named listing5-6.py and execute it, you will see output like the following.

$ python ./listing5-6.py
{"type": "fish", "name": "Spot"}

Note also that we have set a variable to receive the object instance from each method. However, we can chain these methods into a single line of code as follows. Just replace the three lines in Listing 5-6 with the one chained method call.

# Do a find on the collection - find the fish
mydoc = pets.find("type = 'fish'").fields(['name','type']).execute()
print(mydoc.fetch_one())

Although these new classes may seem a lot of extra work, as you become more accustomed to using them, they will become more intuitive. Indeed, if you are used to working with relational data, some of the methods may seem familiar in concept.

Note also that some of the methods allow you to pass in conditions, which are expressions that you can build to form criteria for the operation. We will discuss expressions in a later section. Now, let’s look at the Table class.

Table Class

The table is the major organizational mechanism for relational data. In the X DevAPI, a table is the same relational data construct with which we are all familiar. The X DevAPI has a Table (you can use them with views too) class complete with CRUD operations (select, insert, update, and delete) as well as additional methods for counting the rows or whether the base object is a view. Table 5-13 shows the methods for the Table class.
Table 5-13

Table Class

Method

Returns

Description

am_i_real()

bool

Verifies if this object exists in the database

count()

int

Counts the rows in the table.

delete(condition=None)

DeleteStatement

Creates a new mysqlx.DeleteStatement object

exists_in_database()

bool

Verifies if this object exists in the database

get_connection()

Connection

Returns the underlying connection

get_name()

String

Returns the name of this database object

get_schema()

Schema

Returns the schema object of this database object

insert(*fields)

InsertStatement

Creates a new mysqlx.InsertStatement object

is_view()

bool

Determines if the underlying object is a view or not

name

str

The name of this database object

schema

Schema

The Schema object

select(*fields)

SelectStatement

Creates a new mysqlx.SelectStatement object

update()

UpdateStatement

Creates a new mysqlx.UpdateStatement object

who_am_i()

String

Returns the name of this database object

Note that there aren’t methods for creating the table. We must use the CREATE TABLE SQL command to do this or the sql() method to execute the SQL statement. In fact, there are no methods to create any relational data objects. You must use SQL to issue the appropriate create statement to create the objects. For example, to create a table for our pets data in the previous example, we can use the following CREATE TABLE statement.

CREATE TABLE `animals`.`pets_sql` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `breed` char(20) DEFAULT NULL,
  `type` char(12) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Tip

There are no create methods to create table or views. You must pass the SQL command to the sql() method to create these (and other relational data) objects.

Let’s take the script from the previous document store example and rewrite it to use relational data. In this case, I create a new table named pets_sql in the schema named animals and insert a few rows then select one of them. Listing 5-7 shows the code for this example.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new table
mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
            "`id` int auto_increment primary key, "
            "`name` char(20), "
            "`age` int, "
            "`breed` char(20), "
            "`type` char(12))").execute()
pets = schema.get_table("pets_sql", True)
# Insert some documents
pets.insert().values([None, 'Violet', 6, 'dachshund', 'dog']).execute()
pets.insert().values([None, 'JonJon', 15,'poodle', 'dog']).execute()
pets.insert().values([None, 'Mister', 4,'siberian khatru', 'cat']).execute()
pets.insert().values([None, 'Spot', 7,'koi', 'fish']).execute()
pets.insert().values([None, 'Charlie', 6,'dachshund', 'dog']).execute()
# Do a select (find) on the table - find el gato
mydoc = pets.select().where("type = 'cat'").execute()
print(", ".join("{0}".format(c.get_column_name()) for c in mydoc.columns))
print(", ".join("{0}".format(r) for r in mydoc.fetch_one()))
# Drop the collection
mysqlx_session.drop_schema("animals")
# Close the connection
mysqlx_session.close()
Listing 5-7

CRUD Example Using a Table

If you save this code to a file named listing5-7.py and execute it, you will see output like the following.

$ python ./listing5-7.py
id, name, age, breed, type
3, Mister, 4, siberian khatru, cat

Although I put the CREATE TABLE statement in the example code, it is not normal practice to do so. In fact, most developers will create the table separately from the application. That is, they would execute the CREATE SQL statements manually (or possibly through a DevOps2 tool) and not include them in the application. However, there are some arguments for using temporary tables in which case you would likely include those in the application, but in general, permanent database objects are created separately from the application. The next example shows how to get the table from an existing schema.

Note that there are some interesting new method calls. First, unlike the add() method for collections, the insert() method uses additional chained methods. In this case, we needed the values() method to add the values. This is because the insert() method returns an instance of the InsertStatement class.

This may seem strange until you consider the syntax for the SQL INSERT statement. In particular, the equivalent statements in SQL for these operations are as follows. As you can see, we have a VALUES clause.

INSERT INTO animals.pets VALUES (Null, 'Violet', 6, 'dachshund', 'dog');
INSERT INTO animals.pets VALUES (Null, 'JonJon', 15,'poodle', 'dog');
INSERT INTO animals.pets VALUES (Null, 'Mister', 4,'siberian khatru', 'cat');
INSERT INTO animals.pets VALUES (Null, 'Spot', 7,'koi', 'fish');
INSERT INTO animals.pets VALUES (Null, 'Charlie', 6,'dachshund', 'dog');
The same is true for the select() method, which returns a SelectStatement object where we chained the where() clause. As you may have surmised, the same thing happens for the update() and delete() methods. This is natural for those used to using the SQL statements. Table 5-14 lists the methods for each of the classes related to the CRUD operations for relational data.
Table 5-14

Classes for CRUD Operations for Relational Data

Class

Method

Returns

Description

SelectStatement

A statement for record retrieval operations on a table.

bind(*args)

FilterableStatement

Binds a value to a specific placeholder

execute()

Result

Executes the statement

get_binding_map()

dict

Returns the binding map dictionary

get_bindings()

list

Returns the bindings list

get_grouping()

list

Returns the grouping expression list

get_having()

object

Returns the having expression

get_limit_offset()

int

Returns the limit offset

get_limit_row_count()

int

Returns the limit row count

get_projection_expr()

object

Returns the projection expression

get_sort_expr()

object

Returns the sort expression

get_sql()

String

Returns the generated SQL

get_where_expr()

object

Returns the where expression

group_by(*fields)

ReadStatement

Sets a grouping criterion for the resultset

having(condition)

ReadStatement

Sets a condition for records to be considered in aggregate function operations

 

is_doc_based()

bool

Checks if it is document based

is_lock_exclusive()

bool

Returns true if is EXCLUSIVE LOCK

is_lock_shared()

bool

Returns true if is SHARED LOCK

limit(row_count, offset=0)

FilterableStatement

Sets the maximum number of records or documents to be returned

lock_exclusive()

ReadStatement

Executes a read operation with EXCLUSIVE LOCK; only one lock can be active at a time

lock_shared()

ReadStatement

Executes a read operation with SHARED LOCK; only one lock can be active at a time

order_by(*clauses)

SelectStatement

Sets the order by criteria.

schema

Schema

The Schema object

sort(*sort_clauses)

FilterableStatement

Sets the sorting criteria

target

object

The database object target

where(condition)

FilterableStatement

Sets the search condition to filter

InsertStatement

A statement for insert operations on table

execute()

Result

Executes the statement

get_values()

list

Returns the list of values

is_doc_based()

bool

Checks if it is document based

is_upsert()

bool

Returns true if it’s an upsert

schema

Schema

The Schema object

target

object

The database object target

upsert(val=True)

 

Sets the upsert flag to the boolean of the value provided; setting of this flag allows updating of the matched rows/documents with the provided value

values(*values)

InsertStatement

Sets the values to be inserted

UpdateStatement

A statement for record update operations on a table

bind(*args)

FilterableStatement

Binds a value to a specific placeholder

execute()

Result

Executes the statement

get_binding_map()

dict

Returns the binding map dictionary

get_bindings()

list

Returns the bindings list

get_grouping()

list

Returns the grouping expression list

get_having()

object

Returns the having expression

get_limit_offset()

int

Returns the limit offset

get_limit_row_count()

int

Returns the limit row count

get_projection_expr()

object

Returns the projection expression

get_sort_expr()

object

Returns the sort expression

get_update_ops()

list

Returns the list of update operations

get_where_expr()

object

Returns the where expression

is_doc_based()

bool

Checks if it is document based

limit(row_count, offset=0)

FilterableStatement

Sets the maximum number of records or documents to be returned

 

schema

Schema

The Schema object

set(field, value)

UpdateStatement

Updates the column value on records in a table

sort(*sort_clauses)

FilterableStatement

Sets the sorting criteria

target

object

The database object target

where(condition)

FilterableStatement

Sets the search condition to filter

DeleteStatement

A statement that drops a table

bind(*args)

FilterableStatement

Binds a value to a specific placeholder

execute()

Result

Executes the statement

get_binding_map()

dict

Returns the binding map dictionary

get_bindings()

list

Returns the bindings list

get_grouping()

list

Returns the grouping expression list

get_having()

object

Returns the having expression

get_limit_offset()

int

Returns the limit offset

get_limit_row_count()

int

Returns the limit row count

 

get_projection_expr()

object

Returns the projection expression

get_sort_expr()

object

Returns the sort expression

get_where_expr()

object

Returns the where expression

is_doc_based()

bool

Checks if it is document based

limit(row_count, offset=0)

FilterableStatement

Sets the maximum number of records or documents to be returned

schema

Schema

The Schema object

sort(*sort_clauses)

FilterableStatement

Sets the sorting criteria

target

object

The database object target

where(condition)

FilterableStatement

Sets the search condition to filter

Before we proceed, let us review the sample data needed to execute the examples in the rest of this chapter.

What About Classicsession?

If you’ve read the documentation for the MySQL Shell, you may have encountered a global object named mysqlx, which mirrors the mysqlx module. You also may have encountered a session object named ClassicSession that exists in the mysql global object. This object is only available via the MySQL Shell and is not to be confused with the module named mysql in the Connector/Python code—they are not the same. In fact, the X DevAPI does not have any objects named ClassicSession.

Because this book focuses on the MySQL Document Store and the X DevAPI we present a brief list of the methods in the ClassicSession class. The following lists the commonly used methods.
  • close(): Closes the internal connection to the MySQL server held on this session object.

  • start_transaction(): Starts a transaction context on the server.

  • commit() : Commits all the operations executed after a call to startTransaction().

  • rollback(): Discards all the operations executed after a call to startTransaction().

  • get_uri(): Retrieves the URI string.

  • run_sql(str query, list args=[]): Executes a query and returns the corresponding ClassicResult object.

  • query(str query, list args=[]): Executes a query and returns the corresponding ClassicResult object.

  • is_open(): Returns True if the session is open.

Once again, these methods are for the ClassicSession class, which is only available through the MySQL Shell. This brief sidebar was included for completeness and to clarify the origins of the class.

Example Data Used in this Chapter

The example code for the rest of this chapter uses data we created in the previous examples. I include it here for your convenience. More specific, I include the SQL statements for creating the relational data and a short script for creating the document store data. Listing 5-8 is the code needed for creating the sample document store.

# Create a schema.
# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json", True)
# Insert some documents
pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
# Close the connection
mysqlx_session.close()
Listing 5-8

Sample Document Store

You may note that this resembles many of the previous listings. However, because from this point on we will be using the animals schema, we have omitted the drop_schema( ) call at the end.

Listing 5-9 includes the SQL statements for creating the sample relational data.

CREATE TABLE `animals`.`pets_sql` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `breed` char(20) DEFAULT NULL,
  `type` char(12) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO animals.pets_sql VALUES (Null, 'Violet', 6, 'dachshund', 'dog');
INSERT INTO animals.pets_sql VALUES (Null, 'JonJon', 15,'poodle', 'dog');
INSERT INTO animals.pets_sql VALUES (Null, 'Mister', 4,'siberian khatru', 'cat');
INSERT INTO animals.pets_sql VALUES (Null, 'Spot', 7,'koi', 'fish');
INSERT INTO animals.pets_sql VALUES (Null, 'Charlie', 6,'dachshund', 'dog');
CREATE VIEW `animals`.`num_pets` AS
SELECT type as Type, COUNT(*) as Num
FROM animals.pets_sql
GROUP BY type;
Listing 5-9

Sample Relational Data

Although the preceding examples create these objects, you may want to refer to this section when experimenting with the examples and when running the examples later in the chapter.

Now let’s see the classes for working with results and data sets from the find(), select(), and other methods that return results.

Working with Data Sets

Until now we have seen a few simple examples of working with results and while it may appear all results are the same class, there are several result classes. The object instance for the Result class returned depends on the operation. Table 5-15 shows the type of object instance returned by the origin operation as well as the type of data returned.
Table 5-15

Result Classes (Object Instances) Returned

Object Instance

Origin

Description

Content Returned

Result

Create, update, delete

Returned by add().execute(), modify().execute(), remove().execute()

affected_item_count, auto_increment_value, last_document_id

SqlResult

Session

Returned by session.sql()

auto_increment_value, affected_row_count, fetched data – data set

RowResult

Relational Data select

Returned by select().execute()

fetched data—data set

Note that the content column shows either a result or data set as the content returned. The X DevAPI uses the term data set to refer to the data returned from the read CRUD operation (find(), select(), and sql() methods) and result3 to refer to the data returned from a create, update and delete CRUD operation.

Also, note that there are different objects returned for each of the class of operations. The classes RowResult and SqlResult inherit from a base class (BaseResult) and thus have a lot of the same methods. What sets these apart from the Result class returned from the create, update, and delete operations is the Result class does not support an iterator. This is because a Result object contains the data returned from the server pertaining to the create, update, and delete operations, which do not return any data but may return warnings and similar metadata and is equivalent to the results returned from traditional SQL INSERT, UPDATE, and DELETE statements in MySQL.

Table 5-16 shows all the classes and their methods that you will encounter when working with data sets and results.
Table 5-16

Classes and Methods for Working with Data Sets and Results

Class

Method

Returns

Description

RowResult

Allows traversing the Row objects returned by a Table.select operation

columns

list

The list of columns

count

int

The total of items

fetch_all()

list

Fetches all items

fetch_one()

mysqlx.Row or mysqlx.DbDoc

Fetches one item

get_warnings()

list

Returns the warnings

get_warnings_count()

int

Returns the number of warnings

index_of(col_name)

int

Returns the index of the column

set_closed(flag)

 

Sets if resultset fetch is done

set_generated_id(generated_id)

 

Sets the generated ID

set_has_more_results(flag)

 

Sets if has more resultsets

set_rows_affected(total)

 

Sets the number of rows affected

SqlResult

Represents a result from a SQL statement

columns

list

The list of columns

count

int

The total of items

fetch_all()

list

Fetches all items

fetch_one()

mysqlx.Row or mysqlx.DbDoc

Fetches one item

get_autoincrement_value()

string

Returns the identifier for the last record inserted

get_warnings()

list

Returns the warnings

get_warnings_count()

int

Returns the number of warnings

index_of(col_name)

int

Returns the index of the column

next_result()

bool

Processes the next result

set_closed(flag)

 

Sets if resultset fetch is done

set_generated_id(generated_id)

 

Sets the generated ID

set_has_more_results(flag)

 

Sets if has more resultsets

BufferingResult

Provides base functionality for buffering result objects

count

int

The total of items

fetch_all()

list

Fetches all items

fetch_one()

mysqlx.Row or mysqlx.DbDoc

Fetches one item

get_warnings()

list

Returns the warnings

get_warnings_count()

int

Returns the number of warnings

index_of(col_name)

int

Returns the index of the column

set_closed(flag)

 

Sets if resultset fetch is done

set_generated_id(generated_id)

 

Sets the generated ID

set_has_more_results(flag)

 

Sets if has more resultsets

set_rows_affected(total)

 

Sets the number of rows affected

Result

Allows retrieving information about nonquery operations performed on the database

append_warning(level, code, msg)

 

Appends a warning

get_affected_items_count()

int

Returns the number of affected items for the last operation

get_autoincrement_value()

int

Returns the last insert id auto generated

get_document_id()

String

Returns ID of the last document inserted into a collection

get_document_ids()

list

Returns the list of generated documents IDs

get_warnings()

list

Returns the warnings

The three classes that have iterators implement two methods: fetch_one() and fetch_all(). They work as you would imagine and return either a data set or a set of objects for a set of documents. The fetch_one() method returns the next data item in the data set or NULL if there are no more data items and fetch_all() returns all the data items. More specific, fetch_one() retrieves one data item at a time from the server whereas fetch_all() retrieves all the data from the server in one pass. Which one you would use depends on the size of the data set and how you would want to process the data.

Note

Once you fetch a data item, you cannot fetch it again. That is, the iterators are forward only.

Before we look at how to access data in the data set, let us review document identifiers and auto increment columns.

Tip

From this point on in the examples, you should have the JSON data loaded as described in Listing 5-8 and the relational data as described in Listing 5-9.

Document Identifiers

Recall each document you store in a document store collection has a document identifier (doc id or document id), which is a string of characters that uniquely identifies the document in a collection.4 You do not need to create your own document ids—they are assigned for you automatically.

There are two methods available to retrieve the document id from the Result class (the content returned for create, update, and delete operations). In particular, you can use the get_document_id() method to retrieve the last document id assigned or the get_document_ids() to return a list of the document ids for the bulk add option for the add() method as described above. Listing 5-10 demonstrates retrieving the document ids when adding documents.

Note

The listing from this point on assumes the animals collection does not exist. If you are planning to run the code examples one after another, you should add the drop_schema() call shown in Listing 5-5.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Drop the collection
mysqlx_session.drop_schema("animals")
# Create a schema.
schema = mysqlx_session.create_schema("animals")
# Create a new collection
pets = schema.create_collection("pets_json")
# Insert some documents and get the document ids.
res = pets.add({'name': 'Violet', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'JonJon', 'age': 15, 'breed':'poodle', 'type':'dog'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'Mister', 'age': 4, 'breed':'siberian khatru', 'type':'cat'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'Spot', 'age': 7, 'breed':'koi', 'type':'fish'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))
res = pets.add({'name': 'Charlie', 'age': 6, 'breed':'dachshund', 'type':'dog'}).execute()
print("New document id = '{0}'".format(res.get_document_id()))# Drop the collection
mysqlx_session.drop_schema("animals")
# Close the connection
mysqlx_session.close()
Listing 5-10

Getting Document Ids

If you run the code snippet, you will see the document ids as in the following.

New document id = '9801A79DE0939A8311E805FB3419B12B'
New document id = '9801A79DE093B93111E805FB341CC7B5'
New document id = '9801A79DE093AD4311E805FB341CF6D9'
New document id = '9801A79DE09397AD11E805FB341D1F87'
New document id = '9801A79DE09382E911E805FB341D4568'

Auto Increment

If you are working with relational data and have specified an auto increment field, you can retrieve the last auto increment value using the get_autoincrement_value() method of the SqlResult and Result classes. This method returns the auto increment value generated, which can be helpful if you need to retrieve the last row inserted by the surrogate primary key.

Accessing Data in Data Sets

Let us consider accessing data in data sets. In this case, we issue a find() method on a collection that returns several documents as represented by a specific result object. In this case, we have a set of DbDoc objects to fetch.

There are three ways we can access the data in the data item; we can simply get the data item as a string (naturally), we can access the data elements via a property with the name of the key for the data element, or we can use an array index to find the data element with its key. Listing 5-11 shows a complete script with an example of each mechanism. Note that you should have created the schema and collection populating it with the data using Listing 5-8.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets = mysqlx_session.get_schema("animals").get_collection("pets_json")
# Do a find on the collection - find the dog
find = pets.find("type = 'dog'").execute()
res = find.fetch_one()
while (res):
    print("Get the data item as a string: {0}".format(res))
    print("Get the data elements: {0}, {1}, {2}".format(res.name, res.age, res['breed']))
    res = find.fetch_one()
# Close the connection
mysqlx_session.close()
Listing 5-11

Reading Data from a Data Set

Note how I retrieved the data set with the find().execute() method, which returns an object that I can iterate over. In this case, I fetch the first data item then a while loop to loop through the items. Inside the while loop, I print the string returned from the fetch and demonstrate how to retrieve data elements by property (e.g., res.age, res.name) or by array index using the key name (e.g., res['breed']).

If you save this code to a file named listing5-11.py and execute it, you will see output like the following.

$ python ./listing5-11.py
Get the data item as a string: {"breed": "dachshund", "age": 6, "_id": "9801A79DE093B2B011E805FBCB1FAC51", "type": "dog", "name": "Violet"}
Get the data elements: Violet, 6, dachshund
Get the data item as a string: {"breed": "poodle", "age": 15, "_id": "9801A79DE093B43A11E805FBCB215AFA", "type": "dog", "name": "JonJon"}
Get the data elements: JonJon, 15, poodle
Get the data item as a string: {"breed": "dachshund", "age": 6, "_id": "9801A79DE093BFD511E805FBCB21CF30", "type": "dog", "name": "Charlie"}
Get the data elements: Charlie, 6, dachshund

Now lets’ see how to get rows from a relational data query.

Accessing Metadata in Results

When using relational data and the table or view select() method. This returns an SQL data set that represents the rows you would expect to get from a typical SQL SELECT query. We can then access the data in the row by column name as a property, column index number as the array index, or by column name as the array index. Listing 5-12 demonstrates both methods of getting the data from the row.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets = mysqlx_session.get_schema("animals").get_table("pets_sql")
# Do a select (find) on the table - find the dogs
res = pets.select().where("type = 'dog'").execute()
# Working with column properties
print("Get the data using column names as properties:")
for row in res.fetch_all():
    for col in res.columns:
        print(row.get_string(col.get_column_name())),
    print("")
# Working with column indexes
print("Get the data using column index by integer:")
for row in res.fetch_all():
    for i in range(0,len(res.columns)):
        print(row[i]),
    print("")
# Working with column names
print("Get the data using column index by name:")
for row in res.fetch_all():
    for col in res.columns:
        print(row[col.get_column_name()]),
    print("")
# Close the connection
mysqlx_session.close()
Listing 5-12

Data Set Example—Relational Data

If you save this code to a file named listing5-12.py and execute it, you will see output as the following.

$ python ./listing5-12.py
Get the data using column names as properties:
1 Violet 6 dachshund dog
2 JonJon 15 poodle dog
5 Charlie 6 dachshund dog
Get the data using column index by integer:
1 Violet 6 dachshund dog
2 JonJon 15 poodle dog
5 Charlie 6 dachshund dog
Get the data using column index by name:
1 Violet 6 dachshund dog
2 JonJon 15 poodle dog
5 Charlie 6 dachshund dog

Note how I retrieve the data set with the select().execute() method, which returns an object that I can iterate over. In this case, I fetch the items (rows) using a for loop. Inside the for loop, I use the Row object’s get_string() method, which takes a key name for the column or in this case column name. I use a little trick with iterating over the columns inside a nested for loop. I discuss how to work with the column metadata in the next section.

Column Metadata

The two result classes for relational data (RowResult and SqlResult) support the concept of columns as you would expect from a typical SQL SELECT query. You can get the columns using the columns() method (columns property), which returns a list of Column objects. You can then use the properties in that object to discover more about the columns in the data set. Table 5-17 shows the ColumnMetaData class and its methods.
Table 5-17

ColumnMetaData Class

Method

Returns

Description

get_schema_name()

str

Retrieves the name of the schema where the column is defined

get_table_name()

str

Retrieves table name where the column is defined

get_table_label()

str

Retrieves table alias where the column is defined

get_column_name()

str

Retrieves column name

get_column_label()

str

Retrieves column alias

get_type()

Type

Retrieves column type

get_length()

int

Retrieves column length

get_fractional_digits()

int

Retrieves the fractional digits if applicable

is_number_signed()

bool

Indicates if a numeric column is signed

get_collation_name()

str

Retrieves the collation name

get_character_set_name()

str

Retrieves the character set name

Note that there are several interesting methods including those for discovering the type, character and collation, size, and more. Note also there are methods for getting the name or label of the column. The name is the name from the operation whereas the label is alias or alternative labeling specified in the operation. To see the difference, consider the following SQL statement.

SELECT pet_name as name, age as years_young FROM animals.pets_sql

When you call the get_column_name() and get_column_label() methods, you get the following values. Listing 5-13 demonstrates how to work with these methods.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
res = mysqlx_session.sql("SELECT name as pet_name, age as years_young FROM animals.pets_sql").execute()
cols = res.columns
for col in cols:
    print "name =", col.get_column_name(), "label =", col.get_column_label()
mysqlx_session.close()
Listing 5-13

Working with Column Names and Labels

If you save this code to a file named listing5-13.py and execute it, you will see output like the following.

$ python ./listing5-13.py
name = name label = pet_name
name = age label = years_young

Now let’s discuss the use of expressions to filter data.

Expressions

Expressions are another element in the X DevAPI that is a simple, yet powerful feature. Expressions are synonymous with the clauses we use in SQL statements for filtering data in CRUD statements. There are several forms of expressions. We can use strings, Boolean expressions, or embed actual expressions such as equality or inequality. Let’s examine each of these.

Expression Strings

Expression strings are those strings that need to be evaluated at runtime. Typically, they use one or more variables “bound” (called parameter binding ) to placeholders in the string. This permits you to assign values at runtime for dynamic filtering rather than static values as we will see in the next section. We will see more about parameter binding in a later section.

Listing 5-14 shows an example like one we used in a previous example looking for the fish in our pets_json collection. However, in this case, we use a parameter to contain the type, which could presumably be read at runtime and thus allow us to make our code dynamically filter the collection find results.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets = mysqlx_session.get_schema("animals").get_collection("pets_json")
# Do a find on the collection - find the fish with an expression string and parameter binding
fish_type = 'fish'
mydoc = pets.find("type = :mytype").bind('mytype', fish_type).execute()
print(mydoc.fetch_one())
# Close the connection
mysqlx_session.close()
Listing 5-14

Expression Strings

If you save this code to a file named listing5-14.py and execute it, you will see output like the following.

$ python ./listing5-14.py
{"breed": "koi", "age": 7, "_id": "9801A79DE0938FBD11E805FBCB21AB35", "type": "fish", "name": "Spot"}

Boolean Expression Strings

This form of expression uses a string much like we use in the WHERE clause for SQL statements. That is, we express the filter using natural language where the comparison is either true or false. Listing 5-15 are Boolean expression strings from previous examples. The first line is a relational data example in which we want the results to include only those items whose type column is equal to “dog.” The second is a document store example in which we want the results to include only those items whose type element has the value “fish.”

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the collection.
pets_json = mysqlx_session.get_schema("animals").get_collection("pets_json")
# Get the table.
pets_sql = mysqlx_session.get_schema("animals").get_table("pets_sql")
res = pets_sql.select().where("type = 'dog'").limit(1).execute()
print("SQL result ="),
for row in res.fetch_all():
    for i in range(0,len(res.columns)):
       print("{0}".format(row[i])),
print("")
mydoc = pets_json.find("type = 'fish'").execute()
print("JSON result = {0}".format(mydoc.fetch_one()))
# Close the connection
mysqlx_session.close()
Listing 5-15

Boolean Expression Strings

If you save this code to a file named listing5-15.py and execute it, you will see output like the following.

$ python ./listing5-15.py
SQL result = 1 Violet 6 dachshund dog
JSON result = {"breed": "koi", "age": 7, "_id": "9801A79DE0938FBD11E805FBCB21AB35", "type": "fish", "name": "Spot"}

Tip

You can find a complete set of extended Backus-Naar form5 drawings for expressions and method chaining in the X DevAPI Users guide at https://dev.mysql.com/doc/x-devapi-userguide/en/ .

Warnings and Errors

Another area in which we need to spend some time learning about is the report of warnings sent from the server and handling errors from the X DevAPI. Fortunately, the X DevAPI has facilities for getting the warnings. However, errors will take a bit more work. Let’s look at warnings first.

Warnings from the Server

Handling warnings is easy because the X DevAPI has a mechanism built in to help you get the warnings information. The Warning class has three properties as shown in the following. We can use these to get the warnings should they occur.
  • Level—level of the warning

  • Code—warning code

  • Message—warning message

Note

By default, all warnings are sent from the server to the client. However, you can suppress warnings to save bandwidth. Use the set_fetch_warnings() in the Session class to control whether warnings are discarded at the server or sent to the client. Use the get_fetch_warnings() method to get the active setting.

In fact, we can use the get_warnings() method to check to see if there are warnings we need to process. However, the X DevAPI sends warnings to the client each time they occur so if you want to check for warnings, you must do so after each execution. Listing 5-16 shows one way you can write code to handle errors. It is by no means the only way, but does demonstrate the Warning class methods.

Note

This example requires the animals database setup. See the “Example Data Used in this Chapter” section previously for how to setup the database.

#This method checks the result for warnings and prints them
# if any exist.
#
# result[in]     result object
def process_warnings(result):
    if result.get_warnings_count():
        for warning in result.get_warnings():
            print("WARNING: Type {0} (Code {1}): {2}".format(*warning))
    else:
        print "No warnings were returned."
# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the animals schema.
schema = mysqlx_session.get_schema("animals")
# Try to create the table using a SQL string. It should throw a warning.
res = mysqlx_session.sql("CREATE TABLE IF NOT EXISTS animals.pets_sql ("
                         "`id` int auto_increment primary key, "
                         "`name` char(20), "
                         "`age` int, "
                         "`breed` char(20), "
                         "`type` char(12))").execute()
process_warnings(res)
# Close the connection
mysqlx_session.close()
Listing 5-16

Processing Warnings

Note that I wrote a method named process_warnings() that takes a result object and checks to see if there are errors by calling the get_warnings_count() method. If this method returns a positive integer, it means there are warnings and if so, I get the type, code, and message from the warning object and print the data. If there are no warnings, I print a message stating there were no errors (but you probably don’t want to know that).

If you save this code to a file named listing5-16.py and execute it, you will see the following results. Note that you may have to run it a second time if you deleted the animals collection.

$ python ./listing5-16.py
WARNING: Type 1 (Code 1050): Table 'pets_sql' already exists

Now let’s see how we can handle errors from the X DevAPI.

Errors from the X DevAPI

As I mentioned, there is nothing implemented in the X DevAPI specifically for handling errors, but there are facilities that we can use. In this case, we’re going to get some help from our database connectors. That is, the database connectors implement the language-specific error handling (exception handling) mechanisms making it natural to handle errors from the X DevAPI methods. In other words, they implement exception handling.6

Using Python as an example, the Python language implements a try...exception block (sometimes called a try or exception block ). This construct allows code that “raises” an exception in the form of the raise() method to have the exception captured by the calling code (the code with the nearest try block). The syntax is as follows.

try:
    # some operation 1
    # some operation 2
    # some operation 3
    # some operation 4
    # some operation 5
except:
    # catch the exception
finally:
    # do this after the success or capture

What this allows us to do is “try” an operation (or more) and if they fail by raising an exception, the code will skip any remaining operations in the try segment and skip to the except segment.

Let’s look at what happens when you do not use exception handling and the code fails. That is, the X DevAPI throws an exception. Listing 5-17 shows a simple script with errors. Can you spot them? Hint: check the password and what happens when you try to create a table that already exists?

# Import the MySQL X module
import mysqlx
import getpass
# Get a session with a URI
mysqlx_session = mysqlx.get_session("root:wrongpassworddude!@localhost:33060")
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Get the animals schema.
schema = mysqlx_session.get_schema("animals")
# Try to create the table using a SQL string. It should throw an
# error that it already exists.
res = mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
                         "`id` int auto_increment primary key, "
                         "`name` char(20), "
                         "`age` int, "
                         "`breed` char(20), "
                         "`type` char(12))").execute()
# Close the connection
mysqlx_session.close()
Listing 5-17

Not Handling Errors

If you save this code to a file named listing5-17.py and execute it, you will see the following results (extraneous data removed for brevity).

$ python ./listing5-17.py
Traceback (most recent call last):
  File "./listing5-17.py", line 6, in <module>
    mysqlx_session = mysqlx.get_session("root:wrongpassworddude!@localhost:33060")
...
  File "/Library/Python/2.7/site-packages/mysqlx/protocol.py", line 129, in read_auth_ok
    raise InterfaceError(msg.msg)
mysqlx.errors.InterfaceError: Invalid user or password

Oh, dear, that’s terrible! What we’ve got here is a traceback dump, which is how Python communicates unhandled exceptions. The key message we should heed is the first line that shows us the line of code in the script that started a sequence of method calls that resulted in the exception thrown as shown in the last two lines. Here we see that the get_session() call resulted in a mysqlx.errors.InterfaceError thrown from the X Protocol code in the connector. This demonstrates how badly things can go if you do not use exception handling. But we can make it a lot better.

Let’s look at an example with exception handling. Listing 5-18 shows a script with deliberate errors that will cause the X DevAPI to throw exceptions. In this case, it is the CREATE TABLE SQL statement that will fail. More specific, it will fail because the table already exists.

If you run this script and it does not fail, be sure to check that the table already exists. We are using the fact that the table already exists so when the CREATE is executed, we will get an exception. As you will see, the exception is not easily understood either.

# Import the MySQL X module
import mysqlx
try:
    # Get a session with a URI
    mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
    # Check the connection
    if not mysqlx_session.is_open():
        print("Connection failed!")
        exit(1)
    # Get the animals schema.
    schema = mysqlx_session.get_schema("animals")
    # Try to create the table using a SQL string. It should throw an error
    # that it already exists.
    res = mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
                             "`id` int auto_increment primary key, "
                             "`name` char(20), "
                             "`age` int, "
                             "`breed` char(20), "
                             "`type` char(12))").execute()
except Exception as ex:
    print("ERROR: {0}:{1}".format(*ex))
# Close the connection
mysqlx_session.close()
Listing 5-18

Handling Errors—Global Exception

When we run this code, we get a much better result. If you save this code to a file named listing5-18.py and execute it, you will see the following results. Note that the output you can expect from this improved version. It is easier to read and more informative.

$ python ./listing5-18.py
ERROR: -1: Table 'pets_sql' already exists

Although there is no tried and true rule for how much you can place in an exception block, you should keep exception blocks small—say isolated to a single concept or process—to avoid cases where debugging the code makes it difficult to know which of the dozens of method calls triggered the exception. If you use a language like Python that throws a call stack trace, that may not be difficult but if your language doesn’t have it or rerunning the code to create one is not possible, keeping the exception blocks small can help you isolate the code where the problem occurred.

Listing 5-19 shows an example that includes try blocks around each X DevAPI statement. It also demonstrates how to capture specific exceptions thrown. That is, the except: syntax allows you to specify a specific exception. In this case, I capture the exception thrown by the X DevAPI.

# Import the MySQL X module
import mysqlx
import getpass
# Get a session with a URI
mysqlx_session = None
try:
    mysqlx_session = mysqlx.get_session("root:wrongpassworddude!@localhost:33060")
except mysqlx.errors.InterfaceError as ex:
    print("ERROR: {0} : {1}".format(*ex))
    passwd = getpass.getpass("Wrong password, try again: ")
finally:
    mysqlx_session = mysqlx.get_session("root:{0}@localhost:33060".format(passwd))
# Check the connection
if not mysqlx_session.is_open():
    print("Connection failed!")
    exit(1)
# Demostrate error from get_schema()
schema = mysqlx_session.get_schema("animal")
if (not schema.exists_in_database()):
    print("Schema 'animal' doesn't exist.")
# Get the animals schema.
schema = mysqlx_session.get_schema("animals")
try:
    # Try to create the table using a SQL string. It should throw an
    # error that it already exists.
    res = mysqlx_session.sql("CREATE TABLE animals.pets_sql ("
                      "`id` int auto_increment primary key, "
                      "`name` char(20), "
                      "`age` int, "
                      "`breed` char(20), "
                      "`type` char(12))").execute()
except mysqlx.errors.OperationalError as ex:
    print("ERROR: {0} : {1}".format(*ex))
# Close the connection
if mysqlx_session:
    mysqlx_session.close()
Listing 5-19

Handling Errors—Local Exceptions

If you save this code to a file named listing5-19.py and execute it, you will see the following results. Be sure to enter the correct password when prompted. This is because there is only one test for a correct password. Your challenge is to determine a way to improve the code by allowing multiple retries. Hint: use a loop.

$ python ./listing5-19.py
ERROR: -1 : Invalid user or password
Wrong password, try again:
Schema 'animal' doesn't exist.
ERROR: -1 : Table 'pets_sql' already exists

The example also shows an interesting way you can handle exceptions—retrying the statement. Normally, you would place the statement you want to retry in a loop or similar structure with a time or attempt limit. Here I just retry the session method once prompting the user for the password.

Tip

For best results, encapsulate your code using shorter exception blocks so that you can isolate the code that caused the error easily.

Now let’s look at the additional features available when using the X DevAPI.

Additional Features

Now that we have seen all the major classes and methods available in the X DevAPI, let us now examine some of the features that are exposed by the X DevAPI; specifically, examples of parameter binding, chaining methods, prepared statements, and asynchronous execution.

Note

This example uses the world_x database, which you can download from https://dev.mysql.com/doc/index-other.html . Simply download the compressed file, uncompress it, then include it in the MySQL Shell with the source command or use the mysql client and the source command. For a walk-through of how to install the world_x database, see the “Installing the Sample Database” section in Chapter 4.

Parameter Binding

Parameter binding allows us to apply values to expressions at runtime. Parameter binding is typically used for filters and is done prior to executing the operation (hence you will see .bind().execute() often). Therefore, the benefits of parameter binding are that it allows you to separate values from your expressions. This is accomplished with the bind() method for all classes that support parameter binding.

Parameters can be "bound" using one of two methods: you can use anonymous parameters or you can use named parameters. However, there are restrictions on when you can use each. In particular, anonymous parameters can only be used in SQL strings (expression) whereas named parameters are used in CRUD operations. Let's see an example of each.

Listing 5-20 shows an example of using anonymous parameters. Anonymous parameters are signified by using a question mark. Note how we do this in the SQL statements in the following.

$ mysqlsh root@localhost:33060 --sql
Creating a session to 'root@localhost:33060'
Enter password:
Your MySQL connection id is 74 (X protocol)
Server version: 8.0.11 MySQL Community Server (GPL)
No default schema selected; type use <schema> to set one.
MySQL Shell 8.0.11
Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help' or '?' for help; 'quit' to exit.
 MySQL  localhost:33060+ ssl  SQL > PREPARE STMT FROM 'SELECT * FROM world_x.city WHERE name like ? LIMIT ?';
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > SET @name_wild = 'Ar%';
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > SET @numrows = 1;
Query OK, 0 rows affected (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > EXECUTE STMT USING @name_wild, @numrows;
+----+--------+-------------+------------+------------------------+
| ID | Name   | CountryCode | District   | Info                   |
+----+--------+-------------+------------+------------------------+
| 18 | Arnhem | NLD         | Gelderland | {"Population": 138020} |
+----+--------+-------------+------------+------------------------+
1 row in set (0.00 sec)
 MySQL  localhost:33060+ ssl  SQL > q
Bye!
Listing 5-20

Parameter Binding Example (MySQL Shell)

We can take away a couple of things from this example. First, anonymous parameters are only used in SQL statements. Second, anonymous parameters are completed (values provided) in the order they appear in the SQL statement. Third, and finally anonymous parameters can be used with prepared statements.7

Listing 5-21 shows several examples of using named parameters. The key point to notice is how the parameter is given a name preceded by a colon. When the bind() method is called, we supply the named parameter (without the colon) and its value.

# Import the MySQL X module
import mysqlx
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
schema = mysqlx_session.get_schema("world_x")
# Collection.find() function with hardcoded values
myColl = schema.get_collection('countryinfo')
myRes1 = myColl.find("GNP >= 828").execute()
print(myRes1.fetch_one())
# Using the .bind() function to bind parameters
myRes2 = myColl.find('Name = :param1 and GNP = :param2').bind('param1','Aruba').bind('param2', '828').execute()
print(myRes2.fetch_one())
# Using named parameters
myColl.modify('Name = :param').set('GNP', '829').bind('param', 'Aruba').execute()
# Binding works for all CRUD statements except add()
myRes3 = myColl.find('Name LIKE :param').bind('param', 'Ar%').execute()
print(myRes3.fetch_one())
# Ok, now put the candle back...
myColl.modify('Name = :param').set('GNP', '828').bind('param', 'Aruba').execute()
# Close the connection
mysqlx_session.close()
Listing 5-21

Parameter Binding Example

Note how we pass multiple parameters to be bound. In this case, we simply call bind() as many times as we have parameters to bind. This is possible due to the method chaining feature as described in the next section. That is, the bind() method returns an instance of itself and thus when we call the next bind() method, it is repeating the call but with a different parameter and its value.

Tip

Named parameters may not start with a number. For example,:1test is not a valid named parameter name.

If you save this code to a file named listing5-21.py and execute it, you will see the following results.

$ python ./listing5-21.py
{"GNP": "828", "Name": "Aruba", "government": {"GovernmentForm": "Nonmetropolitan Territory of The Netherlands", "HeadOfState": "Beatrix"}, "demographics": {"LifeExpectancy": 78.4000015258789, "Population": 103000}, "_id": "ABW", "IndepYear": null, "geography": {"SurfaceArea": 193, "Region": "Caribbean", "Continent": "North America"}}
{"GNP": "828", "Name": "Aruba", "government": {"GovernmentForm": "Nonmetropolitan Territory of The Netherlands", "HeadOfState": "Beatrix"}, "demographics": {"LifeExpectancy": 78.4000015258789, "Population": 103000}, "_id": "ABW", "IndepYear": null, "geography": {"SurfaceArea": 193, "Region": "Caribbean", "Continent": "North America"}}
{"GNP": "829", "Name": "Aruba", "government": {"GovernmentForm": "Nonmetropolitan Territory of The Netherlands", "HeadOfState": "Beatrix"}, "demographics": {"LifeExpectancy": 78.4000015258789, "Population": 103000}, "_id": "ABW", "IndepYear": null, "geography": {"SurfaceArea": 193, "Region": "Caribbean", "Continent": "North America"}}

Now let's look at method chaining and how it works.

Method Chaining

Method chaining (also known as named parameter idiom), is a design constraint in object-oriented programming where each method (that supports chaining) returns an instance of an object. Thus, one can access (call) any method on the returned object simply by adding the call to the end of the first method.

For example, if a class X has a method a() that returns object Y with a method b(), we can chain calls together as follows.

x = something.get_x()
res = x.a().b()

In this case, the x.a() method executes first, then when it returns with a Y object instance, it calls the b() method on the Y object instance.

Where method chaining shines in the X DevAPI is in the implementation of the relational data methods. In particular, those classes and methods that support the SQL CRUD commands. Listing 5-22 is an example of a complex SELECT operation for a table.

# Import the MySQL X module
import mysqlx
mysqlx_session = mysqlx.get_session("root:secret@localhost:33060")
# Get the table
city = mysqlx_session.get_schema("world_x").get_table("city")
# Perform a complex select
res = city.select(['Name', 'District']).where("Name LIKE :param1").order_by(["District", "Name"]).bind('param1', 'X%').limit(1).execute()
# Show results
print("SQL result ="),
for row in res.fetch_all():
    for i in range(0,len(res.columns)):
       print("{0}".format(row[i])),
print("")
# Close the connection
mysqlx_session.close()
Listing 5-22

Method Chaining

If you save this code to a file named listing5-22.py and execute it, you will see the following results.

$ python ./listing5-22.py
SQL result = Xuangzhou Anhui

Here we see two lines of code and several object instances in use and a host of methods. On the second code line (ignoring comments), we use a mysqlx session object to get a schema object then chain that with a call to the Schema class method get_table(), which returns a table object instance.

On the third code line, we are using the table object instance calling the select() method, which returns a SelectStatement object instance that we chain by calling its where() method, which returns the same SelectStatement object and we call its order_by() method, which returns the same SelectStatement object then we bind the parameter with the bind() method that returns the same SelectStatement object, and finally we call the execute() method, which returns a SqlResult object. Wow!

If you're getting the idea that method chaining hides a lot of the details about objects and avoids repetitious code of storing object instances in variables, you're right! That's exactly what we're doing.

As you can see, method chaining allows us to express concepts in our code much more clearly that the older style of classes with methods do not return object instances (or even older styles that simply return 0 or 1 to indicate success or failure8). Mastering the X DevAPI means mastering how you can chain methods together to simplify and make your code easier to read and understand. Cool, eh?

For more information about the concepts of method chaining, see https://en.wikipedia.org/wiki/Method_chaining .

CRUD Prepared Statements

Prepared CRUD statements are cases in which we want to perform a number of operations on an object prior to calling the execute() method. In this way, we "prepare" the object instance (statement) for execution. That is, instead of directly binding and executing CRUD operations by chaining bind() and execute() or simply execute() it, we can manipulate the CRUD operation to store such things as filters and other criteria in a variable for later execution.

The advantage of doing this means we can bind several parameters or sets of variables to the expressions. This gives us better performance because we can “prepare” variables ahead of time and execute them later. This can give us get better performance when executing many similar operations.

You may be thinking that CRUD prepared statements are similar in concept to SQL prepared statements. This is true, but unlike SQL prepared statements, CRUD prepared statements are implemented in class methods and thus can be incorporated in our code with very little effort.

Let's look at an example. Listing 5-23 shows an example of preparing a CRUD statement. In this case, we prepare a find() statement using a parameter and save the result (the FindStatement object) to a variable. When we want to execute this statement, we use the variable to call the bind() method providing a value then the execute() method to execute the FindStatement.

# Import the MySQL X module
import mysqlx
# Get a session with a URI
mysql_session = mysqlx.get_session("root:secret@localhost:33060")
# Check the connection
if not mysql_session.is_open():
    print("Connection failed!")
    exit(1)
# Create a schema.
schema = mysql_session.get_schema("animals")
# Create a new collection
pets = schema.get_collection("pets_json")
# Prepare a CRUD statement.
find_pet = pets.find("name = :param")
# Now execute the CRUD statement different ways.
mydoc = find_pet.bind('param', 'JonJon').execute()
print(mydoc.fetch_one())
mydoc = find_pet.bind('param', 'Charlie').execute()
print(mydoc.fetch_one())
mydoc = find_pet.bind('param', 'Spot').execute()
print(mydoc.fetch_one())
# Close the connection
mysql_session.close()
Listing 5-23

CRUD Prepared Statements

Note the three find_pet.bind() method calls. Here we execute the find statement three times; once for each pet’s name we want to find. Clearly, this is only a small example but demonstrates the power of using CRUD prepared statements.

If you save this code to a file named listing5-23.py and execute it, you will see the following results.

$ python ./listing5-23.py
{"breed": "poodle", "age": 15, "_id": "9801A79DE093B43A11E805FBCB215AFA", "type": "dog", "name": "JonJon"}
{"breed": "dachshund", "age": "6", "_id": "9801A79DE093BFD511E805FBCB21CF30", "type": "dog", "name": "Charlie"}
{"breed": "koi", "age": 7, "_id": "9801A79DE0938FBD11E805FBCB21AB35", "type": "fish", "name": "Spot"}

Asynchronous Execution

For those clients that support asynchronous programming such as C/J, C/Node.js, and C/Net, the X DevAPI permits the use of asynchronous mechanisms such as callbacks, async() calls, and so forth. These mechanisms make it possible to allow an operation to run in parallel with other operations. Let's see an example from Java.

Note

Currently, neither the C/Py nor C/C++ permit asynchronous execution but may in the future. Check new releases of these connectors for updates.

Table employees = db.getTable("employee");
// execute the query asynchronously, obtain a future
CompletableFuture<RowResult> rowsFuture = employees.select("name", "age").where("name like :name").orderBy("name").bind("name", "m%").executeAsync();

Here we see the executeAsync() method, which is how the Java connector permits the asynchronous execution of the execute() method. That is, the select() runs asynchronously and when it returns (finishes), it triggers the future defined by the CompletableFuture template/class (or generic class in Java9).

Note

Depending on which language you are using, the X DevAPI may implement a function such as executeAsync() in addition to or instead of execute(). Check the X DevAPI documentation for your chosen connector for the correct method names and uses.

For more information about asynchronous execution, see the X DevAPI guide for the connector that matches your choice of language.

For More Information

If you would like to know more detailed information about the implementations of the X DevAPI in the database connectors and MySQL Shell, visit the following links for descriptions and lists of all classes, methods, properties, and help functions. The sites are developer focused and may not include detailed explanations or examples.

Note

Some of the documentation for these components may not match the version numbers listed at the beginning of the chapter. It is fine if the documentation is for a newer release and you should install the newest releases. However, at the time of this writing, the MySQL Shell Users Guide was in the process of being updated. Check back regularly to ensure you are using the latest documentation available.

Summary

The X DevAPI is a marvel of sophistication to the point of simplification for NoSQL interface with a MySQL server. The X DevAPI introduces a new, modern and easy-to-learn way to work with your data.

The X DevAPI is the primary mechanism you will use to build document store applications. Although the X DevAPI isn’t a standalone library—you must use a client that exposes the X DevAPI through the X Protocol—the X DevAPI is still a major effort to change the way you interact with MySQL. For the first time, we now have both an SQL and a NoSQL interface for MySQL.

In this chapter, we explored the X DevAPI and examined the major classes and methods available for connecting to the MySQL server, creating collections, working with results, and even how to work with relational data. Finally, we also saw a set of quick references tables that you can use as the primary reference for developing document store applications.

In Chapter 6, we have a deep dive into the X Plugin, which will give you a better understanding of what the X Plugin does, how to configure it, and how best to manage it as part of your normal database administration tasks. Following that chapter, we will see the details of the X Protocol and later a working example of a document store application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.80.34