|
||
This is a guide to the CommsDat interface. It is also a migration guide for Commdb users. It is intended for use by all Commdb and CommsDat users.
The comms database is a persistent store of the data required to configure comms components or to make connections with remote servers. The data is held in a small set of related tables persisted by a storage server.
The interface to comms data on the device is the CommsDat DLL, which is loaded by a client process and interacts with the data storage server. The storage server API is hidden from the client by an abstract database interface in CommsDat. The storage server in use at the moment is the Central Repository.
CommsDat presents comms configuration data to the client via a set of interface classes that represent the tables in the database. The format of the interface data set does not need to exactly match the format of the data stored on the device, as CommsDat will map between the two schemas. More than one version of the interface classes can be available at one time and CommsDat will map from each different version to the single data format stored on the device. This allows flexibility in data management on the device and protects external clients from backward compatibility issues when changes are required to the data set used by the comms stack.
Comms configuration data is stored in a small database of related tables. Each entry, or Element, in the database consists of:
a unique Id showing the Elements location in the database and expressing its meaning in the database schema as a specific Table, Column, Record or Field
a set of Attributes governing data access control
a value of known type that may be a single numeric, Boolean or descriptor setting or a composite value of one or more nested Elements
The CommsDat API provides functions for accessing and manipulating comms configuration data stored in the comms database.
The API has the following main features:
Data Container API - An abstract interface for data manipulation and caching in the client process. This is a suite of Container classes representing fields, records and tables of data stored in the database.
Data Storage API - An abstract interface for interaction with a storage server that manages persistence of the Comms database.
Versioned Data Set - A specialised set of CommsDat Container classes presenting the current set of comms configuration data to the user in a convenient way. New sets are introduced from time to time as changes to the underlying comms functionality require. One or more deprecated client-side versions of the data set may be supported along with the current version. Deprecated versions are removed when it is commercially convenient to all parties.
Utilities - A small set of related utility classes and functions.
The CommsDat module is a DLL loaded by a client process. It is a mapping and validating interface to the comms database held in a data server. CommsDat is the only interface to the comms database for all comms data users. It maps between one or more client-side representations of the data and the native stored data set.
In addition to an abstract data interface, CommsDat presents an abstract data storage API that does not reveal the API of the data storage server (currently the Central Repository). So CommsDat clients are shielded from future changes in both the comms data format and the comms data storage mechanism.
Beneath the CommsDat API is a mapping layer that transfers data between the set of client-side data formats and the single stored data format, and a validation layer that ensures the integrity of the dataset on the device. Below that is the interface with the storage server.
The following diagram sketches CommsDats structure and its relationship with the client process, the storage server and comms configuration data. Major message flows are shown by dark arrows. Structural design relationships are indicated by dashed lines.
CommsDat is a replacement for the CommDb component. The CommsDat API is new, but all central CommDb functionality is still provided by CommsDat.
There are some behavioural differences. In particular, the Commdb
SQL API has been removed. The SQL API in CommDb was mainly used to select views
of database tables matching particular field values and to create new
user-defined tables in the database at run time. This functionality is
retained. Selection of sets of records can be performed by pattern matching
fields in a primed Container class with records in the database. New tables can
be defined and stored with the CMDBGenericRecord
class.
During the migration period a shim component will be available in place of CommDb. The CommDbShim presents the CommDb API and behaviour unchanged except for the removal of the SQL API. The shim interacts with CommsDat to access the comms database.
Section 9 below discusses issues in the migration and recommends ways to make the most efficient use of CommsDat.
Comms data is stored in table format. A Table has a name and a numeric Id and defines a set of named and numbered Columns of values of a specific data type. Data Fields are stored under these Columns and grouped together into Records. A group of CommsDat Records of the same Table type is referred to as a Table or a Record Set.
Each Table, Record, Column or Field is referred to as a CommsDat Element and has an Id, a set of access control Attributes and a Value of a particular type. Within CommsDat these are presented by a set of classes that are described here.
CMDBElement
is the base class that defines all
Elements that can be stored in the database. The class and the set of
Containers that derive from it encapsulate the three components of an Element -
its Id, access control attributes and its value.
CMDBElement
inherits from the class
that provides an abstract database
access interface to interact with the data in the storage server. The
MMetaDatabase
MMetaDatabase
interface is described in
section 5 below.
There are several specialised Container classes representing Tables,
Records and Fields that derive from CMDBElement
. These classes
present the client with a view or cache of data for a specific Element in the
Comms Database. Container classes are discussed in
section 4 below.
The CMDBElement
class provides functions to manipulate
the Element Id and Attributes of an Element
CMDBElement
functions allow manipulation of the
Element Id, which is expressed as a class
TMDBElementId
This describes the Elements
TypeId - The Table and Column id of the Element
RecordId - The Record id of the Element
The location of an Element in the database schema is identified by its Element Id. Thus the meaning of an Element in the database schema is expressed by the Element Id.
The numeric structure of an Element Id is internal to the CommsDat
component and this implementation detail of the component is not part of the
published API of CommsDat. A set of public constants defined in
CommsDat.h
is provided to enable clients to understand and
manipulate Element Ids.
CMDBElement
presents functions to get, change or query
access control Attribute flags for the Element. Attributes are expressed using
the class
TMDBAttributeFlags
The value of an Element is stored in
class, but is only accessible via
derived Container classes. Lookup tables holding schema details map an Elements
Id with its value type.
CMDBElement
Data in the comms database is protected by platform security capabilities that are policed by the data storage server. Depending on assigned capabilities, a client process has read or read-write access to some or all of the Elements in the database.
The current platform security schema is as follows
Default read capability |
|
Default write capability |
|
Reading (or writing) private user data such as user names or passwords |
|
Writing protected data such as an Operator-set ISP record |
|
Clients with the appropriate capability have the ability to change the access control Attributes of comms data Elements. Symbian OS relies on trusted processes with access capabilities to adhere strictly to data access control protocols.
Processes with no capabilities cannot write to the database and so
cannot disturb the data integrity of the Comms Data Set either via the CommsDat
API or by attempting to manipulate data directly in the storage server. A
client must also have WriteDeviceData
capability to use database
functions such as opening a transaction to write to the database. The platform
security schema ensures that clients with no capability cannot deny usage of
the database to other clients.
The access level of an Element is indicated by the set of
Attribute flags that are set against it. All Attributes can only be set or
cleared in the database by clients with the WriteDeviceData
platform security capability at a minimum and some require additional
capabilities.
The enumeration
TCDAttributeFlags
declared in CommsDat.h
defines four access
control attribute flags.
|
Basic read only guard included for backwards compatibility with CommDb. No additional platform security for this attribute. |
|
Specifies whether or not to display this Element. No additional platform security for this attribute. |
|
Hides private user data such as username or password
fields. The |
|
Protects Elements from being written by most processes.
Set and cleared as part of Commdb Protection API. The
|
The ECDHidden
and
ECDNoWriteButDelete
attributes are not enforced by additional
platform security other than the default WriteDeviceData
that
guards all write operations. The ECDNoWriteButDelete
flag is
considered obsolete but is included for backwards compatibility with Commdb
settings.
Platform security settings in the storage server are
cumulative, so successfully updating a Private and Protected Element in the
database would require the setting of the ECDPrivate
and
ECDWriteProtected
flags in the CommsDat Element Container class;
and would depend on the client process having WriteDeviceData
,
ReadDeviceData
and NetworkControl
capabilities when
it called the database storage function.
Attribute flags set the access control level required by each
Element stored in the database, but the access control permissions that a
CommsDat client wants to use are set using the
SetAttributeMask
in the CMDBSession
class found in metadatabase.h
.
To remove a viewing or writing restriction required by an
Attribute Flag on an Element without changing the Elements access control level
for all users, a client should Set the Attribute Mask for that Flag. So, to
view hidden records, a client should set the attribute mask for
. This will make all Elements with the
ECDHidden
ECDHidden
flag visible to the client during that session. A mask
can be cleared again using the ClearAttributeMask
function.
Setting an Attribute Mask does not remove platform security
checks, however. So, for example, a client process without
NetworkControl
capability would never be able to write to Elements
protected with ECDProtectedWrite
, even if the mask were set for
this flag via the session API. Setting the Attribute Mask does not make a call
to the database server or check client capabilities and the function will not
fail.
The default mask settings ensure that every access control attribute restriction is obeyed.
See section 5 for more information about the Attribute Mask functions.
CommsDat introduces the possibility of Comms Data Set interface versioning that was not available with CommDb.
Allowing multiple versions of client-side data set protects clients from backward compatibility issues inherent in inevitable periodic changes to comms configuration information. New data set versions may be introduced from time to time, in which case older data set versions will be deprecated, but supported for a managed period of time.
To allow preparation, storage and manipulation of comms data in the
client process, CommsDat provides a set of Container classes. Each of the
Container classes represents an Element in the database and inherits from
CMDBElement
, which in turn inherits from the database access
interface class, MMetaDatabase
to allow easy interaction
with the database. This is a summary of the container classes and their
function
Representation of Comms data records:
|
a base class to represent any record |
|
a base class for records with named field members. Inherits from
|
|
a class representing a record from a table that is defined by the
client at run time and not known at compile time to CommsDat. Inherits from
|
|
a base class for |
|
This class expresses a soft-link from a field in one record to a
record of a different type |
Representation of a Comms Data Table
|
An array of records where T is the record type. This represents a Table in a database. |
Representation of Comms data field
|
A set of base class for fields of different basic types. |
|
where |
All function calls in the CMDBElement
class and
the Containers that inherit from it perform actions local to the Containers
themselves and the data they encapsulate. None of these functions interacts
with other mapping or validation classes inside CommsDat or with the underlying
database.
To use the mapping, validation and database access functionality in
CommsDat a client must call functions from the MMetaDatabase
interface.
All CommsDat Container objects should be created on the Heap and deleted explicitly as they inherit from CBase.
CMDBRecordBase
classes should always be created by a
call to the factory function
IMPORT_C static CMDBRecordBase* RecordFactoryL(TMDBElementId);
Or copied from another instance with the copy function
IMPORT_C static CMDBRecordBase* CreateCopyRecordL(
CMDBRecordBase&
aCopyFromRecord);
A Container class gives a view of part of the database and provides a cache of comms database information in the client process.
An overloaded conversion operator will automatically convert the
data value in a CMDBField
class to the templated type.
This allows a CMDBField
object to be used as the right-hand side
argument in assignment calls or to be passed directly into functions requiring
items of the templated type.
To check whether the field has a NULL value, either check the
IsNull()
function before using the conversion operator or use the
GetL()
call instead of the conversion operator. This will leave if
the field value is NULL.
The value of a field in a Container will be NULL if it has not yet been set by the caller and has not yet been loaded from the database. If it is NULL after being successfully loaded from the database a field has been explicitly set to NULL by the writer of the record.
Use the assignment operator to add data to a field. A field of type
T
will accept an item of type T
as the right-hand
argument of an assignment call.
Setting descriptors needs a further preparatory call to set the
size of the fields buffer. First call SetMaxLengthL()
with the
length of the string to be added and then call the assignment operator. Calling
the assignment operator for a descriptor field will have no effect unless the
buffer length has been set to an appropriate size.
Alternatively the SetL()
call can be used. This
function will calculate the length of a descriptor parameter for a descriptor
field type and perform the allocation and assignment together, leaving if there
is an error.
Data in a CCDRecordBase
Container is accessed
by interacting with each of the named member Fields individually as described
above. Fields in a CCDRecordBase
class are public and are normal
data members of the class.
Alternatively, fields can be retrieved using the following functions:
GetFieldByNameL
GetFieldByIdL
The GetFieldByNameL
function should be used as little
as possible as it has to use expensive descriptor comparison. It is provided
only for backward compatibility with the CommDb interface.
It is more efficient to use GetFieldByIdL
which looks
up Fields in a Record Container by their TypeId. It is most efficient to access
the field directly in a specialised CCDRecordBase
class where the
type of the Element is stated at compile time.
The CMDBGenericRecord
class that is used to
define and access user-defined Tables in the database cannot have named Field
members as the Field identity cannot be known at compile time. So for this
class, Field access is exclusively via the GetFieldByNameL
and
GetFieldByIdL
functions.
Unlike the CCDRecordBase
versions of these functions -
which make use of static lookup tables to iterate through the data field
members in the Record and to establish their Name, id or Field type - the
CMDBGenericRecord
versions rely on a Table schema supplied
explicitly at Run-time. A client can initialise the Container with a Table
Schema via the InitialiseL
function or the schema can be retrieved
from the database via LoadL
or FindL
functions.
In a Record Set ( CMDBRecordSet
),
CMDBRecordBase
classes are stored in a public
RArray
member and can be accessed using normal
RArray
interface functions.
A CMDBRecordLink
container is first an integer
field. The data and element id of the linking field can be accessed in the
normal way as for other CMDBField
classes.
The data in the Linked Record is accessed in the normal way via the member
CMDBRecordBase * iLinkedRecord
Sometimes it is known at compile time what Table Type this record will be, in which case a client can cast the record to the appropriate sub-class and access the data via member name.
When the type of the record is not known at compile time a client
should first establish the Element Id of the linked record and then perform an
appropriate down cast or simply access the data via the base class using
GetFieldByNameL
or GetFieldByIdL
.
A client may need to prepare an Element Container object before interaction with the database. This may involve setting field values, the Record Type Id or the Record Id and perhaps setting access control Attribute flags.
If data needs to be stored or modified in the database it must first be added or modified in the Container.
Using the FindL
function requires one or more Fields
in a Record to be primed with values to allow pattern matching with records in
the database see section 5.4.2 below on the FindL
function.
Field values do not need to be prepared before calls to
LoadL
, RefreshL
or DeleteL
.
The Table Id of an Element must always be given before any database
call. The Table Id must be known in every case except when asking to find a
user-defined Table given a Table name, in which case the Type Id should be
zero, or when attempting to store a new Table with a
CMDBGenericRecord
, in which case the Element Id should be
set to the constant KCDNewTableRequest
(Note: this
API is only available to device creators) and it will be then be
returned as a new value by CommsDat once the table has been created in the
database.
The Record Id of an Element is set with the
CMDBElement
function
SetRecordId()
The Record Id must always be set before all
MMetaDatabase
calls except FindL
, which matches
instantiated field values to establish the Record Id.
When storing a new record the client must either set the Record Id
explicitly (assuming it can establish an Id that does not already exist) or set
the Record Id to the constant KCDNewRecordRequest
which
will cause the new Record to be given the next available Record Id in the Table
when it is stored in the database.
Access control Attributes of an Element need only be set if they
are different from the default settings. If a record is hidden or protected,
this must be specified before any call except FindL
and
LoadL
.
Before a call to a MMetaDatabase
function, the client
must also ensure that the general attribute mask is appropriate for the desired
access level, by setting or clearing the attribute mask via calls in the
CMDBSession
instance. CommsDat will not read or write Elements
that have Attribute flags other than those explicitly Set in the Attribute
Mask.
A CommsDat Container should be regarded as a cache of database information in the client process. The Containers have been designed to make it easy for a client to retrieve just the data required from the database with minimal overhead in terms of memory and speed.
A developer should always use the smallest Container available that
will provide the information that is needed. Loading only one field from a
record where the Table, Column and Record id are known can be done using a
single CMDBField
Container. Similarly, it is not necessary to load
a full record set to access a single record.
Using linked records can reduce the amount of code required to access records across a set of linked tables.
Careful use of the FindL
function can restrict the size
of Record Sets retrieved from a stored Table to just the specific records
required by the client in the same way that a DBMS client can use an SQL Select
statement to restrict the size of a table view.
Many routines accessing the comms database have the goal of accessing
a single field, but need to access one or more records first to establish the
id of the target Field. In this kind of usage a Record Container may be needed
to do the FindL
call to establish the target Element Id, but the
target field itself can be accessed with a single CMDBField
class
primed with the retrieved Element Id.
To interact with the database, a session must be created via the class
CMDBSession
. The session object is then used as a handle
to a session with the data storage server when using database access functions
in the MMetaDatabase
class.
As comms data can be presented to CommsDat clients in different data set versions, CommsDat session creation requires the user to specify which version is needed.
A client willing to upgrade to new data set version as it becomes
available should create a session using the latest version as specified by the
KCDLatestVersion
constant in CommsDat.h
.
Clients that would prefer to continue to use a specific data set and need to be
protected from data compatibility changes in later versions should name a
version explicitly. CommsDat will then map stored data to and from that
representation, as long as the requested version is still supported in Symbian
OS.
CMDBSession
contains transaction functions to ensure
consistent reads and atomic data entry in the database.
OpenTransactionL
CommitTransactionL
RollbackTransactionL
A transaction can be opened in ReadOnly
or
ReadWrite
mode. The storage server enforces the transaction model.
Transaction behaviour is implemented in the Storage Server. The CommsDat interface guarantees only that
A successful read or read/write transaction will not have been interrupted by a write from another session.
A read/write transaction will be atomic, with all writes succeeding after a successful Commit or being completely discarded after a failed Commit or a Rollback call.
A client in a read/write transaction can view data modifications made during that transaction as though they have been successfully persisted to the database already.
No other CommsDat clients can view data modifications made during a transaction before that transaction is committed.
Other aspects of transaction behaviour such as the timing of the obtaining of a write lock during a read/write transaction or the reasons for transaction failure are implementation details of the storage server and should not be relied on as part of the CommsDat interface by CommsDat clients.
All MMetaDatabase
read and write functions are
performed using internal transactions and are therefore consistent and atomic
in their own right.
Clients should open write transactions sparingly and should be careful not to hold a transaction open for a long time it locks the database against writes by other sessions.
It can be more efficient to wrap several write operations inside one transaction as data only gets persisted on Commit, but clients should be careful not to hold a transaction open for longer than necessary.
MMetaDatabase
functions using linked records are
atomic across two or more tables without the need for a client to set a
transaction explicitly (see section
4 above).
As with Commdb, clients must ensure that they keep transactions as short as possible as the comms database is used by many other processes.
Very long running transactions may be terminated with a timeout in order to protect the Comms database from deadlock or a denial of service attack. This is a change in behaviour from CommDb. However, this timeout will not affect normal operation.
The CMDBSession functions
SetAttributeMask(TMDBAttributeFlags aAttributeFlags)
ClearAttributeMask(TMDBAttributeFlags aAttributeFlags)
allow the client to specify which of the access control Attribute flags they wish to enforce depending on the clients platform security capabilities. Any access control flag in an Element is ignored while the mask for it is Set and is obeyed while the mask for it is Cleared. If a client process asks to set or clear an access control flag that requires higher capabilities than it has been granted, the request itself will not fail (these calls do not go through to the storage server), but the request will not have any effect since access to the Element in question will be denied anyway by the policing storage server.
The Attribute Mask functions should be used to ask to show (or hide) Hidden records as for Commdb and can also be used to ask to ignore or obey read only but deletable Attributes.
The Attribute Mask functions can be used to ignore or obey the
read-only Protection flag that used to be set by the (private) Commdb
Protection API. To Protect or Unprotect a record is an operation reserved for a
small set of users and this functionality is guarded with
NetworkControl
capability. For users that do not have this
capability, setting or clearing the ECDProtectedWrite
flag
will have no effect as the Storage Server will always enforce platform security
settings on all write calls to Protected Elements.
To Protect a record a client should:
Create a Container of the appropriate type and set the Element and Record Id as necessary to identify the target Element in the database.
Set the Attribute Flag at the highest appropriate level in
the Container to ECDProtectedWrite
(this setting will then
propagate down through each Element in the Container)
Set the Attribute Mask in the session to
ECDProtectedWrite
Call ModifyL
on the Container (or
StoreL
if the element is being stored for the first time).
Clear the ECDProtectedWrite
Attribute Mask in
the session.
To unprotect an element the same procedure should be followed but the client should Clear the Attribute flag in the element at step 2.
Its not necessary to Load the data for an element from the
database before changing its Attributes with ModifyL.
The class MMetaDatabase
is an interface for
manipulating data within the database. It provides functions to read data (
LoadL
, FindL
, RefreshL
), to write data (
StoreL
, ModifyL
, DeleteL
) and functions
to register for notification of changes in the database.
All CommsDat Containers derive from the MMetaDatabase
class. So database access functionality is automatically built into each data
Container. Once data has been prepared in the Container, interaction with the
database is performed by calling one of the MMetaDatabase
functions available in the Container interface.
This section describes the operation of each
MMetaDatabase
function in turn.
Loads data from the database into the Container according to the specified Element Id.
This function loads the data for the requested Element from the
database into the Container. Only data that the client has capabilities to view
and that has had any read-protection cleared in the session's Attribute Mask
will be returned. LoadL
will overwrite any data that already
exists in the Container with data retrieved from the database.
The function will leave if CommsDat or the Storage Server
return errors. It will leave with KErrPermissionDenied
if
the client explicitly requests to load an Element for which it does not have
platform security capabilities, but will silently ignore inaccessible records
within a requested table or inaccessible fields within a requested record.
A LoadL
call will open a read-only transaction
with the storage server if the client is not already in a transaction and hence
can be guaranteed to be atomic and to give a consistent view of persisted data.
Find and load data from the database that matches values primed in the Container.
If a client has primed a Record Container with particular
values in one or more fields, FindL
will attempt to retrieve one
or more Records that have matching values in all of those fields.
FindL
is a Boolean function returning
ETrue
if at least one Element containing the requested data is
found or EFalse
if nothing is found. FindL
will not
return matching Elements that the client process does not have Platform
Security Capabilies to view, nor will matching Elements be returned that have
attributes not explicitly Set in the sessions Attribute Mask.
To find a single Record with particular values a client should
call FindL
on a primed CMDBRecordBase
or
CMDBField
class. The primed Container will be populated with
values from the first matching record found in the database and the Element Id
will be set as appropriate. On failure, the primed record will be left
unchanged.
To find all Records in a Table that match a primed Record, the
client should create a CMDBRecordSet
, append a single Record to
the record array, primed with appropriate field values, and call
FindL
on the whole CMDBRecordSet
instance. On
success, the function will return one or more completed records that contain
fields that match the primed fields. The initial primed record will be
overwritten by the first record found. On failure the primed record will be
left unchanged.
A FindL
call will open a read-only transaction
with the storage server if the client is not already in a transaction and hence
can be guaranteed to be atomic and to give a consistent view of persisted data.
RefreshL
updates the data in the client Container
and is similar to LoadL
except that it will not overwrite Fields
that have been temporarily modified by the client. Fields that have not been
directly modified by the client will be refreshed from the database. This
function is useful when maintaining a client-side cache of comms configuration
information.
RefreshL
will operate in a read-only transaction
with the storage server if the client is not already in a transaction and hence
can be guaranteed to be atomic and to give a consistent view of persisted data.
StoreL
will create new records, columns or tables
where required. Only fields that have been explicitly amended by a user will be
stored. The function will leave with KErrAlreadyExists
if
any field it attempts to create in the database already contains data.
The Element Id must be prepared by the client before
StoreL
is called. It should either be a full id or a request for a
new Record, Table or Column using one of the constants in
CommsDat.h
, in which case the correct Id will be returned by
CommsDat when the new element has been created in the database.
A call to StoreL
will fail if clients do not have
read and write capabilities to set any requested Attribute Flags or if the
appropriate Attribute Mask has not been set in the CDMBSession
.
StoreL
will open a read/write transaction with the
storage server if the client is not already in a transaction and hence can be
guaranteed to be atomic and to give a consistent view of persisted data.
Any error in the StoreL
function will fail the
transaction that is in progress.
ModifyL
will modify the value or the attributes of
the specified Element or set of Elements in the database.
ModifyL
will fail if clients do not have read and
write capabilities to set or clear the Attribute Flags in the element as
requested or if the appropriate Attribute Mask has not been set in the
CDMBSession
.
ModifyL
will not create new Records or Tables, but
will store new Fields within a record where they have not previously been
entered into the database.
ModifyL
will open a read/write transaction with
the storage server if the client is not already in a transaction and hence can
be guaranteed to be atomic and to give a consistent view of persisted data.
Any error in the ModifyL
function will fail the
transaction that is in progress.
DeleteL
will delete the requested Element or a set
of Elements from the database. The function will leave with
KErrPermissionDenied
if clients do not have appropriate read and
write capabilities for all Elements in the database indicated by the deletion
request. It will also Leave if the client has not correctly set the attribute
mask to cover all items to be deleted.
DeleteL
will open a read/write transaction with
the storage server if the client is not already in a transaction and hence can
be guaranteed to be atomic and to give a consistent view of persisted data.
Any error in the DeleteL
function will fail the
transaction that is in progress.
Commsdat supports the idea of a template record. A template record contains default values which are used if the corresponding column in a specific record has a NULL value.
The DIAL_OUT_ISP table is an example: each record in the table corresponds to an ISP that can be used to make an Internet connection. While the amount of information for each ISP is large, much of it may be common to all the ISPs, and can be held in the single template record. Each ISP record then need only define the information that is specific to that record. If, for a given ISP, the default value for a specific piece of information is sufficient, then that column in that ISP's record is NULL.
To create a record that uses the template, set the recordId to KCDDefaultRecord. A user is able to modify a default attribute for a field type, but this will typically only be done prior to the ROM build by using the CommsDat tools.
To load a template record of CCDNetworkRecord
//Create a Session and set the mask to Hidden to enable viewing template records
CMDBSession *cmdbSession = CMDBSession::NewL(CMDBSession::LatestVersion());
cmdbSession->SetAttributeMask(ECDHidden);
//STORE: create a container and set the record id to defaultrecord
CCDNetworkRecord* ptrNetworkRecord =
(CCDNetworkRecord*)CCDRecordBase::RecordFactoryL(KCDTIdNetworkRecord);
ptrNetworkRecord->SetRecordId(KCDDefaultRecord);
ptrNetworkRecord->SetAttributes(ECDHidden); //template records have to be set to hidden
ptrNetworkRecord->StoreL(*cmdbSession);
//LOAD: create a container and set the record id to defaultrecord
CCDNetworkRecord* ptrNetworkRecord =
(CCDNetworkRecord*)CCDRecordBase::RecordFactoryL(KCDTIdNetworkRecord);
ptrNetworkRecord->SetRecordId(KCDDefaultRecord);
ptrNetworkRecord->LoadL(*cmdbSession);
A set of classes derived from CCDRecordBase
provides a convenient representation of the current data set to users. These
classes give a simple interface allowing the client to get and set values in
the Container as though dealing with a simple class or struct.
These data type classes provided by CommsDat must be created via a Factory function.
IMPORT_C static CMDBRecordBase* RecordFactoryL(TMDBElementId);
Or copied from another instance with the copy function
IMPORT_C static CMDBRecordBase* CreateCopyRecordL(CMDBRecordBase&
aCopyFromRecord);
Each CCDRecordBase class implements a constructor that takes a
TMDBElementId
parameter, a macro that defines some
internal access functions ( EXP_DATA_VTABLE
or
DATA_VTABLE
) and has a function that allows lookup of type and
name information for each of the record's nested Elements in a static table:
const SRecordTypeInfo* GetRecordInfo()
{
return iRecordInfo;
}
The SRecordTypeInfo
table defines the type, name and
length of a database Field and states whether or not the Field value can be
NULL.
User-defined Tables can be created, stored and accessed as for all native CommsDat Tables, but user-defined data is not fully supported or maintained by Symbian, although it is backed up and secured with platform security in the same way as Symbian comms data.
To create user-defined table in the Comms Database the client should
use the CMDBGenericRecord
class.
To access user-defined tables a client can either use the
CMDBGenericRecord
class or can define a new
CCDRecordBase
class which should be a little more efficient.
New tables should be created using the
KCDNewTableRequest
constant to ensure their id is unique
and does not clash with existing user-defined tables in the database.
See section 12 for examples showing how to create and access user-defined tables.
The Utilities interface provides support for a small set of CommDb utility functions and constructs that gather comms data for specific use cases and present it in a particular way.
All of these functions could be implemented in client processes as they are not central to CommsDat functionality. They remain in the API for backwards compatibility with CommDb. The intention is that over time these functions will be deprecated where possible and the API will eventually be removed.
/*
Set `aDialString` to be the appropriate string based on the
directory number, where the dial is being performed and the
chargecard to use. `ResolvePhoneNumberL()` opens a comms database
to perform the resolution
*/
IMPORT_C static void ResolvePhoneNumberL(TDesC& aNumber,
TDes& aDialString,
TParseMode aDialParseMode,
TUint32 aLocationId,
TUint32 aChargecardId);
/*
Set `aDialString` to be the appropriate string based on the
directory number, where the dial is being performed and the
chargecard to use
@publishedAll
*/
IMPORT_C static void ResolvePhoneNumberFromDatabaseL(TDesC& aNumber,
TDes& aDialString,
TParseMode aDialParseMode,
TUint32 aLocationId,
TUint32 aChargecardId);
CommsDat also includes the header file CommdbConnPref.h
to allow inclusion of
TCommDbConnPref
and other related constructs. These Containers are currently exported
and implemented by the CommdbShim. They are needed as they are part of the
Esock RConnection
API. When the shim is removed, these
constructs will be moved to CommsDat and supported until they can be deprecated
and replaced by CommsDat Containers themselves or until they can perhaps be
moved to Esock or some other location.
Clients migrating from Commdb to CommsDat should not find the transfer too difficult. However it is worth a little thought to ensure that the use of CommsDat API will be as quick and efficient as possible.
The Commdb API presented an interface based on column names. The CommsDat interface allows the client to move away from this inefficient mechanism towards lookup by numeric id and direct access through mapped data objects.
CommsDat does still support the name comparison method of accessing database fields where this is still necessary (for instance via the current Nifman API). However it is strongly recommended that clients use the more direct interface via Element Id wherever possible as this will be more efficient.
The CommsDat API makes it possible for clients to be very specific about the information they require from the database.
When migrating calls from Commdb to CommsDat developers may find
there is no need to simulate the opening of an entire view using
class. Often one or two single CMDBRecordSet
classes can be used on their own
particularly for data retrieval.
CMDBField
Many commdb actions are searches for commdb records by user-defined
record name. Such searches would be more efficient if they were done with
numeric ids. For this reason a CommsDatTag (
KCDTIdRecordTag
) field has been added as well as the Name
field ( KCDTIdRecordName
). Currently this field is
typically filled with the record id of the record to provide backwards
compatibility with the CommdbId field, but it can be filled with any numeric
record tag.
The CommsDatTag is also a convenient way to link records by specifying a UID as the tag in the records.
Ced and Ceddump have been ported to use CommsDat rather than Commdb. The client-facing interfaces of these tools remain unchanged and existing Ced files will be processed in the normal way.
Existing Ced configuration files can still be used with CommsDat as long as the ported version of Ced is called.
To migrate a particular binary instance of a cdbv3.dat
DBMS comms database to create a central repository database, a client should
first run an older version of ceddump (using an older build with Commdb and
Dbms) and then store the output config file for future use with the ported Ced.
It is strongly recommended that clients use the XML interface for
Ced. The XML interface performs more validation than the .cfg
interface, greatly reducing problems caused by inaccurate configuration files.
The .cfg
file is now deprecated and will soon be removed in a
future release.
Here are a set of examples for typical interaction with CommsDat.
We will use the IAP table for most of our examples here.
****IAP TABLE***
CMDBField<TDesC> iServiceType;
CMDBElementLink<CMDBServiceRecord> iService; // Link to a particular Service record
CMDBField<TDesC> iBearerType;
CMDBElementLink<CMDBBearerRecord> iBearer; // Link to a particular Bearer record
CMDBElementLink<CMDBNetworkRecord> iNetwork; // Link to a particular Network record
CMDBField<TUInt32> iNetworkWeighting; // Network weighting
CMDBElementLink<CMDBLocationRecord> iLocation; // Link to a particular Location record
The examples cover the following topics:
Load a table, find the total number of records in the table and sort them by name
Load all IAP records and print each record name
Search for IAP records by their ServiceType, then modify one of the fields in the last record found and store it
Search for an IAP record by its Name, then load the associated service and bearer table
Delete all but the last record from a table
Get an existing record, change a field value and store it as a NEW record
Follow a Link in a Iap record to other tables and get value of the field by both name and ID
Accessing the value of a field by using a CMDBField class instead of loading the entire record
Access control. Load/Store a protected record.
// Headerfiles and namespace
//Creating a session with the latest version
[OR] //Creating a session with a particular version
//Set required attributes if any
|
// CMDBRecordSet.iRecords is an RPointerArray, Hence the Array Sort can be customized
TInt SortRecordsById(const CMDBRecordBase& aLeft, const CMDBRecordBase& aRight)
{
return (aLeft.RecordId()) < (aRight.RecordId()) ? -1 : 1;
}
//Sort by ID
void RecordSort()
{
CMDBRecordSet<CCDConnectionPrefsRecord>* ptrConnPrefRecordSet =
new (ELeave) CMDBRecordSet(KCDTIdConnectionPrefsRecord);
ptrConnPrefRecordSet->LoadL(*iDb); //The table has been loaded
TInt totalcount = ptrConnPrefRecordSet->iRecords.Count();
// totalcount reflects the total number of records in the Connection Preference table
TLinearOrder<CMDBRecordBase> orderbyId(SortRecordsById);
ptrConnPrefRecordSet->iRecords.Sort(orderbyId);
}
void LoadAllIapRecords()
{
CMDBRecordSet<CCDIAPRecord>* ptrIapRecordSet =
new (ELeave) CMDBRecordSet<CCDIAPRecord>(KCDTIdIAPRecord);
ptrIapRecordSet->LoadL(*iDb);
//The table has been loaded
TInt i(0);
while(i < ptrIapRecordSet->iRecords.Count())
{
//RDebug::Print(ptrIapRecordSet->iRecords[i]->iRecordName);
i++;
}
}
void SearchAllIapRecords()
{
//Create a record set
CMDBRecordSet<CCDIAPRecord>* iapRecordSet =
new (ELeave) CMDBRecordSet<CCDIAPRecord>(KCDTIdIAPRecord);
//To find all IAP records supporting DialOutISP service
_LIT(KServiceType, "DialOutISP");
TPtrC Servicetype(KServiceType);
//To prime for a search, create a record with the priming fields and append it to the Recordset
CCDIAPRecord* ptrPrimingRecord = static_cast<CCDIAPRecord *>
(CCDRecordBase::RecordFactoryL(KCDTIdIAPRecord));
ptrPrimingRecord->iServiceType = Servicetype;
iapRecordSet->iRecords.AppendL(ptrPrimingRecord);
ptrPrimingRecord = NULL; //since ownership is been passed to the recordset
//Search
if(iapRecordSet->FindL(*iDb))
{
//The iapRecordSet->iRecords.Count() will now reflect the number of records found
TInt iapRecordsFound = iapRecordSet->iRecords.Count();
//Lets modify a field in the last record
CCDIAPRecord* iapRecord =
static_cast<CCDIAPRecord*>( iapRecordSet->iRecords [iapRecordsFound-1]);
iapRecord->iNetworkWeighting = 1;
/*Now store this modified record back to commsdat,
ModifyL() will only submit the changes done by the user
iapRecordSet->ModifyL(*iDb); will go thorough all the records
and submit the user changes*/
//to explictly submit only this record
iapRecord ->ModifyL(*iDb);
}
else {
/* No records found..but iRecords[0] is still present (though will only
contain the priming values), so its important to check for the return code */
}
}
void LoadLinkedRecords()
{
//To find an IAP with the following name
_LIT(KMyIap, "NTRas with Null Modem");
//When we search by Name or Id, there can be only one record to be returned
CCDIAPRecord* ptrIAPRecord =
static_cast<CCDIAPRecord *>(CCDRecordBase::RecordFactoryL(KCDTIdIAPRecord));
TPtrC recordname(KMyIap);
ptrIAPRecord->iRecordName.SetMaxLengthL(recordname.Length());
ptrIAPRecord->iRecordName = recordname;
//OR ptrIAPRecord->SetL(recordname);
if(ptrIAPRecord->FindL(*iDb))
{
// Found a matching record
/*Now for loading the associated service and bearer tables, iService,iBearer, etc
are links pointing to other records. These links have a "Value" and "iLinkedRecord".
The "Value" represent the id of the target field.and gets initialized along with the
parent record(ptrRecord). When we explictly call load on these LinkedFields,
"iLinkedRecord" ptr point to the appropriate object. The "iLinkedRecord" ptr is
owned by the parent record and gets deleted along with it.
So it has to be set to NULL if the user wants to take ownership.*/
ptrIAPRecord->iService.LoadL(*iDb);
ptrIAPRecord->iBearer.LoadL(*iDb);
//Take ownership of the loaded service record cache
CCDServiceRecordBase* ptrService =
static_cast< CCDServiceRecordBase *> (ptrIAPRecord->iService.iLinkedRecord);
(ptrIAPRecord->iService).iLinkedRecord = NULL;
/*Deleting ptrIAPRecord also deletes all the loaded iLinkedRecord
pointers it owns unless set to null*/
delete ptrIAPRecord;
//delete the service view
delete ptrService;
}
}
void DeleteRecords()
{
//Load a record set
CMDBRecordSet<CCDIAPRecord>* iapRecordSet =
new (ELeave) CMDBRecordSet<CCDIAPRecord>(KCDTIdIAPRecord);
iapRecordSet->LoadL(*iDb);
TInt totalCount = iapRecordSet->iRecords.Count();
/*To exempt one particular record (say the last one) from deletion,
we should remove it from the list before calling delete on the recordset..*/
CCDIAPRecord* ptrSingleIAPRecord;
if(totalCount > 1)
{
ptrSingleIAPRecord =
static_cast< CCDIAPRecord *>(iapRecordSet->iRecords [totalCount-1]);
iapRecordSet->iRecords.Remove(totalCount-1);
}
//deletes all records except the one we removed
iapRecordSet->DeleteL(*iDb);
//now delete the exempted record anyway
if(ptrSingleIAPRecord)
{
ptrSingleIAPRecord->DeleteL(*iDb);
}
delete iapRecordSet;
delete ptrSingleIAPRecord;
}
//Case 1: Change the ranking of the first connection pref record
//Case 2: Create an empty record and add it to the database
|
Say the structure of the IAP table is
Record Name |
NTRas with Null Modem |
---|---|
Service |
NtRas |
ServiceType |
DialOutIsp |
Bearer |
... |
BearerType |
... |
Network |
... |
and in the DialOutIsp table, the record named NtRas says
Record Name |
NtRas |
---|---|
Description |
Test |
Type |
..... |
DefaultTel |
..... |
DialResolution |
..... |
BearerSpeed |
9600 |
......... |
So now we need to find out the value of the DefaultTel
field by following the link from the IAP table. The example also explains the
usage of the GetFieldByNameL
and
GetFieldByIdL
functions.
//Case 1: You know what service type it is
/* Case2: Assume if the service type is unknown OR as a general case [ IMPORTANT: Both the following functions are more expensive than Case1 ] */
//Case 2a: to Find the field by its ID.
/*Case 2b: To find a field by its name, [IMPORTANT: GetFieldByNameL is more expensive than GetFieldByIdL and should be avoided where ever possible] */
|
Here we get the value of a field in the GlobalSettings table without loading the entire record
void GetFieldValue()
{
CMDBField<TDesC>* descField = new(ELeave) CMDBField<TDesC>(KCDTIdBearerAvailabilityCheckTSY);
descField->SetMaxLengthL(KMaxTextLength);
descField->SetRecordId(1); //recorded is ALWAYS 1 for global settings table
descField->LoadL(*iDb);
if(descField->IsNull())
{
//The field is not initialized with a value
User::Leave(KErrNotFound);
}
else
{
TDesC Value = *descField;
}
delete descField;
}
It's preferable to clear the session attribute mask once the access control operations have been completed to safeguard against any unintended writes to access-restricted records.
void StoreProtectedRecord()
{
CMDBSession *iDb = CMDBSession::NewL(CMDBSession::LatestVersion());
iDb ->SetAttributeMask(ECDProtectedWrite);
CCDIAPRecord* iapRecord =
static_cast<CCDIAPRecord*>(CCDRecordBase::RecordFactoryL(KCDTIdIAPRecord));
iapRecord->SetAttributes(ECDProtectedWrite);
iapRecord->StoreL(*iDb);
iDb->ClearAttributeMask(ECDProtectedWrite);
}
//Load all hidden records
void LoadHiddenRecords()
{
CMDBSession *iDb = CMDBSession::NewL(CMDBSession::LatestVersion());
CMDBRecordSet<CCDIAPRecord>* iapRecordSet = new (ELeave)
CMDBRecordSet<CCDIAPRecord>( KCDTIdIAPRecord);
//Load all IAP records including the hidden records
iDb ->SetAttributeMask(ECDHidden);
iapRecordSet ->LoadL(*iDb);
//Load non-hidden records only
iDb ->ClearAttributeMask(ECDHidden);
iapRecordSet ->LoadL(*iDb);
}
The tables are identified using a name and/or an Id and are created by passing in a Table definition/Schema files
//Step 1: Create a schema for the new table
const SGenericRecordTypeInfo recordInfo[] = {
SGenericRecordTypeInfo(KCDTIdRecordName,EText,ENotNull,KCDTypeNameRecordName),
SGenericRecordTypeInfo(KCDTIdRecordTag,EUint32,ENoAttrs,KCDTypeNameRecordTag),
SGenericRecordTypeInfo(KCDTIdWLANServiceId,EUint32,ENoAttrs,KNameWLANServiceId),
SGenericRecordTypeInfo(KCDTIdWLANType,EUint32,ENoAttrs,KNameWLANType),
SGenericRecordTypeInfo(KCDTIdWLANEnabled,EBool,ENoAttrs,KNameWLANEnabled),
SGenericRecordTypeInfo(KCDTIdWLANPriority,EUint32,ENoAttrs,KNameWLANPriority),
SGenericRecordTypeInfo(0,0,ENoAttrs,KCDNull)
};
//Step 2: Assign a name for this new table
_LIT(KGenericTable,"MyGenericTable");
//Step3: Create a CMDBGenericRecord object & Initialise it with the name and schema
CMDBGenericRecord* ptrNewTable =
static_cast<CMDBGenericRecord*>(CCDRecordBase::RecordFactoryL(KCDNewTableRequest));
ptrNewTable->InitialiseL(KGenericTable(),recordInfo);
//Step4: Add a new record to it. You can use GetFieldByNameL or GetFieldByIdL to access a field
TInt valueType; CMDBElement* LanType =
ptrNewTable-> GetFieldByNameL(KNameWLANType, valueType);
CMDBElement* RecordName = ptrNewTable->GetFieldByIdL(KCDTIdRecordName);
//Step5: Modify the fields and store it (creates a new record)
CMDBField<TUint32>* LanTypefield = static_cast<CMDBField<TUint32>*>(LanType);
CMDBField<TDesC>* RecordNamefield = static_cast<CMDBField<TDesC>*>(RecordName);
*LanTypefield = 100; //say
_LIT(KNewName, " NewName ");
RecordNamefield->SetMaxLengthL(KNewName().Length());
*RecordNamefield = KNewName();
//[OR] RecordNamefield->SetL(KNewName());
ptrNewTable->SetRecordId(KCDNewRecordRequest);
ptrNewTable->StoreL(*iDb);
//Step 1: A generic record is identified by its TableId or a TableName, So create a new object with that type
Case 1: Creating using an Id, when table name is not known
CMDBGenericRecord* ptrTable =
static_cast<CMDBGenericRecord*>(CCDRecordBase::RecordFactoryL(TableId));
[OR]
Case 2: Creating using an Tablename, when the id is not known
CMDBGenericRecord* ptrTable = static_cast<CMDBGenericRecord*>(CCDRecordBase::RecordFactoryL(0));
ptrTable->InitialiseL(KGenericTable(),NULL);
//Step2: Now set the record ID (say 1) and call LoadL
CMDBSession *dbSession = CMDBSession::NewL(CMDBSession::LatestVersion());
ptrTable->SetRecordId(1);
ptrTable->LoadL(*dbSession);
//Step3: Access a value
CMDBField<TUint32>* field = (CMDBField<TUint32>*)ptrTable->GetFieldByIdL(KCDTIdWLANType);
TUint32 LanType = *field; //LanType should now have a value of 100
//Step4: Modify a value and store it back
*field = 30;
//[OR] field->SetL(30);
ptrTable->ModifyL(*dbSession);
delete ptrTable;
delete dbSession;