Symbian
Symbian Developer Library

SYMBIAN OS V9.4

Feedback

[Index] [Previous] [Next]


How to use SQL

[Top]


Coding SQL queries

To query an existing database which has already been populated, create an RSqlDatabase object and call its RSqlDatabase::Open() function. Once you have finished querying, close the database by calling RSqlDatabase::Close().

_LIT(KCountryDb,"countries.db")

RSqlDatabase countriesDatabase;
TInt err;
err = countriesDatabase.Open(KCountryDb);
// code for SQL queries here
countriesDatabase.Close();

See also: Creating and managing a database.

[Top]


Example 1: Selecting a row from a table

A SELECT query passes data to the database and also retrieves data. Our example query

"SELECT name FROM countries WHERE population > :Value;"

interrogates a data table 'countries' for the data in the 'names' column if the value of the 'population' column in the current row is greater than the value of the passed-in parameter ':Value'. You therefore have to

Here is some example code.

_LIT(kQueryString,"SELECT name FROM countries WHERE population > :Value";);
_LIT(kName,"name");
_LIT(kPopulation,"population");
_LIT(kVal,":Value");

RSqlStatement myStatement;
TInt err;
err = myStatement.Prepare(countriesDatabase,kQueryString); // prepare
TInt nameIndex = myStatement.ColumnIndex(kName); // index
TInt populationIndex = myStatement.ColumnIndex(kPopulation);
TInt parameterIndex = myStatement.ParameterIndex(kVal);
err = myStatement.BindInt(parameterIndex,10); // bind
err = myStatement.Next(); // execute
TPtrC myData;
myData = myStatement.ColumnTextL(nameIndex); // read return data
err = myStatement.Close(); // close 

[Top]


Example 2: Inserting a row into a table

An INSERT query passes data to the database but does not retrieve data. This query inserts a new entry into our example 'countries' table.

INSERT INTO countries (name, population) VALUES (:value1, :value2);

In this case you need to perform fewer operations than in the previous example. Since we are not going to read data from a column we do not need to bind to it or use a read function. You need to:

Note the use of RSqlStatement::BindText() to bind the text string "Italy" to its parameter and the use of RSqlStatement::Exec() to execute a statement which returns no data. Here is some example code.

_LIT(kQueryString,"INSERT INTO countries (name, population) VALUES (:value1, :value2);");
_LIT(kValue1,":value1);
_LIT(kValue2,":value2");
_LIT(kItaly,"Italy");

RSqlStatement myStatement;
TInt err;
err = myStatement.Prepare(countriesDatabase,kQueryString); // prepare
TInt index1 = myStatement.ParameterIndex(kValue1); // index
TInt index2 = myStatement.ParameterIndex(kValue2);
err = myStatement.BindText(index1,kItaly);
err = myStatement.BindInt(index2,59); // bind
err = myStatement.Exec(); // execute
err = myStatement.Close(); // close

You perform SQL queries containing UPDATE commands according to the same pattern as INSERT statements.

[Top]


Example 3: Deleting rows from a table

The simplest SQL statements to execute are DELETE statements because they neither pass nor return data. In fact, DELETE statements are so simple that you do not need to create an RSqlStatement to run them. The only thing you have to do is execute the query and you can do this with just the RSqlDatabase object, which has its own RSqlDatabase::Exec() function taking a query string as its parameter.

DELETE FROM countries WHERE population < 2;

If our database object 'countriesDatabase' is open, the code to execute a DELETE statement is this.

_LIT(kQueryString,"DELETE FROM countries WHERE population < 2;");
countriesDatabase.Exec(kQueryString);

It is possible to call a DELETE statement from an RSqlStatement object but it is computationally more expensive and you are recommended not to do so.

[Top]


Example 4: Selecting several rows from a table

If you want a SELECT to return several rows from a data table, you must execute it within a loop. This example uses the same query as in Example 1 and consists of similar C++ code. The difference is that you call RSqlStatement::Next() as the condition of a while loop: the loop terminates when RSqlStatement::Next() fails to return data. In the body of the loop you place the call to the RSqlStatement::ColumnText() function, which now reads a different row of data for each call to RSqlStatement::Next().

_LIT(kQueryString,"SELECT name FROM countries WHERE population > :Value;");
_LIT(kName,"name");
_LIT(kPopulation,"population");
_LIT(kVal,":Value");

RSqlStatement myStatement;
TInt err;
err = myStatement.Prepare(countriesDatabase,kQueryString); // prepare
TInt nameIndex = myStatement.ColumnIndex(kName); // index
TInt parameterIndex = myStatement.ParameterIndex(kVal);
err = myStatement.BindInt(parameterIndex,10); // bind
while((err = myStatement.Next()) == kSqlAtRow) // execute
       {
       myData = myStatement.ColumnText(nameIndex); // read return data
       // do something with myData
       }
err = myStatement.Close(); // close 

[Top]


Example 5: Inserting several rows into a table

If you want to add several rows to a data table you call an INSERT statement within a loop. In this example we insert rows containing the numbers 1 to 10 into our countries table. This time the call to the bind function takes place inside the body of the loop because we are writing the loop counter to the data table. The execute function is RSqlStatement::Exec() as in example 2. After each call to RSqlStatement::Exec() you must call RSqlStatement::Reset() to reverse the effect of the bind function before the loop iterates. In the following example, error checking has been omitted for the sake of simplicity: in practice, each value of the variable err would be subject to further checking.

_LIT(kQueryString,"INSERT INTO countries (rank) VALUES (:value1);");
_LIT(kValue,":value1");

RSqlStatement myStatement;
TInt err;
err = myStatement.Prepare(countriesDatabase,kQueryString); // prepare
TInt index1 = myStatement.ParameterIndex(kValue1); // index
for(TInt i=1;i<=10;i++)
       {
       err = myStatement.BindInt(index1,i);
       err = myStatement.Exec(); // execute
       err = myStatement.Reset()
       }
err = myStatement.Close(); // close

[Top]


Handling large data values

Our example SELECT statement retrieved integer data and held it in a TInt variable. But a data table field often contains large amounts of data which cannot be held in a variable: instead we have to hold it in buffers, pointers to memory or data streams.

[Top]


Example 6: Reading to a buffer

To hold data in a buffer you

RSqlStatement myStatement;
TInt err;
...
while((err = myStatement.Next()) == KSqlAtRow)
    {
    RBuf myBuffer; 
    err = myBuffer.CreateL(myStatement.ColumnSize(columnIndex));
    err = myStatement.ColumnBinary(myColumnIndex,myBuffer);
    ...
    // process data
    myBuffer.Close()
    }

[Top]


Example 7: Reading to memory

You can avoid copying to a buffer by declaring a TPtrC object pointing to an area in memory and holding the data there without making a local copy.

RSqlStatement myStatement;
TInt err;
...
while((err = myStatement.Next()) == KSqlAtRow)
    {
    TPtrC myData;
    TInt err;
    err = myStatement.ColumnBinary(myColumnIndex,myData);
    // process data
    }

[Top]


Example 8: Reading to a data stream

You can read data to a data stream by declaring an RSqlColumnReadStream object and calling RSqlColumnReadStream::ColumnBinary().

RSqlStatement myStatement;
TInt err;
TInt myColumnIndex;
RSqlColumnReadStream myStream;
...
// Prepare an SQL statement that references large binary column data
...
CleanupClosePushL(myStream);
while((err = myStatement.Next()) == KSqlAtRow)
         {
        User::LeaveIfError(myStream.ColumnBinary(myStatement,myColumnIndex));
        TInt size = myStatement.ColumnSize(myColumnIndex);
        ...
        // Read the column data into a buffer in one chunk. In practice
        // you might want to retrieve the data in smaller portions.
        RBuf buf;
        buf.CreateL(size);
        CleanupClosePushL(buf);
        myStream.ReadL(buf,size);
        ...
         // Process data
        ...
        // Close the buffer
         CleanupStack::PopAndDestroy();
        }
// Close the stream
    CleanupStack::PopAndDestroy();

[Top]


Example 9: Writing from a data stream

Data streams can also be used to write to a database. You declare an RSqlParamWriteStream object and call one of its bind functions.

RSqlStatement myStatement;
TInt err;
TInt myParameterIndex;
RSqlParamWriteStream myStream;
RBuf buf;
...
// Prepare an SQL statement that references a large text parameter
...
CleanupClosePushL(myStream);
while((err = myStatement.Next()) == KSqlAtRow)
        {
        User::LeaveIfErrormyStream.BindTextL(myStatement,myParameterIndex));
        ...
        // ensure that buf has appropriate data
        ...
        // write out the parameter data
        myStream.WriteL(buf);
        ...
        // Commit the stream
        myStream.CommitL();
        }
// Close the stream        
CleanupStack::PopAndDestroy();

[Top]


Example 10: Scalar queries

A scalar query is a SELECT statement which refers to several data fields but returns a single result, for instance a COUNT query on a database column. Symbian OS provides a class TSqlScalarFullSelectQuery, which you are recommended to use to make scalar queries; this is more efficient than RSqlStatement.

TSqlFullSelectQuery has the peculiarity that a class instance is declared with a parameter that refers to the database to be queried. The class supplies a number of functions taking SELECT statements as parameters: which one you use depends on the data type of the return value.

RSqlDatabase myDatabase;
_LIT(kQueryString,"SELECT COUNT (*) FROM myTable");
...
TSqlScalarFullSelectQuery myFullSelectQuery(myDatabase);
TInt recCount = myFullSelectQuery.SelectIntL(kQueryString));

[Top]


Creating and managing a database

To create a database you declare an RSqlDatabase object and call its Create() function with an appropriate name as argument. This enables you to use the RSqlDatabase object as a handle to the actual database. The procedure for creating, opening and closing a database is slightly different for secure and non-secure databases.

[Top]


Creating, opening and closing a non-secure database

To create a non-secure database you declare an RSqlDatabase object and call RSqlDatabase::Create() with the database name as argument. An optional second parameter, if specified, should point to a descriptor containing information about configuration. A non-secure database is created open: as soon as it is created you can start passing SQL statements to it. You close a database with its Close() function and reopen it with its Open() function.

RSqlDatabase myDatabase;
_LIT(kPath,"ExampleDatabase.db");
myDatabase.Create(kPath,"");
// code to populate the database here
myDatabase.Close();
myDatabase.Open();
// code to query or update the database here
myDatabase.Close();

[Top]


Creating a security policy for your database

A database security policy is a specification of user permissions on a database. It is held in an RSqlSecurityPolicy object which you must create before the database which it refers to.

To create a database security policy you must first create a general security policy, a TSecurityPolicy object which comes with null default values. The TSecurityPolicy object is passed to the Create() function of the RSqlSecurityPolicy object and this in turn is passed to the Create() function of the database object. Between the calls to the two Create() functions you have to set the required permissions on the RSqlSecurity policy object. There are three kinds of permissions and each one requires you to declare a separate TSecurityPolicy object, so that you make up to four such declarations in all.

The three kinds of database permissions are 'read', 'write' and 'schema', and they apply to the entire database not to individual tables. The 'schema' permission allows you to create and drop database tables, while the other two allow you to read from and write to existing tables only. They are represented by the three constants

The first argument of RSqlSecurityPolicy::SetDBPolicy() is one of these constants; the second argument is the associated TSecurityPolicy object. It is this function which must be called to set permissions between the calls to the two Create() functions of the RSqlSecurityPolicy and RSqlDatabase objects. A security policy once set cannot be changed, but it can be retrieved using the GetSecurityPolicy() function of the database object.

[Top]


Creating a secure database

The example code for creating a secure database with read, write and schema permissions looks like this.

TSecurityPolicy defaultPolicy;
RSqlSecurityPolicy securityPolicy;
RSqlDatabase database;
TInt err;
securityPolicy.Create(defaultPolicy);
TSecurityPolicy schemaPolicy;
TSecurityPolicy writePolicy;
TSecurityPolicy readPolicy;

err=securityPolicy.SetDbPolicy(RSqlSecurityPolicy::ESchemaPolicy, schemaPolicy);
// <check for error>
err=securityPolicy.SetDbPolicy(RSqlSecurityPolicy::EWritePolicy, writePolicy);
// <check for error>
err=securityPolicy.SetDbPolicy(RSqlSecurityPolicy::EReadPolicy, readPolicy);
// <check for error>
err=database.Create(kDatabaseName, securityPolicy);
// <check for error>
securityPolicy.Close();

[Top]


Other database operations

To manage your databases you also need to know how to attach, detach, copy and delete them.

[Top]


Attaching and detaching databases

Attaching a database means making it appear to be part of another database, which is designated the primary database, so that a single SQL statement can be used to query them both. Attachment is a virtual operation which does not modify the file system: each database retains its own configuration and security policy. A primary database can have several attached databases (Symbian OS allows a maximum of 10). An attach operation is reversed by a detach operation. The primary database must be opened before any attachment takes place and must not be closed until all other databases have been detached from it.

You attach a database by calling the RSqlDatabase::Attach() function of the primary database with two parameters: the file name and logical name of the attached database. The logical name is a name of your choice and is used in SQL statements as a prefix to the table names of the attached database. For instance a table 'userID' in an attached database with the logical name 'userDB' is accessed as 'userDB.userID' (a query on 'userID' will also succeed but only if the primary database has no table of that name). If you query the primary database, it will now appear to contain all its own data and also the data held in the attached database. You perform a detachment by calling the RSqlDatabase::Detach() function of the primary database with the logical name of the attached database as its parameter. The example code for attaching and detaching a database looks like this.

myDatabase.Open();
_LIT(kFileName,"T:\DatabaseFolder\ExampleDatabase.db")
_LIT(kLogicalName,"myAttachedDatabase")
myDatabase.Attach(kFileName,kLogicalName);
// code to query the combined databases here
myDatabase.Detach(kLogicalName);
myDatabase.Close()

[Top]


Copying and deleting databases

A database object of the class RSqlDatabase has functions RSqlDatabase::Copy() and RSqlDatabase::Delete(). Both are static functions: that is, they are not used to copy or delete the calling database object but the one supplied as a parameter of the function. Copy() takes two parameters, the source database file name and the target database file name. Either both databases must be secure or both must be non-secure. If they are both non-secure then there are no restrictions on copying. However, if they are both secure, only the application which created the source database may copy it to the target.

[Top]


Database configuration

You can configure a database using a string called a configuration descriptor: this is an example of one.

page_size=1000;cache_size=1024;encoding=UTF-8

A configuration descriptor contains three parameters. The page size is the size of the memory pages to be used and must be a multiple of 512 up to a maximum of 4096. The cache size is the size of the cache expressed as the number of pages. The encoding is either UTF-8 or UTF-16. If a database is not explicitly configured it takes a default configuration. A configuration descriptor may be used as the last argument of the Create() function of a database object, in which case it overrides the default configuration. It may also be used as the last parameter of the Open() function of a database object, but only to modify the cache size: the page size and encoding should be omitted.

[Top]


Isolation level

Several database objects may be connected to the same physical database at the same time. While a write statement is modifying data, the data is said to be uncommitted until the statement has finished execution, at which point it becomes committed. If at the same time a separate read statement is retrieving uncommitted data there is a risk that it will return invalid or inconsistent query results. By default, a write statement blocks all other read and write statements from executing until the data is committed. However, it is sometimes desirable to override this provision in order to enhance the performance of a client application. In this case you alter what is called the isolation level of the client database object by calling its RSqlDatabase::SetIsolationLevel() function. This function has two possible parameters: the constant EReadUncommitted which allows you to read uncommitted data, and the constant ESerializable which restores the default isolation level.

[Top]


Memory management

Databases manage the problem of data corruption by logging the transactions taking place, and the log files occupy memory. This has the consequence that a DELETE statement might temporarily increase and not decrease the amount of disk space being used. To ensure that sufficient memory is available for DELETE to free up more memory, an RSqlDatabase object has memory management functions. They are ReserveDriveSpace(), FreeReservedSpace(), GetReserveAccess() and ReleaseReserveAccess().

[Top]


Error codes

Identifier

Value

KSqlErrGeneral

-311

KSqlErrInternal

-312

KSqlErrPermission

-313

KSqlErrAbort

-314

KSqlErrBusy

-315

KSqlerrLocked

-316

KSqlErrNoMem

-317

KSqlErrReadOnly

-318

KSqlErrInterrupt

-319

KSqlErrIO

-320

KSqlErrCorrupt

-321

KSqlErrNotFound

-322

KSqlErrFull

-323

KSqlErrCantOpen

-324

KSqlErrProtocol

-325

KSqlErrEmpty

-326

KSqlErrSchema

-327

KsqlErrTooBig

-328

KSqlErrConstraint

-329

KSqlErrMismatch

-330

KSqlErrMisuse

-331

KSqlErrNoLFS

-332

KSqlErrAuthorization

-333

KSqlErrFormat

-334

KSqlErrRange

-335

KSqlErrNotDb

-336

KSqlErrStmtExpired

-360

[Top]


Data types

Data type Symbian OS data type Bind function Column function

Integer

ESqlInt

BindInt()

ColumnInt()

64 bit integer

ESqlInt64

BindInt64()

ColumnInt64()

Real

ESqlReal

BindReal()

ColumnReal()

Text

ESqlText

BindText()

ColumnText()

Binary

ESqlBinary

BindBinary()

ColumnBinary()

Null

ESqlNull

[Top]


SQL keywords used in Symbian OS

These are the SQL keywords which may be used with an RSqlStatement object. Symbian OS does not implement the PRAGMA command. The ATTACH and DETACH commands are deprecated in Symbian OS in favour of the database class functions explained earlier. Statements containing the ATTACH and DETACH commands will fail if applied to a secure database.

[Top]


Data definition language (DDL)

[Top]


Data modelling language (DML)

[Top]


Other keywords