Beta Draft: 2016-08-16

Chapter 9 Working with Result Sets

Table of Contents

9.1 Result Set Classes
9.2 Working with Document IDs
9.3 Working with AUTO-INCREMENT Values
9.4 Working with Data Sets
9.5 Fetching All Data Items at Once
9.6 Working with SQL Result Sets
9.7 Working with Metadata
9.8 Support for Language Native Iterators

This section explains how to work with the results of processing.

9.1 Result Set Classes

All database operations return a result. The type of result returned depends on the operation which was executed. The different types of results returned are outlined in the following table.

Result Class

Returned By

Provides

Result

add().execute(), insert().execute(), ...

affectedRows, lastInsertId, warnings

SqlResult

session.sql()

affectedRows, lastInsertId, warnings, fetched data sets

DocResult

find().execute()

fetched data set

RowResult

select.execute()

fetched data set

The following class diagram gives a basic overview of the result handling.

Figure 9.1 Results - Class Diagram

Results - Class Diagram

9.2 Working with Document IDs

Every document has a unique identifier called the document ID, which can be thought of as the equivalent of a tables' primary key. The document ID value can be manually assigned when adding a document. If no value is assigned, a document ID is generated and assigned to the document automatically. Without knowledge of the generated IDs you cannot reliably fetch and update any of the previously inserted documents. The following methods enable you to access the document ID value from the return value of collection.add():

  • getDocumentId()

  • getDocumentIds()

Note the difference in the plural. getDocumentId() is used to get the unique identifier value when a single document is added. For example assuming that a test schema is assigned to the variable db and the collection my_collection exists:

// Get the collection
var myColl = db.getCollection('my_collection');

// Insert a document
var res = myColl.add({ name: 'Jack', age: 15, height: 1.76 }).execute();

// Print the document ID assigned to the document
print('Document Id:', res.getDocumentId());

getDocumentIds() returns a list of all document IDs for documents added. For example:

var res = collection.add({ _id: 1, name: 'Jack'}).add({ _id: 223, name: 'Jim'}).execute();

ids = res.getDocumentIds());
ids.forEach(function(id) { print(id); });

// prints 1
// prints 223

These two methods are necessary because X DevAPI supports chaining of collection.add() and table.insert() calls as one command. For example:

coll.add({name: 'Jack'}).add({age: 13}).execute();

When the above code is run two documents are added to the collection, which creates two document IDs implicitly. You can not execute getDocumentID() in this case, because multiple document IDs are returned. To access all the resulting document IDs from a chain of calls, use getDocumentIds(). To get the document ID of the most recently added document, use getDocumentId().

To retrieve the document IDs of certain documents that have been added, use getDocumentIds() and address the specific document IDs. For example:

coll.add({name: 'Jack'}).add({age: 13}).execute();
ids = res.getDocumentIds());
// [0] - first, [1] - second and last, ...
print(ids[1]);

9.3 Working with AUTO-INCREMENT Values

A common MySQL task is to use AUTO_INCREMENT columns, for example generating primary key values. This section explains how to retrieve AUTO_INCREMENT values when adding rows using X DevAPI. For more background information, see Using AUTO_INCREMENT. X DevAPI provides the following methods to return AUTO_INCREMENT column values from the return value of table.insert():

  • getFirstAutoIncrementValue()

  • getAutoIncrementValues()

In the following examples it is assumed that the table contains a column for which the AUTO_INCREMENT attribute is set. Furthermore it is assumed that all insertions succeed. The getFirstAutoIncrementValue() function is used when adding rows individually, or in other words when not chaining table.insert() calls. For example:

res = tab.insert(['name']).values('Sakila'}.execute();
print(res.getFirstAutoIncrementValue());

When you chain multiple table.insert() calls, there are potentially multiple AUTO_INCREMENT values returned. The getAutoIncrementValues() function returns a list of all AUTO_INCREMENT values generated when inserting multiple rows:

res = tab.insert(['name']).values('Sakila').values('Otto').execute();
print(res.getAutoIncrementValues());

// prints a list of values for 'Sakila' and 'Otto'

Note that AUTO_INCREMENT columns may be used for generating primary key or id values but are not limited to them.

9.4 Working with Data Sets

Operations that fetch data items return a data set as opposed to operations that modify data and return a result set. Data items can be read from the database using Collection.find(), Table.select() and NodeSession.sql(). All three methods return data sets which encapsulate data items. Collection.find() returns a data set with documents and Table.select() respectively NodeSession.sql() return a data set with rows.

All data sets implement a unified way of iterating their data items. The unified syntax supports fetching items one by one using fetchOne() or retrieving a list of all items usning fetchAll(). fetchOne() and fetchAll() follow forward-only iteration semantics. Connectors implementing the X DevAPI can offer more advanced iteration patterns on top to match common native language patterns. Consult your language's Connector reference for more details, see Additional Documentation.

The following example shows how to access the documents returned by a Collection.find() operation by using fetchOne() to loop over all documents.

The first call to fetchOne() returns the first document found. All subsequent calls increment the internal data item iterator cursor by one position and return the item found making the second call to fetchOne() return the second document found, if any. When the last data item has been read and fetchOne() is called again a NULL value is returned. This ensures that the basic while loop shown works with all languages which implement the X DevAPI if the language supports such an implementation.

When using fetchOne() it is not possible to reset the internal data item cursor to the first data item to start reading the data items again. An data item - here a Document - that has been fetched once using fetchOne() can be discarded by the Connector. The data item's life time is decoupled from the data set. From a Connector perspective items are consumed by the caller as they are fetched. This example assumes that the test schema exists.

MySQL Shell JavaScript Code

var myColl = db.getCollection('my_collection');

var res = myColl.find('name like :name').bind('name','S%').
        execute();

var doc;
while (doc = res.fetchOne()) {
  print(doc);
}

MySQL Shell Python Code

myColl = db.getCollection('my_collection')

res = myColl.find('name like :name').bind('name','S%').execute()

doc = res.fetchOne()
while doc:
        print doc
        doc = res.fetchOne()

C# Code

var myColl = db.GetCollection("my_collection");

var res = myColl.Find("name like :name").Bind("name", "S%")
  .Execute();

DbDoc doc;
while ((doc = res.FetchOne()) != null)
{
  Console.WriteLine(doc);
}

Java Code

Collection myColl = db.getCollection("my_collection");

DocResult res = myColl.find("name like :name").bind("name", "S%")
  .execute();

DbDoc doc;
while ((doc = res.fetchOne()) != null) {
  System.out.println(doc);
}

C++ Code

Collection myColl = db.getCollection("my_collection");

DocResult res = myColl.find("name like :name").bind("name", "S%").execute();
DbDoc doc;
while ((doc = res.fetchOne()))
{
  cout <<*doc <<endl;
}

When using Node.js results are returned to a callback function, which is passed to execute() in an asychronous manner whenever results from the server arrive.

Node.js JavaScript Code

myColl.find('name like :name').bind('S%').execute(function (doc) {
    console.log(doc);
});

The following example shows how to directly access the rows returned by a Table.select() operation.

The basic code pattern for result iteration is the same. The difference between the following and the previous example is in the data item handling. Here, fetchOne() returns Rows. The exact syntax to access the column values of a Row language dependent. Implementations seek to provide a language native access pattern. The example assumes that the test schema exists and that the employee table exists in myTable.

MySQL Shell JavaScript Code

var myRows = myTable.select(['name', 'age']).
        where('name like :name').bind('name','S%').
        execute();

var row;
while (row = myRows.fetchOne()) {
  // Accessing the fields by array
  print('Name: ' + row['name'] + '\n');

  // Accessing the fields by dynamic attribute
  print(' Age: ' + row.age + '\n');
}

MySQL Shell Python Code

myRows = myTable.select(['name', 'age']).where('name like :name').bind('name','S%').execute()

row = myRows.fetchOne()
while row:
        # Accessing the fields by array
        print 'Name: %s\n' % row[0]

        # Accessing the fields by dynamic attribute
        print ' Age: %s\n' % row.age

        row = myRows.fetchOne()

Node.js JavaScript Code

var myRows = myTable.select(['name', 'age']).
        where('name like :name').bind('name','S%').
	execute(function (row) {

  // Accessing the fields by array
  console.log('Name: ' + row['name']);

  // Accessing the fields by dynamic attribute
  console.log(' Age: ' + row.age);
});

C# Code

var myRows = myTable.Select("name", "age")
  .Where("name like :name").Bind("name", "S%")
  .Execute();

Row row;
while ((row = myRows.FetchOne()) != null)
{
  // Accessing the fields by array
  Console.WriteLine("Name: " + row[0]);

  // Accessing the fields by name
  Console.WriteLine("Age: " + row["age"]);
}

Java Code

RowResult myRows = myTable.select("name, age")
  .where("name like :name").bind("name", "S%")
  .execute();

Row row;
while ((row = myRows.fetchOne()) != null) {
  // Accessing the fields
  System.out.println(" Age: " + row.getInt("age") + "\n");
}

C++ Code

RowResult myRows = myTable.select("name", "age")
                          .where("name like :name")
                          .bind("name", "S%")
                          .execute();

Row row;
while ((row = myRows.fetchOne()))
{
  cout <<"Name: " << row["name"] <<endl;
  // note: dynamic attributes not possible in C++
  Field age("age");
  cout <<"Age: " << row[age] <<endl;
}

9.5 Fetching All Data Items at Once

Data sets feature two iteration patterns available with all Connectors. The first pattern using fetchOne() enables applications to consume data items one by one. The second pattern using fetchAll() passes all data items of a data set as a list to the application. Drivers use appropriate data types of their programming language for the list. Because different data types are used, the language's native constructs are supported to access the list elements. Consult your language's Connector reference for more details, see Additional Documentation. The example assumes that the test schema exists and that the employee table exists in myTable

MySQL Shell JavaScript Code

var myResult = myTable.select(['name', 'age']).
  where('name like :name').bind('name','S%').
  execute();

var myRows = myResult.fetchAll();

for (index in myRows){
  print (myRows[index].name + " is " + myRows[index].age + " years old.");
}

MySQL Shell Python Code

myResult = myTable.select(['name', 'age']) \
  .where('name like :name').bind('name','S%') \
  .execute()

myRows = myResult.fetchAll()

for row in myRows:
  print "%s is %s years old." % (row.name, row.age)

C# Code

var myRows = myTable.Select("name", "age")
  .Where("name like :name").Bind("name", "S%")
  .Execute();
var rows = myRows.FetchAll();

Java Code

RowResult myRows = myTable.select("name, age")
  .where("name like :name").bind("name", "S%")
  .execute();

List<Row> rows = myRows.fetchAll();
for (Row row : rows) {
  // Accessing the fields
  System.out.println(" Age: " + row.getInt("age") + "\n");
}

C++ Code

// The fetchAll() method is not yet implemented in Connector/C++

When mixing fetchOne() and fetchAll() to read from one data set keep in mind that every call to fetchOne() or fetchAll() consumes the data items returned. Items consumed cannot be requested again. If, for example, an application calls fetchOne() to fetch the first data item of a data set, then a subsequent call to fetchAll() returns the second to last data item. The first item is not part of the list of data items returned by fetchAll(). Similarly, when calling fetchAll() again for a data set after calling it previously, the second call returns an empty collection.

The use of fetchAll() forces a Connector to build a list of all items in memory before the list as a whole can be passed to the application. The life time of the list is independent from the life of the data set that has produced it.

Asynchronous query executions return control to caller once a query has been issued and prior to receiving any reply from the server. Calling fetchAll() to read the data items produced by an asynchronous query execution may block the caller. fetchAll() cannot return control to the caller before reading results from the server is finished.

9.6 Working with SQL Result Sets

When executing an SQL operation on a NodeSession with NodeSession.sql() an SqlResult is returned.

Result iteration is identical to working with results from CRUD operations. The example assumes that the users table exists.

MySQL Shell JavaScript Code

var res = nodeSession.sql('SELECT name, age FROM users').execute();

var row;
while (row = res.fetchOne()) {
  print('Name: ' + row['name'] + '\n');
  print(' Age: ' + row.age + '\n');
}

MySQL Shell Python Code

res = nodeSession.sql('SELECT name, age FROM users').execute()

row = res.fetchOne()

while row:
        print 'Name: %s\n' % row[0]
        print ' Age: %s\n' % row.age
        row = res.fetchOne()

Node.js JavaScript Code

var res = nodeSession.sql('SELECT name, age FROM users').execute(function (row) {
  console.log('Name: ' + row['name']);
  console.log(' Age: ' + row.age);
});

C# Code

var res = nodeSession.SQL("SELECT name, age FROM users").Execute();

while (res.Next())
{
  Console.WriteLine("Name: " + res.Current["name"]);
  Console.WriteLine("Age: " + res.Current["age"]);
}

Java Code

SqlResult res = nodeSession.sql("SELECT name, age FROM users").execute();

Row row;
while ((row = res.fetchOne()) != null) {
  System.out.println(" Name: " + row.getString("name") + "\n");
  System.out.println(" Age: " + row.getInt("age") + "\n");
}

C++ Code

RowResult res = nodeSession.sql("SELECT name, age FROM users").execute();

Row row;
while ((row = res.fetchOne())) {
  cout << "Name: " << row["name"] << endl;
  cout << " Age: " << row["age"] << endl;
}

SqlResult differs from results returned by CRUD operations in the way how result sets and data sets are represented. A SqlResult combines a result set produced by, for example, INSERT, and a data set, produced by, for example, SELECT in one. Unlike with CRUD operations there is no distinction between the two types. A SqlResult exports methods for data access and to retrieve the last inserted id or number of affected rows.

Use the hasData() method to learn whether a SqlResult is a data set or a result. The method is useful when code is to be written that has no knowledge about the origin of a SqlResult. This can be the case when writing a generic application function to print query results or when processing stored procedure results. If hasData() returns true, then the SqlResult origins from a SELECT or similar command that can return rows.

A return value of true does not indicate whether the data set contains any rows. The data set may be empty. It is empty if fetchOne() returns NULL or fetchAll() returns an empty list. The example assumes that the procedure my_proc exists.

MySQL Shell JavaScript Code

var res = nodeSession.sql('CALL my_proc()').execute();

if (res.hasData()){

  var row = res.fetchOne();
  if (row){
    print('List of row available for fetching.');
    do {
      print(row);
    } while (row = res.fetchOne());
  }
  else{
    print('Empty list of rows.');
  }
}
else {
  print('No row result.');
}

MySQL Shell Python Code

res = nodeSession.sql('CALL my_proc()').execute()

if res.hasData():

        row = res.fetchOne()
        if row:
                print 'List of row available for fetching.'
                while row:
                        print row
                        row = res.fetchOne()
        else:
                print 'Empty list of rows.'
else:
        print 'No row result.'

C# Code

var res = nodeSession.SQL("CALL my_proc()").Execute();

if (res.HasData)
{

  var row = res.FetchOne();
  if (row != null)
  {
    Console.WriteLine("List of row available for fetching.");
    do
    {
      PrintResult(row);
    } while ((row = res.FetchOne()) != null);
  }
  else
  {
    Console.WriteLine("Empty list of rows.");
  }
}
else
{
  Console.WriteLine("No row result.");
}

Java Code

SqlResult res = nodeSession.sql("CALL my_proc()").execute();

if (res.hasData()){

  Row row = res.fetchOne();
  if (row != null){
    print("List of row available for fetching.");
    do {
      System.out.println(row);
    } while ((row = res.fetchOne()) != null);
  }
  else{
    System.out.println("Empty list of rows.");
  }
}
else {
  System.out.println("No row result.");
}

C++ Code

SqlResult res = nodeSession.sql("CALL my_proc()").execute();

if (res.hasData())
{
  Row row = res.fetchOne();
  if (row)
  {
    cout << "List of row available for fetching." << endl;
    do {
      cout << "next row: ";
      for (unsigned i=0 ; i < row.colCount(); ++i)
        cout << row[i] << ", ";
      cout << endl;
    } while ((row = res.fetchOne()));
  }
  else
  {
    cout << "Empty list of rows." << endl;
  }
}
else
{
  cout << "No row result." << endl;
}

It is an error to call either fetchOne() or fetchAll() when hasResult() indicates that a SqlResult is not a data set.

MySQL Shell JavaScript Code

function print_result(res) {
  if (res.hasData()) {
    // SELECT
    var columns = res.getColumns();
    var record = res.fetchOne();

    while (record){
      for (index in columns){
        print (columns[index].getColumnName() + ": " + record[index] + "\n");
      }

      // Get the next record
      record = res.fetchOne();
    }

  } else {
    // INSERT, UPDATE, DELETE, ...
    print('Rows affected: ' + res.getAffectedRowCount());
  }
}

print_result(nodeSession.sql('DELETE FROM users WHERE age > 40').execute());
print_result(nodeSession.sql('SELECT * FROM users WHERE age = 40').execute());

MySQL Shell Python Code

def print_result(res):
  if res.hasData():
    # SELECT
    columns = res.getColumns()
    record = res.fetchOne()

    while record:
      index = 0

      for column in columns:
        print "%s: %s \n" % (column.getColumnName(), record[index])
        index = index + 1

      # Get the next record
      record = res.fetchOne()

  else:
    #INSERT, UPDATE, DELETE, ...
    print 'Rows affected: %s' % res.getAffectedRowCount()


print_result(nodeSession.sql('DELETE FROM users WHERE age > 40').execute())
print_result(nodeSession.sql('SELECT * FROM users WHERE age = 40').execute())

C# Code

private void print_result(SqlResult res)
{
  if (res.HasData)
  {
    // SELECT
  }
  else
  {
    // INSERT, UPDATE, DELETE, ...
    Console.WriteLine("Rows affected: " + res.RecordsAffected);
  }
}

print_result(nodeSession.SQL("DELETE FROM users WHERE age > 40").Execute());
print_result(nodeSession.SQL("SELECT COUNT(*) AS oldies FROM users WHERE age = 40").Execute());

Java Code

private void print_result(SqlResult res) {
  if (res.hasData()) {
    // SELECT
  } else {
    // INSERT, UPDATE, DELETE, ...
    System.out.println("Rows affected: " + res.getAffectedRowsCount());
  }
}

print_result(nodeSession.sql("DELETE FROM users WHERE age > 40").execute());
print_result(nodeSession.sql("SELECT COUNT(*) AS oldies FROM users WHERE age = 40").execute());

C++ Code

void print_result(SqlResult &&_res)
{
  // Note: We need to store the result somewhere to be able to process it.

  SqlResult res(std::move(_res));

  if (res.hasData())
  {
    // SELECT
    std::list<Column> columns = res.getColumns();
    Row record = res.fetchOne();

    while (record)
    {
      for (unsigned index=0; index < columns.size(); ++inded)
      {
        cout << columns[index].getColumnName() << ": "
             << record[index] << endl;
      }

      // Get the next record
      record = res.fetchOne();
    }

  }
  else
  {
    // INSERT, UPDATE, DELETE, ...
    // Note: getAffectedRowCount() not yet implemented in Connector/C++.
    cout << "No rows in the result" << endl;
  }
}

print_result(nodeSession.sql("DELETE FROM users WHERE age > 40").execute());
print_result(nodeSession.sql("SELECT * FROM users WHERE age = 40").execute());

Calling a stored procedure might result in having to deal with multiple result sets as part of a single execution. As a result for the query execution a SqlResult object is returned, which encapsulates the first result set. After processing the result set you can call nextResult() to move forward to the next result, if any. Once you advanced to the next result set, it replaces the previously loaded result which then becomes unavailable.

MySQL Shell JavaScript Code

function print_result(res) {
  if (res.hasData()) {
    // SELECT
    var columns = res.getColumns();
    var record = res.fetchOne();

    while (record){
      for (index in columns){
        print (columns[index].getColumnName() + ": " + record[index] + "\n");
      }

      // Get the next record
      record = res.fetchOne();
    }

  } else {
    // INSERT, UPDATE, DELETE, ...
    print('Rows affected: ' + res.getAffectedRowCount());
  }
}


var res = nodeSession.sql('CALL my_proc()').execute();

// Prints each returned result
var more = true;
while (more){
  print_result(res);

  more = res.nextDataSet();
}

MySQL Shell Python Code

def print_result(res):
  if res.hasData():
    # SELECT
    columns = res.getColumns()
    record = res.fetchOne()

    while record:
      index = 0

      for column in columns:
        print "%s: %s \n" % (column.getColumnName(), record[index])
        index = index + 1

      # Get the next record
      record = res.fetchOne()

  else:
    #INSERT, UPDATE, DELETE, ...
    print 'Rows affected: %s' % res.getAffectedRowCount()

res = nodeSession.sql('CALL my_proc()').execute()

# Prints each returned result
more = True
while more:
  print_result(res)

  more = res.nextDataSet()

C# Code

var res = nodeSession.SQL("CALL my_proc()").Execute();

if (res.HasData)
{
  do
  {
    Console.WriteLine("New resultset");
    while (res.Next())
    {
      Console.WriteLine(res.Current);
    }
  } while (res.NextResult());
}

Java Code

SqlResult res = nodeSession.executeSql("CALL my_proc()");

C++ Code

// Handling of multiple result sets not yet implemented in Connector/C++

When using Node.js individual rows are returned to a callback, which has to be provided to the execute() method. To identify individual result sets you can provide a second callback, which will be called for meta data which marks the beginning of a result set.

Node.js JavaScript Code

var resultcount = 0;
var res = nodeSession.sql('CALL my_proc()').execute(function (
  function (row) {
    console.log("Row: ", row);
  }, function (meta) {
    resultcount++;
    cosole.log("Begin of result set number ", resultcount);
  }
);

The number of result sets is not know immediately after the query execution. Query results may be streamed to the client or buffered at the client. In the streaming or partial buffering mode a client cannot tell whether a query will emit more than one result set.

9.7 Working with Metadata

Results contain metadata related to the origin and types of results from relational queries. This metadata can be used by applications that need to deal with dynamic query results or format results for transformation or display. Result metadata is accessible via instances of Column. An array of columns can be obtained from any RowResult using the getColumns() method.

For example, the following metadata is returned in response to the query SELECT 1+1 AS a, b FROM mydb.some_table_with_b AS b_table.

Column[0].databaseName = NULL
Column[0].tableName = NULL
Column[0].tableLabel = NULL
Column[0].columnName = NULL
Column[0].columnLabel = "a"
Column[0].type = BIGINT
Column[0].length = 3
Column[0].fractionalDigits = 0
Column[0].numberSigned = TRUE
Column[0].collationName = "binary"
Column[0].characterSetName = "binary"
Column[0].padded = FALSE

Column[1].databaseName = "mydb"
Column[1].tableName = "some_table_with_b"
Column[1].tableLabel = "b_table"
Column[1].columnName = "b"
Column[1].columnLabel = "b"
Column[1].type = STRING
Column[1].length = 20 (e.g.)
Column[1].fractionalDigits = 0
Column[1].numberSigned = TRUE
Column[1].collationName = "utf8mb4_general_ci"
Column[1].characterSetName = "utf8mb4"
Column[1].padded = FALSE

9.8 Support for Language Native Iterators

All implementations of the DevAPI feature the methods shown in the UML diagram at the beginning of this chapter. All implementations allow result set iteration using fetchOne(), fetchAll() and nextResult(). In addition to the unified API drivers should implement language native iteration patterns. This applies to any type of data set (DocResult, RowResult, SqlResult) and to the list of items returned by fetchAll(). You can choose whether you want your X DevAPI based application code to offer the same look and feel in all programming languages used or opt for the natural style of a programming language.