The database dialect is the software layer in SymmetricDS that contains routines specific to a database platform. SymmetricDS implements data replication using a general architecture that works across platforms. When a task needs specific details for a database, it calls the database dialect to perform them. Examples of database-specific tasks handled by the dialect are installing triggers, querying metadata, and altering tables. By writing an implementation for the dialect interfaces, support for a new database can be added to SymmetricDS.
Evaluating a New Database
Before implementing a new dialect, evaluate the database for its capabilities to see which features of SymmetricDS it will support.
- Data Capture
- The data capture system requires database triggers. If the database doesn’t support triggers, then it can’t be a source of data, but it can still be a target to load data. The triggers record data changes in comma separated value (CSV) format, which requires functions to concatenate strings and replace quotes with escaped quotes.
- Transaction Identifier
- When a row of data is captured, the transaction it belongs to is also recorded. This enables SymmetricDS to ensure that all the data for the same transaction is loaded together. This feature requires a transaction identifier from the database.
- Conditional Sync
- The sync_on_x_condition columns of the Trigger table allow a user to specify an expression that is built into the trigger. This feature requires the database to support a procedural language that allows an “if” statement and condition.
- Update Loop Prevention
- With a data capture system that records changes and a data loader system that updates data, a mechanism to prevent an update loop is needed. This feature requires a way to store state that is scoped to the login session or transaction, such as a session variable or private temporary table.
- CLOB Sync
- To capture a character large object (CLOB) as part of a transaction, the database needs functions for handling CLOBs to concatenate them and replace quotes with escaped quotes. Instead of capturing CLOBs within the transaction, there is an option to stream them from the database when batching instead.
- BLOB Sync
- To capture a binary large object (CLOB) as part of a transaction, the database needs functions for encoding BLOBs as a varchar using known format such as base64 or hex. Instead of capturing BLOBs within the transaction, there is an option to stream them from the database when batching instead.
Database Dialect Classes
At the highest level, the database dialect is available in the symmetric-client project that is responsible for interfacing with a database platform. The classes are found in the org.jumpmind.symmetric.db package.
Class | Interface | Responsibility |
---|---|---|
AbstractSymmetricDialect | ISymmetricDialect | Main database dialect class that handles calls and contains the trigger template and platform classes. |
AbstractTriggerTemplate | Generate the data definition language statements that create database triggers on tables for data capture | |
JdbcSymmetricDialectFactory | Bootstraps the dialect by detecting the platform and instantiating the correct AbstractSymmetricDialect |
At a lower level, the database dialect in the symmetric-db project is responsible for generating data definition language (DDL) and data manipulation language (DML) statements. The classes are found in the org.jumpmind.db.sql package.
Class | Interface | Responsibility |
---|---|---|
AbstractDdlBuilder | IDdlBuilder | Generates statements to create and alter tables. It can process schema changes for a table and determine the alter statements needed. |
DmlStatement | Generates the statements to insert, update, and delete data in tables. |
At the lowest level, the database dialect in the symmetric-jdbc project is responsible for using JDBC calls to the database to satisfy service calls. The classes are found in the org.jumpmind.db.platform package.
Class | Interface | Responsibility |
---|---|---|
AbstractJdbcDatabasePlatform | The platform class contains the SQL template, DDL reader, and DLL builder. | |
AbstractJdbcDdlReader | IDdlReader | Reads metadata about tables and columns |
AbstractSqlTemplate | ISqlTemplate | Runs queries and updates on database |
JdbcDatabasePlatformFactory | Bootstraps the platform by detecting the database and instantiating the correct AbstractJdbcDatabasePlatform |
Database Dialect Implementation
A typical database dialect would extend the core classes, providing an implementation in its own package. The extended classes follow a naming convention with a prefix for the database name. For example, here are the classes used for the MySQL database dialect:
Project | Package | Implementation |
---|---|---|
symmetric-client | org.jumpmind.symmetric.db.mysql | MySqlSymmetricDialect |
MySqlTriggerTemplate | ||
symmetric-db | org.jumpmind.db.platform.mysql | MySqlDdlBuilder |
MySqlDmlStatement | ||
symmetric-jdbc | org.jumpmind.db.platform.mysql | MySqlDatabasePlatform |
MySqlDdlReader | ||
MySqlJdbcSqlTemplate |
Finally, to bootstrap the new platform and dialect, the JdbcDatabasePlatformFactory and JdbcSymmetricDialectFactory classes are modified to detect the database and instantiate the platform and dialect instances.
Testing
After implementing a new dialect, you can run the JUnit test cases to verify it is working. The tests will only exercise functions that the dialect says it supports. For example, if the dialect returns false for isBlobSyncSupported(), then the BLOB tests are skipped.
Integration tests are performed between two instances of the replication engine. A root instance installs triggers and captures changes, while the client instance loads the changes. The db-test.properties file defines which databases will be tested and how to connect to them. For example, if you want to test MySQL as the root and H2 as the client, here are the sections of the file:
test.root=mysql test.client=h2 mysql.db.driver=com.mysql.jdbc.Driver mysql.db.user=root mysql.db.password=admin mysql.client.db.url=jdbc:mysql://localhost/SymmetricClient?tinyInt1isBit=false mysql.root.db.url=jdbc:mysql://localhost/SymmetricRoot?tinyInt1isBit=false h2.db.driver=org.h2.Driver h2.db.user=sa h2.db.password= h2.client.db.url=jdbc:h2:file:target/clientdbs/client h2.root.db.url=jdbc:h2:file:target/rootdbs/root
The tests will run from Maven with the “test” goal. Properties can be specified on the command line to override which database to use for the root and client. If no properties are specified, then the settings from db-test.properties is used.
mvn -Dtest.client=mysql -Dtest.root=h2 test
If you using an integrated developer environment like Eclipse, you can run individual test case classes. From Eclipse, right click on the class and select Run As->JUnit Test. The run configuration for the integration test looks like this:
Project: | symmetric-server | |
Test Class: | org.jumpmind.symmetric.test.SimpleIntegrationTest | |
Test Runner: | JUnit 5 |