Click here to view and discuss this page in DocCommentXchange. In the future, you will be sent there automatically.

SQL Anywhere 11.0.1 (中文) » MobiLink - 客户端管理 » 用于 MobiLink 的 SQL Anywhere 客户端 » 脚本式上载

 

脚本式上载示例

此示例说明如何设置提供冲突检测的脚本式上载。此示例创建脚本式上载所需的统一数据库和远程数据库、存储过程、发布和预订。所提供的示例既可以只用于阅读,也通过剪切和粘贴文本来运行该示例。

创建统一数据库

创建保存示例文件的目录。例如,将该目录命名为 scriptedupload。打开命令提示符,转到该目录。

(在此示例中,我们指定文件名,并假设这些文件在当前目录中。在实际的应用程序中,应指定文件的完整路径。)

运行以下命令创建统一数据库:

dbinit consol.db

下一步,运行以下命令定义统一数据库的 ODBC 数据源:

dbdsn -w dsn_consol -y -c "uid=DBA;pwd=sql;dbf=consol.db;eng=consol"

要将数据库用作统一数据库,必须运行一个安装脚本,此脚本添加 MobiLink 使用的系统表、视图和存储过程。以下命令将 consol.db 设置为统一数据库:

dbisql -c "dsn=dsn_consol" %sqlany11%\MobiLink\setup\syncsa.sql

打开 Interactive SQL 并使用 dsn_consol DSN 连接到 consol.db。运行下面的 SQL 语句。这些语句在统一数据库上创建 employee 表,向该表中插入值,然后创建所需的同步脚本。

CREATE TABLE employee (
   id      unsigned integer primary key,
   name    varchar( 256),
   salary  numeric( 9, 2 )
);

INSERT INTO employee VALUES( 100, 'smith', 225000 );
COMMIT;

CALL ml_add_table_script( 'default', 'employee', 'upload_insert',
       'INSERT INTO employee ( id, name, salary ) VALUES ( ?, ?, ? )' );

CALL ml_add_table_script( 'default', 'employee', 'upload_update',
       'UPDATE employee SET name = ?, salary = ? WHERE id = ?' );

CALL ml_add_table_script( 'default', 'employee', 'upload_delete',
       'DELETE FROM employee WHERE id = ?' );

CALL ml_add_table_script( 'default', 'employee', 'download_cursor',
       'SELECT * from employee' );
创建远程数据库

在命令提示符处,从示例目录运行以下命令创建远程数据库:

dbinit remote.db

下一步,运行以下命令定义 ODBC 数据源:

dbdsn -w dsn_remote -y -c "uid=dba;pwd=sql;dbf=remote.db;eng=remote"

在 Interactive SQL 中,使用 dsn_remote DSN 连接到 remote.db。运行下面一组语句,在远程数据库中创建对象。

首先,创建要同步的表。insert_time 列和 delete_time 列不进行同步,但这两列中包含上载存储过程确定要上载哪些行所使用的信息。

CREATE TABLE employee (
     id            unsigned integer primary key,
     name          varchar( 256),
     salary        numeric( 9, 2 ),
     insert_time   timestamp default '1900-01-01'
);

接下来,需要定义用于处理上载的存储过程和其它项目。需要分别为插入、删除和更新定义。

处理插入

首先,创建一个触发器,此触发器在插入每一行时设置该行上的 insert_time。此时间戳用于确定自上次同步以来是否插入了行。在 dbmlsync 应用从统一数据库下载的插入时将不触发此触发器,因为在此示例中稍后会将 FireTriggers 扩展选项设置为 off。通过下载插入的行所获得的 insert_time 值为 1900-01-01,这是创建 employee 表时定义的缺省值。此值应始终早于开始进度,以使这些行不会被视为新插入行,也不会在下一同步期间上载。

CREATE TRIGGER emp_ins AFTER INSERT ON employee
REFERENCING NEW AS newrow
FOR EACH ROW
BEGIN
    UPDATE employee SET insert_time = CURRENT TIMESTAMP
    WHERE id = newrow.id
END;

接下来,创建一个以结果集形式返回所有要上载的插入行的过程。此过程返回所有自上次成功上载以来(根据 insert_time)插入但随后未删除的行。上次成功上载的时间由 #hook_dict 表中的 start progress 值确定。此示例使用 dbmlsync 扩展选项 LockTables 的缺省设置,该设置可使 dbmlsync 锁定正在同步的表。因此,不必排除结束进度后插入的行:在构建上载时,表锁定可防止在结束进度后发生任何操作。

CREATE PROCEDURE employee_insert()
RESULT( id  unsigned integer,
          name varchar( 256 ),
          salary numeric( 9,2 )
      )
BEGIN
    DECLARE start_time timestamp;
    SELECT value
    INTO start_time
    FROM #hook_dict
    WHERE name = 'start progress as timestamp';

    // Upload as inserts all rows inserted after the start_time
    // that were not subsequently deleted
    SELECT id, name, salary
    FROM employee e
    WHERE insert_time > start_time AND
       NOT EXISTS( SELECT id FROM employee_delete ed  WHERE ed.id = e.id );

END;
处理更新

若要处理上载,必须确保根据构建上载时的开始进度使用正确的前映像。

首先,创建用于维护更新行的前映像的表。生成脚本式上载时将使用前映像。

CREATE TABLE employee_preimages (
   id           unsigned integer NOT NULL,
   name         varchar( 256),
   salary       numeric( 9, 2 ),
   img_time     timestamp default CURRENT TIMESTAMP,
   primary key( id, img_time )
);

接下来,创建一个触发器,此触发器在更新每一行时存储该行的前映像。与插入触发器一样,下载时不触发此触发器。

请注意,每次更新行时,此触发器均存储前映像行(除非两次更新时间太近,致使它们获得相同的时间戳)。乍看起来这种方法浪费了资源。只有在表中没有行的前映像时才存储前映像,在上载前映像后依靠 sp_hook_dbmlsync_upload_end 挂接将其删除,这种方法更具吸引力。

但是,sp_hook_dbmlsync_upload_end 挂接在这种方法中并不可靠。如果在发送上载后确认上载前的这段时间内由于发生硬件或软件故障停止了 dbmlsync,则不会调用此挂接,从而导致不会从前映像表中删除行,即使这些行已成功上载。另外,如果发生通信故障,dbmlsync 也不会从服务器收到上载确认。在这种情况下,传送到挂接的上载状态为 'unknown'。如果发生这种情况,则挂接将无法判断前映像表是应该清除还是应该保留原样。通过存储多个前映像,将始终能够根据构建上载时的开始进度选择正确的前映像。

CREATE TRIGGER emp_upd AFTER UPDATE OF name,salary ON employee
   REFERENCING OLD AS oldrow
   FOR EACH ROW
BEGIN
   INSERT INTO employee_preimages ON EXISTING SKIP VALUES(
      oldrow.id, oldrow.name, oldrow.salary, CURRENT TIMESTAMP );
END;

接下来,创建一个用于处理更新的上载过程。此存储过程返回一个所包含列数为其它脚本包含列数两倍的结果集:该结果集包含前映像(上次从 MobiLink 服务器接收或成功上载到 MobiLink 服务器时行中的值)和后映像(要输入到统一数据库中的值)。

前映像是 employee_preimages 中在 start_progress 后记录的最早的一组值。请注意,此示例不能正确处理删除后再次插入的现有行。在更完整的解决方案中,这些将作为更新上载。

CREATE PROCEDURE employee_update()
RESULT(
       preimage_id  unsigned integer,
       preimage_name varchar( 256),
       preimage_salary numeric( 9,2 ),
       postimage_id  unsigned integer,
       postimage_name varchar( 256),
       postimage_salary numeric( 9,2 )
      )
BEGIN
    DECLARE start_time timestamp;

    SELECT value
    INTO start_time
    FROM #hook_dict
    WHERE name = 'start progress as timestamp';

    // Upload as an update all rows that have been updated since 
    // start_time that were not newly inserted or deleted.
    SELECT ep.id, ep.name, ep.salary, e.id, e.name, e.salary
    FROM employee e JOIN employee_preimages ep 
        ON ( e.id = ep.id )
    // Do not select rows inserted since the start time. These should be
    // uploaded as inserts.
    WHERE insert_time <= start_time 
      // Do not upload deleted rows.
      AND NOT EXISTS( SELECT id FROM employee_delete ed  WHERE ed.id = e.id )
      // Select the earliest pre-image after the start time.
      AND ep.img_time = ( SELECT MIN( img_time )
            FROM employee_preimages
            WHERE id = ep.id
            AND img_time > start_time );
END;
处理删除

首先,创建一个用于维护删除行列表的表:

CREATE TABLE employee_delete (
    id           unsigned integer  primary key NOT NULL,
    name         varchar( 256 ),
    salary       numeric( 9, 2 ),
    delete_time  timestamp
);

接下来,创建一个触发器,此触发器在从 employee 表中删除行时填充 employee_delete 表。下载时不调用此触发器,因为稍后会将 dbmlsync 扩展选项 FireTriggers 设置为 false。请注意,此触发器假设不会再插入已删除行;所以它不处理多次删除的行。

CREATE TRIGGER emp_del AFTER DELETE ON employee
REFERENCING OLD AS delrow
FOR EACH ROW
BEGIN
     INSERT INTO employee_delete 
VALUES( delrow.id, delrow.name, delrow.salary, CURRENT TIMESTAMP );
END;

下一个 SQL 语句创建一个用于处理删除的上载过程。此存储过程返回一个包含统一数据库上要删除行的结果集。此存储过程使用 employee_preimages 表,这样如果对行进行更新然后再删除,上载的删除映像是成功下载或上载的最后一个映像。

CREATE PROCEDURE employee_delete()
RESULT( id  unsigned integer,
          name varchar( 256),
          salary numeric( 9,2 )
      )
BEGIN
    DECLARE start_time timestamp;

    SELECT value
    INTO start_time
    FROM #hook_dict
    WHERE name = 'start progress as timestamp';

   // Upload as a delete all rows that were deleted after the 
   // start_time that were not inserted after the start_time.
   // If a row was updated before it was deleted, then the row
   // to be deleted is the pre-image of the update.
    SELECT IF ep.id IS NULL THEN ed.id ELSE ep.id ENDIF,
           IF ep.id IS NULL THEN ed.name ELSE ep.name ENDIF,
           IF ep.id IS NULL THEN ed.salary ELSE ep.salary ENDIF
    FROM employee_delete ed LEFT OUTER JOIN employee_preimages ep
          ON( ed.id = ep.id AND ep.img_time > start_time )
    WHERE
      // Only upload deletes that occurred since the last sync.
      ed.delete_time > start_time
      // Don't upload a delete for rows that were inserted since 
      // the last upload and then deleted.
    AND NOT EXISTS ( 
      SELECT id
         FROM employee e
         WHERE e.id = ep.id AND e.insert_time > start_time )
    // Select the earliest preimage after the start time.
    AND ( ep.id IS NULL OR ep.img_time = (SELECT MIN( img_time )
                                          FROM employee_preimages
                                          WHERE id = ep.id
                                           AND img_time > start_time ) );
END;
清除前映像表

接下来,创建 upload_end 挂接,以在上载成功后清理 employee_preimage 和 employee_delete 表。此示例使用 dbmlsync 扩展选项 LockTables 的缺省设置,以便同步时锁定这些表。因此,您不必担心在表中留下在 end_progress 后发生的操作的行。锁定会防止发生这些操作。

CREATE PROCEDURE sp_hook_dbmlsync_upload_end()
BEGIN
    DECLARE val   varchar(256);
    
    SELECT value
    INTO val 
    FROM #hook_dict
    WHERE name = 'upload status';
    
    IF val = 'committed' THEN
      DELETE FROM employee_delete;
      DELETE FROM employee_preimages;
    END IF;
END;
创建发布、MobiLink 用户和预订

名为 pub1 的发布使用脚本式上载语法 (WITH SCRIPTED UPLOAD)。它为 employee 表创建一个项目,然后注册刚创建的三个存储过程用于脚本式上载。它创建一个名为 u1 的 MobiLink 用户和一个在 v1 和 pub1 之间的预订。将扩展选项 FireTriggers 设置为 off 以防止在远程数据库上应用下载时触发触发器,从而防止下次同步时上载下载的更改。

CREATE PUBLICATION pub1 WITH SCRIPTED UPLOAD (
TABLE employee( id, name, salary ) USING (
   PROCEDURE employee_insert FOR UPLOAD INSERT, 
   PROCEDURE employee_update FOR UPLOAD UPDATE, 
   PROCEDURE employee_delete FOR UPLOAD DELETE, 
      )
)

CREATE SYNCHRONIZATION USER u1;

CREATE SYNCHRONIZATION SUBSCRIPTION TO pub1 FOR u1
TYPE 'tcpip'
ADDRESS 'host=localhost'
OPTION FireTriggers='off';
演示脚本式上载

使用脚本式上载连接到远程数据库并插入要同步的数据。例如,在 Interactive SQL 中针对远程数据库运行以下 SQL 语句:

INSERT INTO employee(id, name, salary) VALUES( 7, 'black', 700 );
INSERT INTO employee(id, name, salary) VALUES( 8, 'anderson', 800 );
INSERT INTO employee(id, name, salary) VALUES( 9, 'dilon', 900 );
INSERT INTO employee(id, name, salary) VALUES( 10, 'dwit', 1000 );
INSERT INTO employee(id, name, salary) VALUES( 11, 'dwit', 1100 );
COMMIT;

在命令提示符处,启动 MobiLink 服务器:

mlsrv11 -c "dsn=dsn_consol" -o mlserver.mls -v+ -dl -zu+

使用 dbmlsync 启动同步:

dbmlsync -c "dsn=dsn_remote" -k -uo -o remote.mlc -v+

现在可验证是否已上载插入。

示例清除

若要在完成示例后清理计算机,请执行以下步骤:

mlstop -h -w
dbstop -y -c eng=consol
dbstop -y -c eng=remote

dberase -y consol.db
dberase -y remote.db

del remote.mlc mlserver.mls