马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PowerBuilder SyntaxFromSQL 全解:动态生成 DataWindow 的样式参数、报错形态与生成语法串的真实结构(PB10 基准 · PB12.5 实测)
阅读说明
- 适用版本:PB10 基准(代码按 PB10 语法书写);PB 12.5 实机实测。两版差异见第九节。
- 支持数据库:SQL Server 2008 / 2008 R2。本文代码通过 SNC SQL Native Client(OLE DB) 接口连接(DBParm 指定 Provider='SQLNCLI10'),必须连库才有意义。
- 操作系统与环境要求:Windows 7 及以上;客户机需安装 SQL Server Native Client 10.0;登录账号需要建表权限(示例会重建一张演示表)。
- 难度系数:★★★☆☆
- 其它:建议先读《PowerBuilder DataWindow 入门》与《PowerBuilder 事务对象全解》;文中的连接参数(服务器、库、账号)请替换成你自己的环境值。
一、它是干什么的
写程序经常遇到这种需求:用户选了张表、选了几个字段,程序要在运行时把结果展示出来。表是运行时才知道的,画板里画好的 DataWindow 帮不上忙。这时有两条路:一是手写 DataWindow 语法串(那些 datawindow(units=0 ...)、table(column=(type=long ...)) 的长串,写过的人都知道有多苦),二是让 PowerBuilder 替你写——这就是 SyntaxFromSQL。
它的输入只要两样东西:一条 select 语句,一段「表现串」(描述想要什么外观);输出是一段完整的 DataWindow 语法串。拿到语法串后用 Create() 一造、SetTransObject() 一接、Retrieve() 一查,一个运行时才诞生的 DataWindow 就活了。三步走:
- SyntaxFromSQL 生成语法串;
- Create 把语法串变成可用的 DataWindow(或 DataStore);
- SetTransObject + Retrieve 取数。
实际项目里它最常出现在三类地方:一是自定义查询窗口——用户勾字段、填条件,程序现场拼 select、现场生成、现场展示;二是按库生成管理界面——表结构在建表后就定了,用一次 SyntaxFromSQL 把每张表生成一个可维护的 DataStore,省掉一批手工画板作业;三是给后台批处理快速搭容器——DataStore 不占界面资源,配合第六节的三类失败形态检查,生成失败能第一时间拿到原因。共同点是:select 语句在运行时才确定,画板帮不上忙。
与《Modify 与 Describe》那篇的分工:Modify/Describe 改的是已存在的 DataWindow 的属性;SyntaxFromSQL 解决的是从零生成。本机全程实测的结论先放在这里:这个函数本身很好用,但表现串的键值规则比想象中严苛(引号、未知键、很多想当然的键全都会报错),失败形态有三类,判成败只看返回串长度。
二、最小可运行示例
先看完整代码。示例连上库、重建一张演示表 t_s76,然后一行 select 生成一个 grid 风格的 DataStore 并取数(连接参数请换成你自己的):
- // 输入值在前面集中定义
- string ls_sql // 取数 SQL:决定取什么数据
- string ls_style // 表现串:决定长什么样
- string ls_syn // 收 SyntaxFromSQL 生成的语法串
- string ls_err // 收错误信息(函数会写入)
- long ll_rows // 检索到的行数
- datastore lds // 后台数据容器
- // 标准连接(SNC 接口连 SQL Server)
- SQLCA.DBMS = 'SNC SQL Native Client(OLE DB)'
- SQLCA.LogId = '你的账号'
- SQLCA.LogPass = '你的口令'
- SQLCA.ServerName = '你的服务器,端口'
- SQLCA.Database = '你的库名'
- SQLCA.AutoCommit = true
- SQLCA.DBParm = "Provider='SQLNCLI10',DataSource='你的服务器,端口'"
- CONNECT USING SQLCA;
- if SQLCA.SQLCode <> 0 then
- MessageBox('连接失败', SQLCA.SQLErrText)
- return
- end if
- // 建一张演示表(已存在则先删)
- EXECUTE IMMEDIATE "if object_id('dbo.t_s76') is not null drop table dbo.t_s76" USING SQLCA;
- EXECUTE IMMEDIATE "create table dbo.t_s76(id int primary key, name nvarchar(10), qty int)" USING SQLCA;
- EXECUTE IMMEDIATE "insert into dbo.t_s76 values(1,'A',10)" USING SQLCA;
- EXECUTE IMMEDIATE "insert into dbo.t_s76 values(2,'B',20)" USING SQLCA;
- // 核心:生成语法串
- ls_sql = 'select id, name, qty from dbo.t_s76'
- ls_style = 'style(type=grid)'
- ls_syn = SQLCA.SyntaxFromSQL(ls_sql, ls_style, ls_err)
- if Len(ls_syn) = 0 then
- MessageBox('生成失败', ls_err) // 失败时 err 里有原因
- return
- end if
- // 造出来、接上事务、取数
- lds = create datastore
- lds.Create(ls_syn, ls_err)
- lds.SetTransObject(SQLCA)
- ll_rows = lds.Retrieve()
- MessageBox('结果', '行数=' + String(ll_rows))
复制代码
本机实测这段逻辑(连接真实库)输出 行数=2。表现串 style(type=grid) 是最短合法写法;把 grid 换成 tabular 或 freeform 同样可用。
三、生成的语法串长什么样
返回的语法串是一段多行文本,本机 3 列 grid 约 5.4KB、4 列约 7.1KB、1 列约 2KB——平均每列多出 1.1KB 上下,因为每个列要生成「table 里的列定义 + 明细带上的列控件 + 列头文本对象」三段。骨架如下(按真实输出节选、注释为笔者所加):
- release 12.5; ← 版本头(PB10 生成的则是 release 10;)
- datawindow(units=0 timer_interval=0 color=1073741824 processing=1 ...) ← 头部属性,processing=1 即 grid
- table(
- column=(type=long update=yes key=yes updatewhereclause = yes name=id dbname="t_s76.id")
- column=(type=char(10) update=yes updatewhereclause = yes name=name dbname="t_s76.name") ← nvarchar(10) 映射成 char(10)
- column=(type=long update=yes updatewhereclause = yes name=qty dbname="t_s76.qty")
- retrieve="select id, name, qty from dbo.t_s76" ← 你的 SQL 原样进 retrieve
- update="t_s76" updatewhere=1) ← 可更新表 + UpdateWhere 默认 1
- header(height=68)
- detail(height=80)
- column(band=detail id=1 x="14" y="8" height="64" width="274" ...) ← 明细带列控件(后接一长串字体/背景属性)
- text(band=header ... name=id_t ...) ← 列头文本对象
- htmltable(border="0" ...) ← 结尾的 HTML 导出配置
复制代码
四个要点:
- 版本头会带版本号:本机 PB10 生成 release 10;、PB12.5 生成 release 12.5;。也就是说 A 版本生成的语法串拿到 B 版本去解析,可能撞上 release 号或长属性行不兼容的问题(把语法串落盘成 .srd 跨版本导入是踩过的坑,别这么干)。
- 更新属性是自动的:主键列会自动带 update=yes key=yes,可更新表自动是 update="t_s76",并且 updatewhere=1(Key+修改列,默认态与《更新机制全解》篇实测的 UpdateWhere=1 语义一致)。所以 SyntaxFromSQL 生成的对象天生可更新。注意属性字样是 update=,没有 updatetable=;但 Describe('datawindow.table.updatetable') 能取到表名——属性名和语法串字样不是一回事。
- 列类型做了映射:数据库类型不原样照搬(见第五节对照表)。
- grid 与 tabular/freeform 的差别全在头部:processing=1 是 grid,tabular 与 freeform 都是 processing=0,靠 band/控件的坐标布局区分。
四、表现串语法规则(实测)
表现串 = 以空格分隔的若干「节」,每节形如 节名(键=值 键=值 ...)。规则逐条实测如下:
| 写法 | 结果 | | style(type=grid) / tabular / freeform | 都可用 | | style(type=nosuchstyle) | 报 invalid,返回空串 | | STYLE(TYPE=GRID) 全大写 | 可用(大小写不敏感) | | datawindow(color=6710886) | 可用,背景色写进生成串 | | datawindow(units=1) | 可用 | | datawindow(color="6710886") 值加引号 | 报 invalid | | datawindow(grid.lines=1) | 报 invalid(别把语法串里的属性想当然搬进来) | | datawindow(nosuchkey=999) | 报 invalid,未知键不静默忽略 | | column(border=2) / column(color=255) | 可用 | | column(width=400) / column(x=300) | 报 invalid | | text(color=255) | 可用 | | text(band=header color=255) / column(band=detail id=1 border=2) | 报 invalid | | 多节组合 style(type=grid) column(border=2) text(color=255) | 可用 |
三条总结:值不加引号;不认识的键直接报错(不静默吞);column()/text() 里只认少量键(本机实测 border、color 可用;width、x、band、id 都拒收)。想精细排版,别指望表现串,生成后用 Modify(那套在《Modify 与 Describe》篇)。
五、数据库类型到语法串的映射(实测)
| SQL Server 列类型 | 语法串里的类型 | | int | long | | nvarchar(10) | char(10)(nvarchar 字样不出现) | | varchar(50) | char(50) | | money | decimal | | numeric(10,2) | decimal | | datetime | datetime | | smallint | 未以原样出现(映射为 PB 数值类型) |
两个附带的实测点:identity 自增列在语法串里保留 identity 字样(自增属性跟着走);主键列自动生成 update/key 属性(见第三节)。
六、err 参数与三类失败形态
err 是引用参数,函数必定写入它:成功时覆盖为空串——哪怕你传进去时预置了内容也会被清掉;失败时写入原因。所以判断成败只看返回串是否为空,不要靠 err 的旧值。
- // 三类失败,一次看全(每类单独调一次)
- string ls_syn, ls_err
- long ll_len
- // 类 1:SQL 错误(表不存在 / 列不存在 / SQL 语法错)
- ls_syn = SQLCA.SyntaxFromSQL('select id from dbo.t_nosuch76', 'style(type=grid)', ls_err)
- ll_len = Len(ls_syn) // = 0,ls_err = 驱动原文(SQLSTATE + 描述 + 原 SQL)
- // 类 2:表现串非法(值加了引号、键不存在等)
- ls_syn = SQLCA.SyntaxFromSQL('select id from dbo.t_s76', 'style(type=grid) column(width=400)', ls_err)
- ll_len = Len(ls_syn) // = 0,ls_err = Style string '...' is not valid.
- // 类 3:事务未连接(没 CONNECT 就调)
- transaction lt
- lt = create transaction
- lt.DBMS = 'SNC SQL Native Client(OLE DB)'
- // ……赋了参数但没 CONNECT
- ls_syn = lt.SyntaxFromSQL('select id from dbo.t_s76', 'style(type=grid)', ls_err)
- ll_len = Len(ls_syn) // = 0,ls_err = Transaction not connected,SQLCode = -1
复制代码
类 1 的错误文本是数据库驱动的原话(本机 SQL Server 环境下含 SQLSTATE 42S02/42000/42S22 与出错的 SQL 语句),排错最方便;类 2 的错误文本会把整个表现串回显在 Style string '...' is not valid. 里,一眼能看出是哪个串写坏了。上面的代码块是分段讲解形态,本机对三类各自单独跑过、读数一致。
再补一个容易混淆的分工:SyntaxFromSQL 的 err 参数收的是生成阶段的错误;而 SQLCA.SQLErrText 是你后续用这个连接 Retrieve 失败时的取数错误通道。排错时先看 err 拿到生成失败的原因,别急着翻 SQLErrText。
七、SQL 写法对生成结果的影响
- order by 会保留在 retrieve= 里,不生成 sort= 属性。也就是说生成的对象取数即有序,但那是 SQL 排的序;想在运行期改排序,自己 SetSort + Sort(参考《排序与过滤全解》)。
- 计算列没问题:select id, qty * 2 as dbl from ... 正常生成,dbl 按表达式列处理。
- group by + 聚合列也能生成,但注意:生成的只是平铺的结果集,不会生成 group() 分组带、更不会有小计。要分组统计效果,得拿平铺结果改造成分组 DataWindow(参考《分组与汇总统计全解》),或者直接换思路。
- where 子句原样进 retrieve=:本机对比 where qty > 0 与 where qty > 99999 两次生成的语法串,长度差恰好等于字面量的长度差,说明 where 文本被原封不动搬进去。
八、端到端验证
第二节示例在本机(SQL Server 2008 R2,SNC 接口)的完整读数:Create 返回 1、SetTransObject 返回 1、Retrieve 返回 3(演示表内 3 行:A/10、B/20、C/30)、Describe('datawindow.processing') 返回 1(grid)、Describe('datawindow.table.updatetable') 返回 t_s76。链路完整可用。
九、PB12.5 差异
- 生成的语法串版本头不同:PB10 生成 release 10;,PB12.5 生成 release 12.5;(本机双轨读数)。这是两版唯一的实测差异。
- 表现串规则(引号、未知键、column/text 可用键)、类型映射、三类报错形态、order by/计算列/group by 行为,两版一致。
- 验证口径:PB10 基线 = 导入与全量编译 0 错误、对象齐全(编译级);本机 PB10 运行环境未装 SNC 接口运行库,故 PB10 侧未做连库运行验证;PB12.5 = 全部探针连库实跑(运行级)。读者在 PB10 环境运行前请按第六节的三类失败形态自行核对。
十、坑清单
- 判成败只看返回串长度,别信 err 的旧值——成功时 err 会被覆盖成空串。
- 表现串的值不能加引号,加了直接 invalid。
- 表现串里没有静默容错:一个不认识的键整个串作废,错误信息会把整个表现串回显。
- column()/text() 只认少量键;想设坐标、宽度,生成后用 Modify。
- 语法串带版本头,跨版本落盘 .srd 再导入是坑。
- 数据库类型映射不是原样照搬:nvarchar/varchar 都变 char,money 变 decimal——Describe('列名.coltype') 拿到的是映射后的类型(本机实测 nvarchar(10) 列返回 char(10)),按映射后的算才不会错。
- group by 只出平铺结果,不出分组带与小计。
- order by 只进 retrieve=,不出 sort= 属性;运行期排序用 SetSort。
- 必须 CONNECT 之后再调,否则报 Transaction not connected。
- 每列约 1.1KB 的语法串开销是正常体量,大宽表生成的串很长,别按行数估体积。
十一、与相近方案对比,以及最顺的组合拳
先把三条相近路线摆在一起:
- 手写语法串:全文自己拼 table(column=...) 那套,可控但极易写错列类型与更新属性;SyntaxFromSQL 把列类型、更新属性、band 布局全部自动生成,代价是必须连着库、且外观定制要走 Modify。
- Describe('datawindow.syntax'):只能取已有 DataWindow 的语法串,解决不了运行时从零生成的问题。
- SyntaxFromSQL + Modify 组合:生成后立刻 Modify 改坐标、改宽度、加计算域,是实践中最顺的组合拳。
组合拳的标准动作(接在第二节 Create 之后):
- // lds 为已用语法串 Create 出来的 DataStore
- string ls_coltype // 收映射后的列类型
- string ls_mod // 收 Modify 的返回值
- ls_coltype = lds.Describe('name.coltype') // 注意:返回的是映射后的类型
- MessageBox('列类型', ls_coltype)
- ls_mod = lds.Modify('name.width=500') // 返回空串即成功
- if ls_mod <> '' then
- MessageBox('修改失败', ls_mod)
- return
- end if
- MessageBox('新列宽', lds.Describe('name.width'))
复制代码
本机实测三个读数:Describe('name.coltype') 返回 char(10)(数据库里的 nvarchar(10) 已被映射,坑 6 的直接证据);Modify('name.width=500') 返回空串、列宽从 251 变 500;改完 SetTransObject + Retrieve 照常取到 3 行。也就是说:SyntaxFromSQL 负责把骨架和列类型一次生成对,外观与行为微调交给 Modify,两者各干各的擅场。
一句话收束:SyntaxFromSQL 干的是脏活——替你把列定义、类型映射、更新属性一次写对;它不擅长的外观微调,交给 Modify 去做。记住「返回串为空即失败、err 里必有原因」,这个函数就能放心用。 |