Transactd tutorial
Introduction
Target
This tutorial is written for the beginner who learn Transactd for the first time. It shows the basic concepts of Transactd and how to use API, with sample codes.
The knowledge about SQL is not required. But you should install MySQL/MariaDB and use its command line clients.
Index
- Set up plugin and clients
- Prepare for each programming language
- Operation on databases and tables
- Read like SQL
- Create and drop databases and tables
- Download sample code
Set up plugin and clients
Transactd is composed of two modules. "Transactd plugin" works as the server plugin for MySQL/MariaDB. "Transactd clients" is used in user programs. This section guides how to set up these modules.
Install plugin and clients
MySQL/MariaDB and Transactd plugin
"Transactd plugin" works as the server plugin for MySQL/MariaDB. Add plugin module to your installed MySQL/MariaDB server.
To install MySQL / MariaDB, please refer to the documentation of each products.
To install Transactd plugin, please refer to Install guide.
After installation, please do Show status of Transactd Plugin to check installation.
Transactd clients
The modules for following programming languages are provided now:
- C++
- ActiveX (COM) : Used by C#, JScript etc, on Windows.
- Ruby
- PHP
Pre-built C++ client binaries are available for Linux 64bit GCC and Windows Visual Studio 32bit/64bit. The binaries for Linux is generic binary which based on LSB.
For other compilers, build client binaries from source code.
Please refer to Install guide.
Pre-built COM component binaries are available.
COM component for x86-32bit/64bit are provided.
Please refer to Install guide.
Transactd client for Ruby is available on RubyGems.org.
Please refer to Install guide.
For Linux, build PHP client binaries from source code.
Pre-built binaries for PHP are available on Windows.
Please refer to Install guide.
Pre-built COM component binaries are available.
COM component for x86-32bit/64bit are provided.
Please refer to Install guide.
Sample database and table
Execute following SQL (with command line client or other tool) to create user database tdtutorial
:
CREATE DATABASE `tdtutorial`;
use tdtutorial;
CREATE TABLE `users` (`id` int NOT NULL, `name` varbinary(32) NOT NULL,
`tel` varbinary(20) NOT NULL, `group` int NOT NULL,
PRIMARY KEY (`id`),index group_idx (`group`));
CREATE TABLE `groups` (`id` int NOT NULL, `name` varbinary(32) NOT NULL, PRIMARY KEY (`id`));
/* users record */
INSERT INTO `users` VALUES
(1, 'user1', '0123-456-xxx', 1),
(2, 'user2', '9876-543-xxx', 2),
(3, 'user3', '', 1),
(4, 'user4', '080-xxxx-xxxx', 2),
(5, 'user5', '090-yyyy-yyyy', 3),
(6, 'user6', '090-zzzz-zzzz', 3),
(7, 'user7', '', 3),
(8, 'user8', '090-aaaa-bbbb', 3),
(9, 'user9', '0123-456-xxx', 2),
(10, 'user10', '0123-456-aaa', 3),
(11, 'user11', '090-cccc-dddd', 3),
(12, 'user12', '0123-456-bbb', 3),
(13, 'user13', '0123-456-ccc', 2),
(14, 'user14', '', 3),
(15, 'user15', '0123-456-dddd', 3);
/* groups record */
INSERT INTO `groups` VALUES
(1, 'Administrators'),
(2, 'DBOperators'),
(3, 'Users'),
(4, 'Guests');
tdtutorial
database has users
and groups
table.
Prepare for each programming language
In this tutorial, sample codes works actually. How to use Transactd in program code is different between each programming language.
Configure following directory settings to include Transactd header file and link library files.
Windows
- Add
source
directory in Transactd source code package or SDK to include path.
(ex:C:\transactd-client-win64_with_sdk-2.4.3\source
) - Add BOOST path to include path.
(ex:C:\boost\boost_1_58_0
) - Add
lib
directory in SDK or build directory to library path to link withtdclcpp_xxx_xx_xxx.lib
.
(ex:C:\transactd-client-win64_with_sdk-2.4.3\lib
) - Add
tdclcpp_xxx_xx_xxx.lib
to project to link it, or, addTRDCL_AUTOLINK
to preprocessor of project property in Visual Studio or Embarcadero C++ Builder.
Linux
- Add
source
directory in Transactd source code package or SDK to include path.
(ex:~/transactd-client-linux-x86_64_with_sdk-2.4.3/source
) - Add に
source/linux
directory to include path too.
(ex:~/transactd-client-linux-x86_64_with_sdk-2.4.3/source/linux
) - Add BOOST path to include path.
(ex:~/boost/boost_1_58_0
) - Add the directory where
libtdclcpp_64m.so.2.4
is installed to library path. Configure Makefile to linktdclcpp_64m
. Makefile Example
JScript program does not need compiling.
Ruby program does not need compiling.
PHP program does not need compiling.
Select Visual Studio menu - [Project] - [Add Reference...] and open [Reference Manager].
Enable transactd
in [COM] list.
After adding transactd
to Reference, you can see Transactd's constant values, functions and classes in
[Object Browser].
include, require
#include
header files in SDK and standard library header file which is used in sample.
#include <bzs/db/protocol/tdap/client/database.h>
#include <bzs/db/protocol/tdap/client/table.h>
#include <stdio.h>
Define using namespace
to omit the name space.
using namespace bzs::db::protocol::tdap::client;
using namespace bzs::db::protocol::tdap;
JScript program does not need require
or include
.
Create instance with new ActiveXObject
.
var db = new ActiveXObject('transactd.database');
Transactd client which installed from RubyGems.org can be used with require
, as same as other gem packages.
require 'transactd'
You should require
wrapper file transactd.php
. It contains Transactd's constant values,
functions, classes and useful interfaces like ArrayAccess
.
- Put
transactd.php
in PHPinclude_path
, thenrequire('transactd.php');
. require('path/to/transactd.php');
.
The namespace where classes are is BizStation\Transactd
.
In this tutorial, use
to omit long namespace.
require('transactd.php');
use BizStation\Transactd\Transactd;
use BizStation\Transactd\Database;
use BizStation\Transactd\ActiveTable;
use BizStation\Transactd\Query;
use BizStation\Transactd\SortFields;
use BizStation\Transactd\GroupQuery;
use BizStation\Transactd\Count;
use BizStation\Transactd\RecordsetQuery;
use BizStation\Transactd\Tabledef;
Define using
to omit the name space.
using transactd;
Operation on databases and tables
Open and close database
Let's open database first.
static const _TCHAR* uri = _T("tdap://localhost/tdtutorial?dbfile=transactd_schema");
database* db = database::create();
db->open(uri, TYPE_SCHEMA_BDF, TD_OPEN_NORMAL);
// ...
// close and release database object
database::destroy(db);
var TYPE_BDF = 0;
var OPEN_NORMAL = 0;
var URI = 'tdap://localhost/tdtutorial?dbfile=transactd_schema';
// open database
var db = new ActiveXObject('transactd.database');
db.Open(URI, TYPE_BDF, OPEN_NORMAL);
// ...
// close database
db.Close();
COM releases database
object memory automatically.
The database will be closed automatically when memory releasing,
but it is preferred to call Close
explicitly.
uri = 'tdap://localhost/tdtutorial?dbfile=transactd_schema'
# open database
db = Transactd::Database.new()
db.open(uri, Transactd::TYPE_SCHEMA_BDF, Transactd::TD_OPEN_NORMAL)
# ...
# close database
db.close()
Ruby's GC releases database
object memory automatically.
The database will be closed automatically when memory releasing,
but it is preferred to call close
explicitly.
$uri = 'tdap://localhost/tdtutorial?dbfile=transactd_schema';
// open database
$db = new Database();
$db->open($uri, Transactd::TYPE_SCHEMA_BDF, Transactd::TD_OPEN_NORMAL);
// ...
// close database
$db->close();
PHP's GC releases database
object memory automatically.
The database will be closed automatically when memory releasing,
but it is preferred to call close
explicitly.
const string uri = "tdap://localhost/tdtutorial?dbfile=transactd_schema";
// open database
Database db = new Database();
db.Open(uri, eSchemaType.schemaBdf, eOpenMode.normal);
// ...
// close database
db.Close();
COM releases database
object memory automatically.
The database will be closed automatically when memory releasing,
but it is preferred to call close
explicitly.
To open database, create database
object first.
Next, call open
method with URI parameter.
After some operations, close database with close
method.
URI format
There is format of URI which was passed to open method:
tdap://username@localhost/tdtutorial?dbfile=transactd_schema&pwd=password
‾‾‾‾ ‾‾‾‾‾‾‾‾ ‾‾‾‾‾‾‾‾‾ ‾‾‾‾‾‾‾‾‾‾ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ ‾‾‾‾‾‾‾‾‾‾‾‾
scheme username host database schema table password
Element | Description |
---|---|
tdap | It means "use Transactd protocol". |
username | User name on MySQL. (It is not required in default. Only with "Authentication by combination of users and hosts".) |
host | Host name or IP address of server. |
database | Database name. |
schema table | Specify schema table name with dbfile= .
transactd_schema means "generate schema table automatically".(Transactd 3.0 or later) Schema table is optional. If it is not specified, get schema information automatically. |
password | Password with pwd= .
(It is not required in default. Only with "Authentication by combination of users and hosts".) |
What is "schema table"?
"schema table" is a meta information table for Transactd plugin. It contains schema information of all tables in database.
The schema table can be generated automatically from an existing database.
Specify transactd_schema
as schema table name on open
,
Transactd will read table information and will generate schema table automatically.
On Transactd 3.0 or later, schema table is optional. If you want, no schema table will be saved, and read table schema information on each database open.
After modifing table schema with SQL
If you modify table schema with CREATE TABLE
or ALTER TABLE
SQL,
there is the difference between the information stored in schema table and actual schema information.
In such case, drop schema table once. Then open database with schema table name transactd_schema
.
The schema table will be generated automatically with the latest information.
Specify close(true /* withDropDefaultSchema */)
to drop
transactd_schema
table on closing database.
Connection
database
object manages TCP or named-pipe connections. If there is no connection,
new connection will be created on open
. So you do not have to care about it.
Prease reffer to multi thread application page (Japanese) too.
Open and close table
Next, open a table.
// ...
// open table
table* tb = db->openTable(_T("users"), TD_OPEN_NORMAL);
// ...
// release table
tb->release();
// ...
// close and release database object
database::destroy(db);
// ...
// open table
var tb = db.OpenTable('users', OPEN_NORMAL);
// ...
// close table
tb.Close();
// ...
// close database
db.Close();
# ...
# open table
tb = db.openTable('users', Transactd::TD_OPEN_NORMAL)
# ...
# close table
tb.close()
# ...
# close database
db.close()
// ...
// open table
$tb = $db->openTable('users', Transactd::TD_OPEN_NORMAL);
// ...
// close table
$tb->close();
// ...
// close database
$db->close();
// ...
// open table
Table tb = db.OpenTable("users", eOpenMode.normal);
// ...
// close table
tb.Close();
// ...
// close database
db.Close();
Call openTable
to open a table, and call close
to close the table.
Please be careful that close the database after close the table.
stat
of database
object show the result status code of the operation. Success code is 0.
Read
First reading
How to read data from table:
- Set the index which will be used in searching.
- Set key value to the indexed field(s).
- Call
seek
methods. - Check the result status code with
stat
. Success code is 0.
// read a record
tb->setKeyNum(0);
tb->clearBuffer();
tb->setFV(_T("id"), 2);
tb->seek();
if (tb->stat() == 0)
_tprintf(_T("id = 2, name = %s\n"), tb->getFVstr(_T("name")));
else
_tprintf(_T("Can not read id = 2"));
- Set values with
setFV
. First argument is field name, next is value. There are some overloaded functions according to the type of value, such as_TCHAR*
int64
int
short
double
float
. - Get values with
getFVxxx
. There are some overloaded functions according to the type of value, such asgetFVint
getFV64
getFVstr
getFVdbl
getFVbin
.
// read a record
tb.KeyNum = 0;
tb.ClearBuffer();
tb.Vlng('id') = 2;
tb.Seek();
if (tb.Stat == 0)
WScript.Echo('id = 2, name = ' + tb.Text('name'));
else
WScript.Echo('Can not read id = 2');
- There are some "property with parameter" according to the type of value, such as
V64
Vlng
Vbin
Text
. Set or get values with them.
# read a record
tb.setKeyNum(0)
tb.clearBuffer()
tb.setFV('id', 2)
tb.seek()
if (tb.stat() == 0)
puts('id = 2, name = ' + tb.getFVstr('name'))
else
puts('Can not read id = 2')
end
- Set values with
setFV
. First argument is field name, next is value. - Get values with
getFVxxx
. There are some overloaded functions according to the type of value, such asgetFVint
getFVstr
getFVbin
.
// read a record
$tb->setKeyNum(0);
$tb->clearBuffer();
$tb->setFV('id', 2);
$tb->seek();
if ($tb->stat() == 0)
echo('id = 2, name = ' . $tb->getFVstr('name') . PHP_EOL);
else
echo('Can not read id = 2' . PHP_EOL);
- Set values with
setFV
. First argument is field name, next is value. - Get values with
getFVxxx
. There are some overloaded functions according to the type of value, such asgetFVint
getFVstr
getFVbin
.
// read a record
tb.KeyNum = 0;
tb.ClearBuffer();
tb.Vlng["id"] = 2;
tb.Seek();
if (tb.Stat == 0)
Console.WriteLine("id = 2, name = " + tb.Text["name"]);
else
Console.WriteLine("Can not read id = 2");
- There are some "property with parameter" according to the type of value, such as
V64
Vlng
Vbin
Text
. Set or get values with them.
Seek a record which match with specified key value in above code. The found record (row) is "current row". If record was not found, current row is undefined.
Add more information about functions in above code.
- The field number (zero-origin) is available instead of the field name. Access with field number is faster than access with field name.
seek
method seeks the record which match with specified key value.seekGreater / seekLessThan
method seeks the record which same as specified key value, or, first greater/less-than record. IfOR_EQUAL
parameter isfalse
, the record which same as specified key value will be excluded.seekFirst / seekLast
method seeks the first/last record with index order.
Method | Operator and match record | |
---|---|---|
seek() | = | Record which match with specified key value |
seekGreater(OR_EQUAL = true) | >= | First record which match with specified key value or greater than |
seekGreater(OR_EQUAL = false) | > | First record which is greater than specified key value |
seekLessThan(OR_EQUAL = true) | <= | First record which match with specified key value or less than |
seekLessThan(OR_EQUAL = false) | < | First record which is less than specified key value |
seekFirst() | First record with index order | |
seekLast() | Last record with index order |
Server cursor
Before next step, introduce "Server cursor".
The current row in Transactd's table
class is managed by server side.
For example, nstable::seekNext()
method moves current row to next row in server, and send the data to client.
This kind of cursor is called Server cursor.
Moving row
Now you get current row, so you can move current row with seekPrev
or seekNext
.
seekPrev
andseekNext
method get prev/next of current row. If you use these methods, you must get the record successfully in the previous operation.- Get the result status code of operation with
stat
. Success code is0
. If there is no more record, it returnsSTATUS_EOF
error code.
Read all records with seekFirst and seekNext
Let's read all records on users table.
// read all records
tb->seekFirst();
while (tb->stat() == 0)
{
_tprintf(_T("id = %ld name = %s\n"), tb->getFVint(_T("id")) , tb->getFVstr(_T("name")));
tb->seekNext();
}
// read all records
tb.SeekFirst();
while (tb.Stat == 0)
{
WScript.Echo('id = ' + tb.Vlng('id') + ', name = ' + tb.Text('name'));
tb.SeekNext();
}
# read all records
tb.seekFirst()
while (tb.stat() == 0) do
puts('id = ' + tb.getFVint('id').to_s() + ', name = ' + tb.getFVstr('name'))
tb.seekNext()
end
// read all records
$tb->seekFirst();
while ($tb->stat() == 0)
{
echo('id = ' . $tb->getFVint('id') . ', name = ' . $tb->getFVstr('name') . PHP_EOL);
$tb->seekNext();
}
// read all records
tb.SeekFirst();
while (tb.Stat == 0)
{
Console.WriteLine("id = " + tb.Vlng["id"] + ", name = " + tb.Text["name"]);
tb.SeekNext();
}
Read records which match "id >= 2 and id <= 5" with seekGreater
Next, read records whose id
is in range 2 to 5.
// select records id >= 2 and id <= 5
const static int OR_EQUAL = true;
tb->clearBuffer();
tb->setFV(_T("id"), 2);
tb->seekGreater(OR_EQUAL);
while (tb->stat() == 0)
{
if (tb->getFVint(_T("id")) > 5)
break;
_tprintf(_T("id = %ld, name = %s\n"), tb->getFVint(_T("id")), tb->getFVstr(_T("name")));
tb->seekNext();
}
// select records id >= 2 and id <= 5
var OR_EQUAL = true;
tb.ClearBuffer();
tb.Vlng('id') = 2;
tb.SeekGreater(OR_EQUAL);
while (tb.Stat == 0)
{
if (tb.Vlng('id') > 5)
break;
WScript.Echo('id = ' + tb.Vlng('id') + ', name = ' + tb.Text('name'));
tb.SeekNext();
}
# select records id >= 2 and id <= 5
or_equal = true
tb.clearBuffer()
tb.setFV('id', 2)
tb.seekGreater(or_equal)
while (tb.stat() == 0) do
break if (tb.getFVint('id') > 5)
puts('id = ' + tb.getFVint('id').to_s() + ', name = ' + tb.getFVstr('name'))
tb.seekNext()
end
// select records id >= 2 and id <= 5
const OR_EQUAL = true;
$tb->clearBuffer();
$tb->setFV('id', 2);
$tb->seekGreater(OR_EQUAL);
while ($tb->stat() == 0)
{
if ($tb->getFVint('id') > 5)
break;
echo('id = ' . $tb->getFVint('id') . ', name = ' . $tb->getFVstr('name') . PHP_EOL);
$tb->seekNext();
}
// select records id >= 2 and id <= 5
var OR_EQUAL = true;
tb.ClearBuffer();
tb.Vlng('id') = 2;
tb.SeekGreater(OR_EQUAL);
while (tb.Stat == 0)
{
if (tb.Vlng["id"] > 5)
break;
Console.WriteLine("id = " + tb.Vlng["id"] + ", name = " + tb.Text["name"]);
tb.SeekNext();
}
In the case of using seekNext
, the order of records is order of index
.
In this case, records are order by id
. So checking exit condition in loop is very easy.
Insert
How to insert record to table:
- Call
clearBuffer()
to initialize all field values. - Set field values to
table
object, and callinsert
.
// Insert user16
tb->clearBuffer();
tb->setFV(_T("id"), 16);
tb->setFV(_T("name"), _T("user16"));
tb->setFV(_T("tel"), _T("0123-abcd-efgh"));
tb->setFV(_T("group"), 3);
tb->insert();
if (tb->stat() != 0)
_tprintf(_T("\n Can not insert user16, stat = %d\n"), tb->stat());
else
_tprintf(_T("\n user16 insert success.\n"));
// Insert user16
tb.ClearBuffer();
tb.Vlng('id') = 16;
tb.Text('name') = 'user16';
tb.Text('tel') = '0123-abcd-efgh';
tb.Text('group') = 3;
tb.Insert();
if (tb.Stat != 0)
WScript.Echo('Can not insert user16, stat = ' + tb.Stat);
else
WScript.Echo('user16 insert success.');
# Insert user16
tb.clearBuffer()
tb.setFV('id', 16)
tb.setFV('name', 'user16')
tb.setFV('tel', '0123-abcd-efgh')
tb.setFV('group', 3)
tb.insert()
if (tb.stat() != 0)
puts('Can not insert user16, stat = ' + tb.stat().to_s())
else
puts('user16 insert success.')
end
// Insert user16
$tb->clearBuffer();
$tb->setFV('id', 16);
$tb->setFV('name', 'user16');
$tb->setFV('tel', '0123-abcd-efgh');
$tb->setFV('group', 3);
$tb->insert();
if ($tb->stat() != 0)
echo('Can not insert user16, stat = ' . $tb->stat() . PHP_EOL);
else
echo('user16 insert success.');
// Insert user16
tb.ClearBuffer();
tb.Vlng["id"] = 16;
tb.Text["name"] = "user16";
tb.Text["tel"] = "0123-abcd-efgh";
tb.Vlng["group"] = 3;
tb.Insert();
if (tb.Stat != 0)
Console.WriteLine("Can not insert id = 16, stat = " + tb.Stat);
else
Console.WriteLine("user16 insert success.");
Update
How to update existing record:
- Set the index which will be used in searching.
- Set key value to the indexed field(s).
- Call
seek
methods and find target record. - Change field values and call
update
.
For example, search the record which have id=2
, and update name
value to userB
.
// Update user2 to userB
tb->clearBuffer();
tb->setFV(_T("id"), 2);
tb->seek();
if (tb->stat() == 0)
{
tb->setFV(_T("name"), _T("userB"));
tb->update();
if (tb->stat() != 0)
_tprintf(_T("\n Can not update name id = 2 stat = %d\n"), tb->stat());
else
_tprintf(_T("\n user2 update success.\n"));
}
else
_tprintf(_T("\n Can not read id = 2\n"));
// Update user2 to userB
tb.ClearBuffer();
tb.Vlng('id') = 2;
tb.Seek();
if (tb.Stat == 0)
{
tb.Text('name') = 'userB';
tb.Update();
if (tb.Stat != 0)
WScript.Echo('Can not update name id = 2 stat = ' + tb.Stat);
else
WScript.Echo('user2 update success.');
}
else
WScript.Echo('Can not read id = 2');
# Update user2 to userB
tb.clearBuffer()
tb.setFV('id', 2)
tb.seek()
if (tb.stat() == 0)
tb.setFV('name', 'userB')
tb.update()
if (tb.stat() != 0)
puts('Can not update name id = 2 stat = ' + tb.stat().to_s())
else
puts('user2 update success.')
end
else
puts('Can not read id = 2')
end
// Update user2 to userB
$tb->clearBuffer();
$tb->setFV('id', 2);
$tb->seek();
if ($tb->stat() == 0)
{
$tb->setFV('name', 'userB');
$tb->update();
if ($tb->stat() != 0)
echo('Can not update name id = 2 stat = ' . $tb->stat() . PHP_EOL);
else
echo('user2 update success.');
}
else
echo('Can not read id = 2' . PHP_EOL);
// Update user2 to userB
tb.ClearBuffer();
tb.Vlng["id"] = 2;
tb.Seek();
if (tb.Stat == 0)
{
tb.Text["name"] = "userB";
tb.UpDate();
if (tb.Stat != 0)
Console.WriteLine("Can not update name id = 2 stat = " + tb.Stat);
else
Console.WriteLine("user2 update success.");
}
else
Console.WriteLine("Can not read id = 2");
update
method overwrites current row.
If you call it without getting current row, stat
returns STATUS_NO_CURRENT
error code.
If the target record has been updated or deleted by other client between your reading and updating,
conflict will be detected, stat
returns STATUS_CHANGE_CONFLICT
error code.
If changeinkey
is specified to update
, you can omit finding target record before.
Seek current row with key value automatically. (In the case of using unique key without updating key value.)
Delete
How to delete a record:
- Set the index which will be used in searching.
- Set key value to the indexed field(s).
- Call
seek
methods and find target record. - Call
delete
.
For example, delete the record which have id=16
.
// Delete user16
tb->clearBuffer();
tb->setFV(_T("id"), 16);
tb->seek();
if (tb->stat() == 0)
{
tb->del();
if (tb->stat() != 0)
_tprintf(_T("\n Can not delete id = 16, stat = %d\n"), tb->stat());
else
_tprintf(_T("\n user16 delete success.\n"));
}
else
_tprintf(_T("\n Can not read id = 16\n"));
// Delete user16
tb.ClearBuffer();
tb.Vlng('id') = 16;
tb.Seek();
if (tb.Stat == 0)
{
tb.Delete();
if (tb.Stat != 0)
WScript.Echo('Can not delete id = 16, stat = ' + tb.Stat);
else
WScript.Echo('user16 delete success.');
}
else
WScript.Echo('Can not read id = 16');
# Delete user16
tb.clearBuffer()
tb.setFV('id', 16)
tb.seek()
if (tb.stat() == 0)
tb.del()
if (tb.stat() != 0)
puts('Can not delete id = 16, stat = ' + tb.stat().to_s())
else
puts('user16 delete success.')
end
else
puts('Can not read id = 16')
end
// Delete user16
$tb->clearBuffer();
$tb->setFV('id', 16);
$tb->seek();
if ($tb->stat() == 0)
{
$tb->del();
if ($tb->stat() != 0)
echo('Can not delete id = 16, stat = ' . $tb->stat() . PHP_EOL);
else
echo('user16 delete success.');
}
else
echo('Can not read id = 16' . PHP_EOL);
// Delete user16
tb.ClearBuffer();
tb.Vlng["id"] = 16;
tb.Seek();
if (tb.Stat == 0)
{
tb.Delete();
if (tb.Stat != 0)
Console.WriteLine("Can not delete id = 16, stat = " + tb.Stat);
else
Console.WriteLine("user16 delete success.");
}
else
Console.WriteLine("Can not read id = 16");
delete
method deletes current row. If you call it without getting current row,
stat
returns STATUS_NO_CURRENT
error code.
If the target record has been updated or deleted by other client between your reading and deleting,
conflict will be detected, stat
returns STATUS_CHANGE_CONFLICT
error code.
If inkey=true
is specified to delete
, you can omit finding target record before.
Seek current row with key value automatically. (In the case of using unique key.)
Transaction
Use transaction to update multi records atomically.
Change user names on id=2
and id=3
records with updateUserName
function.
Update two records atomically.
updateUserName(table* tb, int id, const _TCHAR* name)
{
tb->setKeyNum(0);
tb->clearBuffer();
tb->setFV(_T("id"), id);
tb->seek();
if (tb->stat() == 0)
{
tb->setFV(_T("name"), name);
tb->update();
}
return tb->stat();
}
/* Transaction */
db->beginTrn();
short ret = updateUserName(tb, 2, _T("John"));
if (ret == 0)
ret = updateUserName(tb, 3, _T("Akira"));
if (ret == 0)
db->endTrn();
else
db->abortTrn();
function updateUserName(tb, id, name)
{
tb.KeyNum = 0;
tb.ClearBuffer();
tb.Vlng('id') = id;
tb.Seek();
if (tb.Stat == 0)
{
tb.Text('name') = name;
tb.Update();
}
return tb.Stat;
}
// Transaction
db.BeginTrn();
var ret = updateUserName(tb, 2, 'John');
if (ret == 0)
ret = updateUserName(tb, 2, 'Akira');
if (ret == 0)
{
db.EndTrn();
WScript.Echo('Commit transaction.');
}
else
{
db.AbortTrn();
WScript.Echo('Rollback transaction.');
}
def updateUserName(tb, id, name)
tb.setKeyNum(0)
tb.clearBuffer()
tb.setFV('id', id)
tb.seek()
if (tb.stat() == 0)
tb.setFV('name', name)
tb.update()
end
return tb.stat()
end
db.beginTrn()
ret = updateUserName(tb, 2, 'John')
if (ret == 0)
ret = updateUserName(tb, 2, 'Akira')
end
if (ret == 0)
db.endTrn()
puts('Commit transaction.')
else
db.abortTrn()
puts('Rollback transaction.')
end
function updateUserName($tb, $id, $name)
{
$tb->setKeyNum(0);
$tb->clearBuffer();
$tb->setFV('id', $id);
$tb->seek();
if ($tb->stat() == 0)
{
$tb->setFV('name', $name);
$tb->update();
}
return $tb->stat();
}
// Transaction
$db->beginTrn();
$ret = updateUserName($tb, 2, 'John');
if ($ret == 0)
$ret = updateUserName($tb, 2, 'Akira');
if ($ret == 0)
{
$db->endTrn();
echo('Commit transaction.' . PHP_EOL);
}
else
{
$db->abortTrn();
echo('Rollback transaction.' . PHP_EOL);
}
static private eStatus updateUserName(Table tb, int id, string name)
{
tb.KeyNum = 0;
tb.ClearBuffer();
tb.Vlng["id"] = id;
tb.Seek();
if (tb.Stat == 0)
{
tb.Text["name"] = name;
tb.UpDate();
}
return tb.Stat;
}
// Transaction
db.BeginTrn();
eStatus ret = updateUserName(tb, 2, "John");
if (ret == 0)
ret = updateUserName(tb, 2, "Akira");
if (ret == 0)
{
db.EndTrn();
Console.WriteLine("Commit transaction.");
}
else
{
db.AbortTrn();
Console.WriteLine("Rollback transaction.");
}
Start transactions with beginTrn
and commit it with endTrn
.
Call abortTrn
to rollback.
Transaction runs on database
object unit.
Reading in transaction gets lock of target record(s).
So conflict will not be occurred on update
or delete
.
More detail about record lock, please refer to
InnoDB lock and control it (Japanese) or
Control InnoDB lock with Transactd (Japanese).
Error handling (database, table class)
The result status code of the methods which was introduced above is got by stat
on each class.
If stat() == 0
is success. Others are failed.
Operations in database
and table
classes do not throw any exceptions.
The description of error codes which is returned by stat
are listed in
Error code table (Japanese).
Or, call statMsg
to get error message.
_TCHAR errorMsg[1024] = { 0x00 };
if (db->stat() != 0)
{
db->statMsg(errorMsg);
_tprintf(_T("database erorr No.%d %s\n"), db->stat(), errorMsg);
}
if (db.Stat != 0)
{
WScript.Echo("database erorr No." + db.Stat + " " + db.StatMsg());
}
if db.stat() != 0
puts('database erorr No.' + db.stat().to_s() + ' ' + db.statMsg())
end
if ($db->stat() != 0)
{
echo('database erorr No.' . $db->stat() . ' ' . $db->statMsg() . PHP_EOL);
}
if (db.Stat != 0)
{
Console.WriteLine("database erorr:No.{0:d} {1}.", db.Stat, db.StatMsg());
}
You can download sample code with error handling.
Read like SQL
Error handling code is snipped from samples in this section to clearify essence of the code. Files in sample download contain error handling code.
activeTable
activeTable
is class that access to data with SQL-like method.
table
class accesses to server with one record unit.
activeTable
class can read
multiple records in once, and it is more faster.
query
class provides sql-like phrases such as SELECT
or WHERE
.
recordset
is result data set class.
It has operations such as OrderBy
, MutchBy
and GroupBy
.
In addition, you can Join
another table to recordset
.
Read with activeTable
How to read data with activeTable
:
- Create
activeTable
object. - Set the index which will be used in searching.
- Set key value to the indexed field(s).
- Set search conditions with
query
object. read
data.
Now, read user records which has 2 or lesser group
value from users
table.
Following code show step 1 to 3:
database* db = database::create();
db->open(uri, TYPE_SCHEMA_BDF, TD_OPEN_NORMAL);
activeTable at(db, _T("users"));
at.index(1).keyValue(0).alias(_T("name"), _T("user_name"));
// ...
database::destroy(db);
There are overloaded keyValue
same as table::setFV
. For example,
_TCHAR*
, int64
, int
, short
, double
, float
.
var db = new ActiveXObject('transactd.database');
db.Open(URI, TYPE_BDF, OPEN_NORMAL);
var at = new ActiveXObject('transactd.activeTable');
at.SetDatabase(db, 'users');
at.Index(1).KeyValue(0).Alias('name', 'user_name');
// ...
db.Close();
db = Transactd::Database.new()
db.open(uri, Transactd::TYPE_SCHEMA_BDF, Transactd::TD_OPEN_NORMAL)
at = Transactd::ActiveTable.new(db, 'users')
at.index(1).keyValue(0).alias('name', 'user_name')
# ...
# MUST release ActiveTable object explicitly
at.release()
db.close()
$db = new Database();
$db->open($uri, Transactd::TYPE_SCHEMA_BDF, Transactd::TD_OPEN_NORMAL);
$at = new ActiveTable($db, 'users');
$at->index(1)->keyValue(0)->alias('name', 'user_name');
// ...
$db->close();
Database db = new Database();
db.Open(uri, eSchemaType.schemaBdf, eOpenMode.normal);
ActiveTable at = new ActiveTable();
at.SetDatabase(db, "users");
at.Index(1).KeyValue(0).Alias("name", "user_name");
// ...
db.Close();
Set index which contains group
field as index
, because search condition is group
.
Key number is desided in order of creation, start from 0. In this case, group
index is 1
.
The value of keyValue
is start position. Normally set lower value. In this case, set 0
.
You can set alias to fields with alias
method.
In addition, methods which set index or field value or alias
returns self atciveTable
object to use method chain.
Next, set conditions to query
as step 4:
query q;
q.select(_T("id"), _T("user_name"), _T("group"), _T("tel"))
.where(_T("group"), _T("<="), 2).reject(1);
There are overloaded where
same as table::setFV
. For example,
_TCHAR*
, int64
, int
, short
, double
, float
.
var q = new ActiveXObject('transactd.query');
q.Select('id', 'user_name', 'group', 'tel')
.Where('group', '<=', 2).Reject(1);
q = Transactd::Query.new()
q.select('id', 'user_name', 'group', 'tel')
.where('group', '<=', 2).reject(1)
$q = new Query();
$q->select('id', 'user_name', 'group', 'tel')
->where('group', '<=', 2)->reject(1);
QueryBase q = new QueryBase();
q.Select("id", "user_name", "group", "tel")
.Where("group", "<=", 2).Reject(1);
List up fields which will be read with select
. select
can receive 11 fields in once.
If you want to more fields, call select
several times.
Set searching condition with where
. Pass field name, operator, and value.
and_
or or_
can continue multiple conditions.
reject(1)
means that finish searching if a field which do not match condition is found.
In this case, the condition contains key field, so searching will be finished immediately
if unmatch record (group=3
) was found. Use reject
efficiently to reduce search time.
Set reject(0xFFFF)
to do not finish searching even if unmatch records were found.
query
's methods which set values returns self query
object, as same as activeTable
.
Last, step 5, read
data. The result is recordset
object.
The code which contain all steps is:
activeTable at(db, _T("users"));
at.index(1).keyValue(0).alias(_T("name"), _T("user_name"));
query q;
q.select(_T("id"), _T("user_name"), _T("group"), _T("tel"))
.where(_T("group"), _T("<="), 2)
.reject(1);
recordset rs;
at.read(rs, q);
var at = new ActiveXObject('transactd.activeTable');
at.SetDatabase(db, 'users');
at.Index(1).KeyValue(0).Alias('name', 'user_name');
var q = new ActiveXObject('transactd.query');
q.Select('id', 'user_name', 'group', 'tel')
.Where('group', '<=', 2)
.Reject(1);
var rs = at.Read(q);
at = Transactd::ActiveTable.new(db, 'users')
at.index(1).keyValue(0).alias('name', 'user_name')
q = Transactd::Query.new()
q.select('id', 'user_name', 'group', 'tel')
.where('group', '<=', 2)
.reject(1)
rs = at.read(q)
$at = new ActiveTable($db, 'users');
$at->index(1)->keyValue(0)->alias('name', 'user_name');
$q = new Query();
$q->select('id', 'user_name', 'group', 'tel')
->where('group', '<=', 2)
->reject(1);
$rs = $at->read($q);
ActiveTable at = new ActiveTable();
at.SetDatabase(db, "users");
at.Index(1).KeyValue(0).Alias("name", "user_name");
QueryBase q = new QueryBase();
q.Select("id", "user_name", "group", "tel")
.Where("group", "<=", 2)
.Reject(1);
Recordset rs = at.Read(q);
rs
values like this:
+---------------------------------------------------------------+
|id |user_name |group |tel |
+---------------+---------------+---------------+---------------+
|1 |user1 |1 |0123-456-xxx |
|3 |Akira |1 | |
|2 |John |2 |9876-543-xxx |
|4 |user4 |2 |080-xxxx-xxxx |
|9 |user9 |2 |0123-456-xxx |
|13 |user13 |2 |0123-456-ccc |
+---------------+---------------+---------------+---------------+
recordset
recordset
contains rows and fields. It is similar to two-dimensional array.
You can access data with row number and field name, like rs[1]["name"]
.
Let's see rows and fields in recordset
with loop.
// dump recordset
void dumpRecordset(recordset& rs)
{
std::tcout.setf(std::ios::left, std::ios::adjustfield);
const fielddefs& fds = *rs.fieldDefs();
for(size_t i = 0; i < rs.size(); ++i)
{
std::tcout << _T("[") << i + 1 << _T("]\n");
row& rec = rs[i];
for(size_t col = 0; col < rec.size(); ++col)
std::tcout << _T("\t") << std::setw(15)
<< fds[col].name()
<< rec[col].c_str()
<< std::endl;
}
}
Read functions for each type are provided.
For example, c_str
, i64
, d
, getBin
.
// dump recordset
function dumpRecordset(rs)
{
// get field definitions
var fd = rs.FieldDefs;
// show records
for (var i = 0; i < rs.Count; i++)
{
WScript.Echo('[' + i + ']');
for (var j = 0; j < rs.Record(i).Size; j++)
WScript.Echo('\t[' + fd.FieldDef(j).Name + ']' + rs.Record(i).Field(j).Text);
}
}
# dump recordset
def dumpRecordset(rs)
# get field definitions
fd = rs.fieldDefs()
# show records
for i in 0...rs.size()
puts '[' + i.to_s + ']'
for j in 0...rs[i].size()
puts ' [' + fd[j].name + ']' + rs[i][j].str()
end
end
end
// dump recordset
function dumpRecordset($rs)
{
foreach ($rs as $row_id => $record)
{
echo('[' . $row_id . ']' . PHP_EOL);
foreach ($record as $field_name => $field_value)
{
echo("\t" . '[' . $field_name . ']' . $field_value . PHP_EOL);
}
}
}
// dump recordset
static void dumpRecordset(Recordset rs)
{
// get field definitions
FieldDefs fds = rs.FieldDefs;
// show records
for (UInt32 i = 0; i < rs.Size; i++)
{
Console.WriteLine("[" + i + "]");
Record r = rs[i];
for (short j = 0; j < r.Size; j++)
Console.WriteLine("\t[{0,-15}] {1}", fds[j].Name, r[j].Text);
}
}
Get field definitions (fielddefs
) with fieldDefs
.
You can access data with []
on this collection, too.
recordset
has SQL-like methods, such as OrderBy
, GroupBy
, Union
,
Join
. See more details in next section.
OrderBy
OrderBy
sorts recordset
.
Let's sort recordset
with order of id
.
rs.orderBy(_T("id"));
rs.OrderBy('id');
rs.orderBy('id')
$rs->orderBy('id');
rs.OrderBy("id");
Sort with more complex order, such as mix of ascending and descending order.
const static bool ASC = true;
const static bool DESC = false;
sortFields orders;
orders.add(_T("group"), ASC);
orders.add(_T("id"), DESC);
rs.orderBy(orders);
var ASC = true;
var DESC = false;
var orders = new ActiveXObject('transactd.sortFields');
orders.Add('group', ASC);
orders.Add('id', DESC);
rs.OrderByEx(orders);
ASC = true
DESC = false
orders = Transactd::SortFields.new()
orders.add('group', ASC)
orders.add('id', DESC)
rs.orderBy(orders)
const ASC = true;
const DESC = false;
$orders = new SortFields();
$orders->add('group', ASC);
$orders->add('id', DESC);
$rs->orderBy($orders);
const bool ASC = true;
const bool DESC = false;
SortFields orders = new SortFields();
orders.Add("group", ASC);
orders.Add("id", DESC);
rs.OrderByEx(orders);
Add order settings (field name and ASC/DESC) with sortFields
object.
Pass it to OrderBy
method.
Clone
If you want to get both of result details and aggregated result, there are two way by SQL. (1) Execute two queries, or, (2) Get detail result and aggregate it on program code.
Transactd's recordset
is copyable. If you got recordset
,
copy it and aggregate copied recordset
. In this way, data reading is only once.
Copy rs
to rs2
:
recordset& rs2 = *rs.clone();
// ...
rs2.release();
var rs2 = rs.Clone();
rs2 = rs.clone()
$rs2 = clone $rs;
recordset
has magic method __clone()
. Use clone
statement.
Recordset rs2 = rs.Clone();
GroupBy
How to grouping records with GroupBy
:
- Create
groupQuery
object. - Set grouping
KeyField
. - Set calculation function and result field name.
- Group with
groupBy
.
Group by group
field value and count records with count
function,
store result to user_count
.
std::tcout << _T("\n groupBy group\n");
groupQuery gq;
gq.keyField(_T("group"));
count countFunc(_T("user_count"));
gq.addFunction(&countFunc);
rs2.groupBy(gq);
WScript.Echo('groupBy group');
var gq = new ActiveXObject('transactd.groupQuery');
gq.KeyField('group');
gq.AddFunction(1, null, 'user_count'); // eGroupFunc.fcount = 1
rs2.GroupBy(gq);
puts('groupBy group')
gq = Transactd::GroupQuery.new()
gq.keyField('group')
countFunc = Transactd::Count.new('user_count')
gq.addFunction(countFunc)
rs2.groupBy(gq)
echo('groupBy group');
$gq = new GroupQuery();
$gq->keyField('group');
$countFunc = new Count('user_count');
$gq->addFunction($countFunc);
$rs2->groupBy($gq);
Console.WriteLine("\ngroupBy group");
GroupQuery gq = new GroupQuery();
gq.KeyField("group");
gq.AddFunction(eGroupFunc.fcount, null, "user_count");
rs2.GroupBy(gq);
The result is:
+---------------------+
| group|user_count|
+----------+----------+
| 1| 2|
| 2| 4|
+----------+----------+
There are more calculation functions such as sum
, avg
, max
, min
.
You can filter records with when
.
For example, exclude records which have blank tel
.
std::tcout << _T("\n groupBy with condition.\n");
rs2 = *rs.clone();
countFunc.when(_T("tel"), _T("<>") ,_T(""));
rs2.groupBy(gq);
WScript.Echo('groupBy with condition.');
var rq1 = new ActiveXObject('transactd.recordsetQuery');
rq1.When('tel', '<>', '');
gq.Reset();
gq.KeyField('group');
gq.AddFunction(1, null, 'user_count', rq1); // eGroupFunc.fcount = 1
rs2.GroupBy(gq);
puts('groupBy with condition.')
rs2 = rs.clone()
countFunc.when('tel', '<>', '')
rs2.groupBy(gq)
echo('groupBy with condition.');
$rs2 = $rs->clone();
$countFunc->when('tel', '<>', '');
$rs2->groupBy($gq);
Console.WriteLine("\ngroupBy with condition.");
RecordsetQuery rq1 = new RecordsetQuery();
rq1.When("tel", "<>", "");
gq.Reset();
gq.KeyField("group");
gq.AddFunction(eGroupFunc.fcount, null, "user_count", rq1);
rs2.GroupBy(gq);
The number of user on group 1 was decreased.
+---------------------+
| group|user_count|
+----------+----------+
| 1| 1|
| 2| 4|
+----------+----------+
MatchBy
How to filter result set with MatchBy
:
- Create
recordsetQuery
object. - Set filtering condition.
- Filter with
MatchBy
.
Remove records which have blank tel
from result set.
std::tcout << _T("\n matchBy tel is not empty.\n");
recordsetQuery rq;
rq.when(_T("tel"), _T("<>"), _T(""));
rs.matchBy(rq);
WScript.Echo('matchBy tel is not empty.');
var rq = new ActiveXObject('transactd.recordsetQuery');
rq.When('tel', '<>', '');
rs.MatchBy(rq);
puts('matchBy tel is not empty.')
rq = Transactd::RecordsetQuery.new()
rq.when('tel', '<>', '')
rs.matchBy(rq)
echo('matchBy tel is not empty.');
$rq = new RecordsetQuery();
$rq->when('tel', '<>', '');
$rs->matchBy($rq);
Console.WriteLine("\nmatchBy tel is not empty.");
RecordsetQuery rq = new RecordsetQuery();
rq.When("tel", "<>", "");
rs.MatchBy(rq);
Union
Two recordsets which have same field (in same order) can be united.
rs
does not have records which group=3
.
Read records which have group=3
to rs3
, and unite them.
std::tcout << _T("\n append(union) group3 users.\n");
q.reset().select(_T("id"), _T("user_name"), _T("group"), _T("tel"))
.where(_T("group"), _T("="), _T("3")).reject(0);
recordset rs3;
at.index(1).keyValue(3).read(rs3, q);
rs += rs3;
WScript.Echo('append(union) group3 users.');
q.Reset().Select('id', 'user_name', 'group', 'tel').Where('group', '=', 3).Reject(0);
var rs3 = at.Index(1).KeyValue(3).Read(q);
rs.UnionRecordset(rs3);
puts('append(union) group3 users.')
q.reset().select('id', 'user_name', 'group', 'tel').where('group', '=', 3).reject(0)
rs3 = at.index(1).keyValue(3).read(q)
rs.unionRecordset(rs3)
echo('append(union) group3 users.');
$q->reset()->select('id', 'user_name', 'group', 'tel')->where('group', '=', 3)->reject(0);
$rs3 = $at->index(1)->keyValue(3)->read($q);
$rs->unionRecordset($rs3);
Console.WriteLine("\nappend(union) group3 users.");
q.Reset().Select("id", "user_name", "group", "tel").Where("group", "=", 3).Reject(0);
Recordset rs3 = at.Index(1).KeyValue(3).Read(q);
rs.UnionRecordset(rs3);
Join
All Transactd JOIN is done on the client side. You can JOIN records between different databases and servers.
JOIN has two kinds of methods.
- The way that read data from the table and combine it using the key value on the recordset.
recordset - table JOIN
- The way that JOIN two recordsets.
recordset - recordset JOIN
They are called Table Join
and Recordset Join
.
I will explain basics Table Join
here.
About Recordset Join
, see recordset::join in SDK document.
Table Join
Table Join can be processed at high speed, by using the index of the joined table. The following restrictions apply to using this:
- The join key field on the table must be an indexed field. (In the case of multi-segment key, lower field can be omitted.)
- Use above index for the search index.
=
can only be used as Join condition.
Most Join can do this way if the tables and their relationships are designed correctly.
1 on 1 Join
How to Join
:
- Get
recordset
. - Create
activeTable
which have table you want to join. - Set the index which will be used in searching.
select
fields withquery
object.- Pass
recordset
(step 1) and field name which will be used as searching key tojoin
.
rs
has group id in group
field. Let's join group name with this id.
std::tcout << _T("\n Join group name.\n");
// open groups table
activeTable at2(db, _T("groups"));
at2.index(0).alias(_T("name"), _T("group_name"));
// join
q.reset().select(_T("group_name"));
at2.join(rs, q, _T("group"));
WScript.Echo('Join group name.');
// open groups table
var at2 = new ActiveXObject('transactd.activeTable');
at2.SetDatabase(db, 'groups');
at2.Index(0).Alias('name', 'group_name');
// join
q.Reset().Select('group_name');
at2.Join(rs, q, 'group');
puts('Join group name.')
# open groups table
at2 = Transactd::ActiveTable.new(db, 'groups')
at2.index(0).alias('name', 'group_name')
# join
q.reset().select('group_name')
at2.join(rs, q, 'group')
echo('Join group name.');
// open groups table
$at2 = new ActiveTable($db, 'groups');
$at2->index(0)->alias('name', 'group_name');
// join
$q->reset()->select('group_name');
$at2->join($rs, $q, 'group');
Console.WriteLine("\nJoin group name.");
// open groups table
ActiveTable at2 = new ActiveTable();
at2.SetDatabase(db, "groups");
at2.Index(0).Alias("name", "group_name");
// join
q.Reset().Select("group_name");
at2.Join(rs, q, "group");
In this case, index(0)
on groups
is primary key (it means it is unique key),
then this is 1 on 1 join.
"HasMany" Join
Read all group first, and join users to them. It is "HasMany" join because a group has many users.
std::tcout << _T("\n Has many join.\n");
// read all groups
rs.clear();
at2.index(0).keyValue(0)
.alias(_T("id"), _T("group_id"))
.alias(_T("name"), _T("group_name"))
.read(rs, q.reset());
// join groups
at.alias(_T("id"), _T("user_id"))
.alias(_T("name"), _T("user_name"));
q.reset().select(_T("user_id"), _T("user_name"));
at.index(1).join(rs, q, _T("group_id"));
WScript.Echo('Has many join.');
// read all groups
rs = at2.Index(0).KeyValue(0)
.Alias('id', 'group_id')
.Alias('name', 'group_name')
.Read(q.Reset());
// join groups
at.Alias('id', 'user_id')
.Alias('name', 'user_name');
q.Reset().Select('user_id', 'user_name');
at.Index(1).Join(rs, q, 'group_id');
puts('Has many join.')
# read all groups
rs = at2.index(0).keyValue(0)
.alias('id', 'group_id')
.alias('name', 'group_name')
.read(q.reset())
# join groups
at.alias('id', 'user_id')
.alias('name', 'user_name')
q.reset().select('user_id', 'user_name')
at.index(1).join(rs, q, 'group_id')
echo('Has many join.');
// read all groups
$rs = $at2->index(0)->keyValue(0)
->alias('id', 'group_id')
->alias('name', 'group_name')
->read($q->reset());
// join groups
$at->alias('id', 'user_id')
->alias('name', 'user_name');
$q->reset()->select('user_id', 'user_name');
$at->index(1)->join($rs, $q, 'group_id');
Console.WriteLine("\nHas many join.");
// read all groups
rs = at2.Index(0).KeyValue(0)
.Alias("id", "group_id")
.Alias("name", "group_name")
.Read(q.Reset());
// join groups
at.Alias("id", "user_id")
.Alias("name", "user_name");
q.Reset().Select("user_id", "user_name");
at.Index(1).Join(rs, q, "group_id");
The result is:
+---------------------------------------------------+
| group_id|group_name | user_id|user_name |
+-------------------------+-------------------------+
| 1|Administrators| 1|user1 |
| 1|Administrators| 3|user3 |
| 2|DBOperators | 2|user2 |
| 2|DBOperators | 4|user4 |
| 2|DBOperators | 9|user9 |
| 2|DBOperators | 13|user13 |
| 3|Users | 5|user5 |
| 3|Users | 6|user6 |
| 3|Users | 7|user7 |
| 3|Users | 8|user8 |
| 3|Users | 10|user10 |
| 3|Users | 11|user11 |
| 3|Users | 12|user12 |
| 3|Users | 14|user14 |
| 3|Users | 15|user15 |
+-------------------------+-------------------------+
OuterJoin
Above Join
are INNER JOIN
.
If there is no record to join, the record will be removed from result recordset
.
outerJoin
does not remove such records.
If there is no record to join, the record will be marked as isInvalidRecord=true
,
and the field will be initialized with NULL
.
Error handling (activeTable and recordset class)
Methods of activeTable
and recordset
throws exceptions if operation failed.
Write exception catching code.
try
{
activeTable at(db, "users");
recordset rs;
query q;
at.read(rs, q);
// ...
}
catch (bzs::rtl::exception& e)
{
std::tcout << *(bzs::rtl::getMsg(e)) << std::endl;
}
try
{
var at = new ActiveXObject('transactd.activeTable');
at.SetDatabase(db, 'users');
var rs = at.Read(q);
// ...
}
catch(e)
{
WScript.Echo("Error: " + e.name + " " + e.description);
}
begin
at = Transactd::ActiveTable.new(db, 'users')
at.read(q)
# ...
rescue => e
puts e.message
end
try
{
$at = new ActiveTable(db, 'users');
$rs = $at->read($q);
// ...
}
catch (Exception e)
{
echo($e->getMessage() . PHP_EOL);
}
try
{
ActiveTable at = new ActiveTable();
Recordset rs = at.Read(q);
// ...
}
catch (System.Runtime.InteropServices.COMException e)
{
Console.WriteLine(e.Message);
}
Create and drop databases and tables
Create database and table which is same as that created by following SQL:
CREATE DATABASE tdtutorial2;
CREATE TABLE tdtutorial2.users (id int(11) NOT NULL, name binary(33) NOT NULL, UNIQUE KEY key0 (id));
Create database
database::create
creates database and schema table for transactd.
_TCHAR errorMsg[1024] = { 0x00 };
database* db = database::create();
const _TCHAR* uri = _T("tdap://localhost/tdtutorial2?dbfile=transactd_schema");
db->create(uri);
// ...
/* close and release database object */
database::destroy(db);
var db = new ActiveXObject('transactd.database');
var URI = 'tdap://localhost/tdtutorial2?dbfile=transactd_schema';
db.Create(URI);
db = Transactd::Database.new()
URI = 'tdap://localhost/tdtutorial2?dbfile=transactd_schema'
db.create(URI)
const URI = 'tdap://localhost/tdtutorial2?dbfile=transactd_schema';
$db = new Database();
$db->create(URI);
const string uri = "tdap://localhost/tdtutorial2?dbfile=transactd_schema";
Database db = new Database();
db.Create(uri);
You can set any name to schema table. Set extension as bdf
.
If you use transactd_schema
as schema table, extension is not required.
Create table
First, create table definition and save it to schema table. After that,
the table will be created automatically when you open table with database::openTable()
.
How to create table definition:
- Get
dbdef
object fromdatabase
object. - Create table definition object
tabledef
. - Set information such as table name to
tabledef
object. - Create blank field definition object
fielddef
withdbdef::insertField
. - Set information such as field name and field type to
fielddef
object. - Repeat step 4 and step 5 for all fields.
- Create blank key definition object
keydef
withdbdef::insertKey
. - Set information such as key segments to
keydef
object. - Repeat step 7 and step 8 for all keys.
- Update schema table with
dbdef::updateTableDef
.
Step 1 to step 3:
dbdef::insertTable
returns tabledef
object. Set table information to it.
dbdef* def = db->dbDef();
tabledef td;
// table name and file name
td.setTableName(_T("users"));
td.setFileName(_T("users.dat"));
td.id = 1;
def->insertTable(&td);
dbdef::insertTable
returns tabledef
object. Set table information to it.
var dbdef = db.DbDef;
var td = dbdef.InsertTable(1);
// table name and file name
td.TableName = 'users';
td.FileName = 'users.dat';
td.Id = 1;
Create tabledef
object and set information to it. Then pass it to dbdef::insertTable
.
dbdef = db.dbDef()
td = Transactd::Tabledef.new()
# table name and file name
td.setTableName('users')
td.setFileName('users.dat')
td.id = 1
dbdef.insertTable(td)
Create tabledef
object and set information to it. Then pass it to dbdef::insertTable
.
$dbdef = $db->dbDef();
$td = new Tabledef();
// table name and file name
$td->setTableName('users');
$td->setFileName('users.dat');
$td->id = 1;
$dbdef->insertTable($td);
dbdef::insertTable
returns tabledef
object. Set table information to it.
DbDef dbdef = db.DbDef;
TableDef td = dbdef.InsertTable(1);
// table name and file name
td.TableName = "users";
td.FileName = "users.dat";
td.Id = 1;
Please refer to tabledef class reference (Japanese) for more details.
Add fields according to step 4 to step 6.
dbdef::insertField
returns fielddef
object. Set field information to it.
// [id] field
short fieldNum = 0;
fielddef* fd = def->insertField(td.id, fieldNum);
fd->setName(_T("id"));
fd->type = ft_integer;
fd->len = (ushort_td)4;
// [name] field
fd = def->insertField(td.id, ++fieldNum);
fd->setName(_T("name"));
fd->type = ft_zstring;
fd->len = 33;
// [id] field
var filedIndex = 0;
var fd = dbdef.InsertField(td.Id, filedIndex);
fd.Name = 'id';
fd.Type = ft_integer;
fd.Len = 4;
// [name] field
filedIndex = 1;
fd = dbdef.InsertField(td.Id, filedIndex);
fd.Name = 'name';
fd.Type = ft_zstring;
fd.Len = 33;
# [id] field
filedIndex = 0
fd = dbdef.insertField(td.id, filedIndex)
fd.setName('id')
fd.type = Transactd::Ft_integer
fd.len = 4
# [name] field
filedIndex = 1
fd = dbdef.insertField(td.id, filedIndex)
fd.setName('name')
fd.type = Transactd::Ft_zstring
fd.len = 33
// [id] field
$filedIndex = 0;
$fd = $dbdef->insertField($td->id, $filedIndex);
$fd->setName('id');
$fd->type = Transactd::ft_integer;
$fd->len = 4;
// [name] field
$filedIndex = 1;
$fd = $dbdef->insertField($td->id, $filedIndex);
$fd->setName('name');
$fd->type = Transactd::ft_zstring;
$fd->len = 33;
// [id] field
FieldDef fd = dbdef.InsertField(td.Id, 0);
fd.Name = "id";
fd.Type = eFieldType.ft_integer;
fd.Len = 4;
// [name] field
fd = dbdef.InsertField(td.Id, 1);
fd.Name = "name";
fd.Type = eFieldType.ft_zstring;
fd.Len = 33;
Please refer to
fielddef class reference (Japanese) for more details.
What MySQL data type is compatible with Transactd data type, such as ft_integer
, is listed in Data type table (Japanese).
Add keys according to step 7 to step 9.
dbdef::insertKey
returns keydef
object. Set key information to it.
After that, step 10, call dbdef::updateTableDef
to update schema table.
// Insert key
uchar_td keyNum = 0;
keydef* kd = def->insertKey(td.id, keyNum);
kd->segments[0].fieldNum = 0;
kd->segments[0].flags.bit8 = true; //extended type
kd->segments[0].flags.bit1 = true; //updateable
kd->segmentCount = 1;
// set id as primary key
def->tableDefs(td.id)->primaryKeyNum = keyNum;
// update schema table
def->updateTableDef(td.id);
// add key
var keyNum = 0;
var key = dbdef.InsertKey(td.Id, keyNum);
var seg1 = key.Segments(0);
seg1.FieldNum = 0;
seg1.Flags.Bits(key_extend) = true;
seg1.Flags.Bits(key_changeable) = true;
key.SegmentCount = 1;
// set id as primary key
tableDef.PrimaryKeyNum = keyNum;
// update schema table
dbdef.UpDateTableDef(td.Id);
# add key
keyNum = 0
key = dbdef.insertKey(td.id, keyNum)
key.segment(0).fieldNum = 0
key.segment(0).flags.bit8 = 1 # key_extend
key.segment(0).flags.bit1 = 1 # key_changeable
key.segmentCount = 1
# set id as primary key
tabledef.primaryKeyNum = keyNum
# update schema table
dbdef.updateTableDef(td.id)
// add key
$keyNum = 0;
$key = $dbdef->insertKey($td->id, $keyNum);
$key->segment(0)->fieldNum = 0;
$key->segment(0)->flags->bit8 = 1; // key_extend
$key->segment(0)->flags->bit1 = 1; // key_changeable
$key->segmentCount = 1;
// set id as primary key
$td->primaryKeyNum = $keyNum;
// update schema table
$dbdef->updateTableDef($td->id);
// Insert key
byte keyNum = 0;
KeyDef kd = dbdef.InsertKey(td.Id, keyNum);
KeySegment seg = kd[0];
seg.FieldNum = 0;
seg.Flags[key_extend] = true; // extended type
seg.Flags[key_changeable] = true; // updateable
kd.SegmentCount = 1;
// set id as primary key
td.PrimaryKeyNum = keyNum;
// update schema table
dbdef.UpDateTableDef(td.Id);
Please refer to keydef class reference (Japanese) for more details.
Table schema has been updated, but the table has not been created yet at this point.
The table will be created automatically when you open
it for the first time.
Drop database and table
database::dropTable
drops table. It will fail if there is some user who use this table.
dropTable
drops table, but it does not delete table definition in schema table.
Use dbdef::deleteTable(short tableIndex)
to delete table definition.
database::drop
drops database. It also drops schema table which is contined in the database.
database* db = database::create();
static const _TCHAR* uri = _T("tdap://localhost/tdtutorial2?dbfile=transactd_schema");
db->open(uri);
// drop table
db->dropTable(_T("users"));
// drop database
db->drop();
/* close and release database object */
database::destroy(db);
var db = new ActiveXObject('transactd.database');
var URI = 'tdap://localhost/tdtutorial2?dbfile=transactd_schema';
db.Open(URI);
// drop table
db.DropTable('users');
// drop database (then all tables in the database will be dropped.)
db.Drop();
db = Transactd::Database.new()
URI = 'tdap://localhost/tdtutorial2?dbfile=transactd_schema'
db.open(URI)
# drop table
db.dropTable('users')
# drop database (then all tables in the database will be dropped.)
db.drop()
$db = new Database();
const URI = 'tdap://localhost/tdtutorial2?dbfile=transactd_schema';
$db->open(URI);
// drop table
$db->dropTable('users');
// drop database (then all tables in the database will be dropped.)
$db->drop();
const string uri = "tdap://localhost/tdtutorial2?dbfile=transactd_schema";
Database db = new Database();
db.Open(uri);
// drop table
db.DropTable("users");
// drop database (then all tables in the database will be dropped.)
db.Drop();