一、引言
使用过PowerBuilder(以下简称PB)的编程人员都知道,PB允许用户在程序运行过程中应用数据窗口方法或属性表达式查看或修改数据窗口对象的属性,还可以动态的创建数据窗口对象,并将这种在程序运行过程中被修改或创建的数据窗口对象称为动态数据窗口对象。同样,PB提供的数据管道技术也可以在程序运行时进行修改与调整,笔者称其为“动态”数据管道。
二、数据管道技术
PB为在不同数据库表之间移动数据提供了功能强劲的数据管道技术(Data Pipeline)。通过数据管道,我们可以实现将一个或多个源表中的数据复制到一个新的或已经存在的目标表中,这种数据转移不仅可以在同一个数据库中进行,而且可以跨数据库,甚至在不同的DBMS之间进行。
数据管道有两种使用方式:一是在数据管道画笔中定义了数据管道后直接运行数据管道,这种方式方便了开发人员开发和测试应用程序,例如,它可以把服务器中的数据下载到本地机,然后不依赖于服务器地进行前期开发工作;二是在应用程序中通过编写代码使用数据管道,这种方式提供了灵活运用数据管道的手段。本文着重讲述第二种方式,即在应用程序中实现数据管道技术,下面按五个步骤进行说明:
1、 创建应用程序所需对象
为了在应用程序中实现数据管道,需建立管道对象和支撑用户对象。
创建管道对象:管道对象中定义了应用程序需执行的数据定义及数据访问,可在Powerbuilder提供的pipeline画笔中创建并定义它的特征(如图)。
在数据管道画笔工作区上部指定数据管道选项,这些选项决定了将以何种方式运行管道。选项包括:
“Table”:指定目的表的名称
“Options”:定义管道操作方式,选项包括:
Create - Add Table |
在目的数据库中创建指定的目的表,目的数据库中不能存在同名表 |
Replace - Drop/Add Table |
在目的数据库中创建指定的目的表,若同名表存在则首先删除该表 |
Refresh - Delete/Insert Rows |
数据管道将删除目的数据库中指定目的表中的所有数据,然后再插入从源表选择的数据 |
Append - Insert Rows |
目的表中原有数据被保留,然后再插入从源表选择的数据 |
Update - Update/Insert Rows |
根据目的表中键值修改目的表中匹配的记录 |
“Key”:修改目的表主键的名称
“Max Errors”:定义允许出现的最多错误个数
“Commit”:定义将多少条记录作为一个事务提交
“Extended Attributes”:选择此复选框后,在复制数据的同时也复制表的扩展属性(这些属性保存在PowerBuilder资源库中)。
创建支撑用户对象:为了提供对属性、事件及函数的支持,需创建一个继承PB管道系统对象(Pipeline system object)的用户对象。管道系统对象包括了不同的属性、事件及函数,在程序运行时可以用来管理一个管道对象。下表列出了这些属性、事件及函数。
属性 |
DataObject , RowsRead , RowsWriten , RowsInError , Syntax |
事件 |
PipeStart , PipeMeter , PipeEnd |
函数 |
Start , Repair , Cancel |
创建用户对象的过程为:在New对话框的Object标签页中选择Standard Class,然后在Select Standard Class Type对话框的Types列表框中选择pipeline,如图所示,单击OK按钮进入用户对象画笔工作区。根据需要对用户对象进行修改,如为用户对象的相应事件编写事件脚本,定义函数及变量等,最后保存此用户对象。
2、 初始化操作
定义了数据管道及用户支撑对象,为了让管道在应用程序中运转起来必须完成一些初始化工作,为应用程序处理管道运行作准备。
下面以一个实例来说明初始化过程。现在有一个MECHANICSTEST.DB是源数据库,目标数据库为Oracle,这些初始化包括:
1. 初始化事务对象并连接数据库
//事先声明了两个事务变量itrans_source和itrans_destination作为源数据库与目标数据库的连接
//创建一个新的事务对象实例,并保存在itrans_source事务变量中
itrans_source = create transaction
//此处为itrans_source事务对象赋值
…
//与源数据库连接
CONNECT USING itrans_source;
//创建一个新的事务对象实例,并保存在itrans_destination事务变量中
transaction ltrans_destination
itrans_destination = create transaction
//此处为itrans_destination事务对象赋值
…
//与目标数据库连接
CONNECT USING ltrans_destination;
2. 创建标准类用户对象实例
假设此处已经创建了一个名为u_pipeline_sample的支撑用户对象,使用这个用户对象必须事先为它说明一个变量
u_pipeline_sample iuo_pipe_sample
然后在用户事件中创建一个实例并将它保存在iuo_pipe_sample变量中。
iuo_pipe_sample = create u_pipeline_sample
3. 将数据管道对象与标准类用户对象实例联系起来
假设已经建立了一个管道对象pipe_sample,可以设置iuo_pipe_sample变量的DataObject属性将管道对象与用户对象实例联系起来。DataObject属性的数据类型为String,用于指定与数据管道用户对象相关联的数据管道对象名。DataObject属性只能在代码中设置:
iuo_pipe_sample.dataobject = ’pipe_sample’
3、 启动数据管道
在做好初始化工作后,就可以启动数据管道操作了。利用支撑用户对象的Start函数来启动指定的管道。
Start()函数的语法格式为:
pipelineobject.Start (sourcetrans, destinationtrans,errordatawindow {,arg1, arg2,..., argn}
其中:
pipelineobject |
要被执行数据管道对象的数据管道用户对象名称 |
sourcetrans |
连接到源数据库的事务对象名 |
destinationtrans |
连接到目的数据库的事务对象名 |
errordatawindow |
数据窗口控件名,该控件用于显示数据管道运行过程中出现的错误。 |
arg1, arg2,..., argn |
可选参数,对应于定义数据管道数据源时SELECT语句所需的检索参数。 |
返回值:函数执行成功返回1。否则返回一个错误代码,可以根据错误代码对错误进行相应的操作。示例脚本:
// dw_pipe_errors为显示错误信息的数据窗口控件
integer li_pipe_return
li_pipe_return=iuo_pipe_sample.start(itrans_source,itrans_destination,dw_pipe_errors)
choose case li_pipe_return
case –1
messagebox(“”,”打不开数据管道”)
return
case –2
messagebox(“”,”列数太多”)
return
case –3
messagebox(“”,”要创建的表已经存在”)
return
…
end choose
4、 处理行错误
管道在运行过程中,可能因某种原因不能将某一行写入目标表中。数据管道把这些错误放置于Start函数调用参数中指定的数据窗口控件中,此数据窗口控件为每一行提供了一个错误信息列。
数据窗口控件中一旦有错误行,用户可以根据需要进行修复与删除。修复调用支撑用户对象的repair()函数;删除使用错误数据窗口的reset()函数。示例如下:
//修复错误行
if iuo_pipe_sample.repair(itrans_destination)<> 1 then
MessageBox(“Pipeline修复操作”,“程序运行错误”,Exclamation!)
Return
End if
//放弃错误行
dw_pipe_errors.Reset()
5、 结束管道操作,清理前面创建的实例
当应用程序完成了所有的管道操作后,应释放管道操作开始时获得的一些资源。包括:清除所创建的支撑用户对象实例,关闭与源数据库和目标数据库之间的连接,清除源事务对象实例和目标事务对象实例。示例脚本:
destroy iuo_pipe_sample
DISCONNECT USINGitrans_source;
destroy itrans_source
DISCONNECT USINGitrans_destination;
destroy itrans_destination
三、“动态”数据管道的应用
前面已将数据管道的应用进行了简单说明,下面将介绍如何在程序中修改和调整数据管道。
注意到支撑用户对象中有一个Syntax属性,该属性的数据类型为String,保存用于创建数据管道对象的语法。如果将Syntax输出,将是类似与下面的文本:
PIPELINE(source_connect=Book,destination_connect=Desi,type=create,commit=100,errors=100,keyname="person_x")
SOURCE(name="person",COLUMN(type=varchar,name="person_no",dbtype="char(8)",key=yes,nulls_allowed=no)
COLUMN(type=varchar,name="dept_no",dbtype="char(3)",nulls_allowed=no)
……
如果利用字符串函数(比如Mid()、Pos()、Len())对Syntax属性进行操作,应用程序可以动态修改数据管道对象语法,从而实现不同的要求。例如将上面文本中的“type=create”改为“type=update”就改变了数据管道的操作方式。
现在我们知道了动态修改数据管道的方法,那么我们能否不建立数据管道对象而只应用支撑用户对象实现管道操作吗?答案是肯定的,方法是通过构造符合Pipeline语法的文本(有关pipeline的语法可以把在画笔中定义的Pipeline对象导成文本文件进行分析),将此文本赋值给Syntax属性,这样不必定义数据管道对象仍然可以进行管道操作。
下面是笔者编写的一个从Forpro数据表向Oracle数据库复制数据的函数,由于应用了“动态”数据管道,无需在画笔中创建数据管道对象,从而实现任意表的数据传递。
Function Integeruf_foxtoora(transaction atrans_source,transaction atrans_destination, stringas_fox_tablename , string as_ora_username ,string as_ora_tablename , integerai_pipe_type )
其中:
atrans_source |
与源数据库建立连接的事务对象实例 |
atrans_destination |
与目标数据库建立连接的事务对象实例 |
as_fox_tablename |
Foxpro数据表名(不加扩展名.dbf) |
as_ora_username |
Oracle数据库的用户名 |
as_ora_tablename |
Oracle数据库的表名 |
ai_pipe_type |
数据管道操作类型 |
返回值:成功返回1,发生错误返回错误代码。
脚本如下:
int i ,li_columncount , li_return
stringls_pipeline1,ls_pipeline2,ls_pipeline3,ls_pipeline4,ls_pipesyn
stringls_pipetype[5] = {"create" , "replace" ,"refresh" , "append" , "update" }//定义数据管道操作方式
stringls_foxtype,ls_oratype,ls_colname,ls_foxdbtype,ls_oradbtype,ls_inital,ls_foxtype1
stringls_insertcol,ls_sel,errors
pipelinepipe_foxtoora
datastore lds_1
ls_sel="SELECT* FROM " + as_fox_tablename
lds_1=createdatastore
//创建动态数据存储对象,用以获取字段名及数据类型
lds_1.create(atrans_source . syntaxfromsql ( ls_sel ,"style(type=grid)",errors))
if len(errors)>0then return -99 //若动态数据存储创建失败,返回错误代码-99
li_columncount =Integer(lds_1.Object.DataWindow.Column.Count)
pipe_foxtoora=createpipeline
ls_pipeline1="PIPELINE(source_connect=foxtoora,destination_connect="+as_ora_username+",type="+ls_pipetype[ai_pipe_type]+",commit=100,errors=100)"
ls_pipeline2="SOURCE(name=~""+as_fox_tablename+"~","
ls_pipeline3="RETRIEVE(statement=~"PBSELECT(VERSION(400)TABLE ( NAME = ~~~""+as_fox_tablename + "~~~")"
ls_pipeline4="DESTINATION(name=~""+as_ora_tablename+"~","
//循环获取字段名及数据类型,构造动态数据管道语法
for i=1 toli_columncount
ls_colname=string(lds_1.describe("#"+string(i)+".dbname"))
ls_foxtype1=string(lds_1.describe("#"+string(i)+".coltype"))
//判断字段数据类型,根据数据类型构造动态数据管道语法
CHOOSE CASEupper(left(ls_foxtype1,3))
CASE "CHA"
ls_foxtype="char"
ls_foxdbtype="~"C"+right(ls_foxtype1,len(ls_foxtype1)-pos(ls_foxtype1,"(")+1)+"~""
ls_oratype="varchar"
ls_oradbtype="~"VARCHAR2"+right(ls_foxtype1,len(ls_foxtype1)-pos(ls_foxtype1,"(")+1)+"~""
ls_inital="~"spaces~""
CASE "DEC"
ls_foxtype="decimal"
ls_foxdbtype="~"N"+"(20,"+right(ls_foxtype1,len(ls_foxtype1)-pos(ls_foxtype1,"("))+"~""
ls_oratype="decimal"
ls_oradbtype="~"NUMBER"+"(20,"+right(ls_foxtype1,len(ls_foxtype1)-pos(ls_foxtype1,"("))+"~""
ls_inital="~"0~""
CASE "DAT"
ifupper(ls_foxtype1)="DATE" then
ls_foxtype="date"
ls_foxdbtype="~"D~""
elseif upper(ls_foxtype1)="DATETIME" then
ls_foxtype="datetime"
ls_foxdbtype="~"T~""
endif
ls_oratype="datetime"
ls_oradbtype="~"DATE~""
ls_inital="~"today~""
CASE "INT"
ls_foxtype="decimal"
ls_foxdbtype="~"N(8,0)~""
ls_oratype="decimal"
ls_oradbtype="~"NUMBER(8,0)~""
ls_inital="~"0~""
CASE "LON"
ls_foxtype="long"
ls_foxdbtype="~"I~""
ls_oratype="double"
ls_oradbtype="~"FLOAT~""
ls_inital="~"0~""
CASE "DOU"
ls_foxtype="decimal"
ls_foxdbtype="~"T~""
ls_oratype="decimal"
ls_oradbtype="~"NUMBER(20,8)~""
ls_inital="~"0~""
CASE "BIT"
ls_foxtype="bit"
ls_foxdbtype="~"T~""
ls_oratype="double"
ls_oradbtype="~"FLOAT~""
ls_inital="~"0~""
CASE "NUM"
ls_foxtype="double"
ls_foxdbtype="~"B~""
ls_oratype="double"
ls_oradbtype="~"FLOAT~""
ls_inital="~"0~""
END CHOOSE
ls_pipeline2+="COLUMN(type="+ls_foxtype+",name=~""+ls_colname+"~",dbtype="+ls_foxdbtype+",nulls_allowed=no)"+"~r~n"
ls_pipeline3+="COLUMN(NAME=~~~""+as_fox_tablename+"."+ls_colname+"~~~")"
ls_pipeline4+="COLUMN(type="+ls_oratype+",name=~""+ls_colname+"~",dbtype="+ls_oradbtype+",nulls_allowed=no,initial_value="+ls_inital+")"+"~r~n"
next
ls_pipeline2+=+")"
ls_pipeline3+=")~")"
ls_pipeline4+=+")"
ls_pipesyn=ls_pipeline1+"~r~n"+ls_pipeline2+"~r~n"+ls_pipeline3+"~r~n"+ls_pipeline4
messagebox("",ls_pipesyn)
pipe_foxtoora.syntax=ls_pipesyn//将数据管道语法字符串赋值给Syntax属性
li_return=pipe_foxtoora.start(atrans_source,atrans_destination,lds_1)//启动数据管道
destroy lds_1
destroypipe_foxtoora
return li_return //返回数据管道执行成功标志或错误代码
四、结论
在移动计算日益普及的今天,各种数据相互传递已经成为应用程序的普遍要求。数据管道技术提供了在数据库内部、数据库之间,甚至不同的数据库管理系统之间快速复制数据的简便途径。而使用“动态”数据管道可以使程序具有较强的适应性,操作更加灵活,进而能够满足数据移动中的特殊要求。