祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 75|回复: 1

[PBIDEA] PBIDEA:SQLite 嵌入式数据库实战全解 —— uo_sqlite3 直连与 uo_database 两条路线(PB10 基准 · PB12.5 实测)

[复制链接]

[PBIDEA] PBIDEA:SQLite 嵌入式数据库实战全解 —— uo_sqlite3 直连与 uo_database 两条路线(PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

4036

积分

PBAI

积分
4036
贡献
在线时间
小时
昨天 09:02 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PBIDEA:SQLite 嵌入式数据库实战全解 —— uo_sqlite3 直连与 uo_database 两条路线(PB10 基准 · PB12.5 实测)

阅读说明
- 适用版本:PowerBuilder 10 及以上(本文以 PB10 语法为基准写作,PB12.5 实机验证,21 组用例两版读数逐字一致);使用 PBIDEA 组件(websuite.pbl、sql.pbl,PbIdea.dll 运行库)
- 支持数据库:SQLite(本文主角,单文件嵌入式数据库,不需要安装任何数据库服务器);不涉及 SQL Server 等网络数据库
- 操作系统与环境要求:Windows 7 及以上;程序目录下需要 pbidea.dll 和 sqlite3.dll(SQLite 路线的必需件,缺了它两条路线都连不上库)
- 难度系数:★★☆☆☆
- 其它阅读说明:示例不需要预置任何数据,运行时自动生成 .db 文件;读过上篇《PBIDEA:用 uo_database 与 uo_database_pool 做数据库连接与连接池复用》会更顺,但不读也不影响本文

PBIDEA 里的 SQLite 有两条使用路线:一条是 uo_database 把 DBMS 属性设成 SQLite,走完整的数据访问对象(能绑定参数、能读结果集);另一条是 uo_sqlite3 直连对象(只有 open 和 exec,没有读取通道)。两条路线我都在 PB10 和 PB12.5 上跑了 21 组用例,本文把每条 API 的真实行为、包括那些和直觉相反的部分,一条条摆出来。

一、SQLite 是什么,什么时候用它

SQLite 是一个嵌在进程里的数据库:没有服务、没有端口、没有账号,整个库就是磁盘上的一个文件。程序里 open 一下文件名就能建表写数,把这个文件拷到别的机器上,数据也跟着走。

它适合的场景:单机配置存储、日志归档、导入导出的中间文件、演示程序的数据源。它不适合的场景:多用户并发写、需要网络访问的场合——这些还是交给 SQL Server 这类服务端数据库。

在 PBIDEA 里,日常读写推荐走路线 A(uo_database),uo_sqlite3 只在「一次性建库灌数」这种场合有优势,原因见第九节的落盘规则实测。

二、环境准备:sqlite3.dll 是第一个坑

两条路线的底层最终都要落到 sqlite3.dll 上。这个 DLL 在 PBIDEA 的发布包里有(sharedll 目录),部署时必须让它和 pbidea.dll 待在一起,或者放进系统 PATH。

缺了它是什么表现?两条路线完全不一样,这是我实测的第一组读数:


  • 路线 A(uo_database):Open() 返回 false,sqldbcode = 3,sqlerrtext 里写着「sqlite3.dll: 找不到指定的模块」——好歹告诉你缺什么。
  • 路线 B(uo_sqlite3):open() 静默返回 false,SqlCode = 0,SqlErrText 是空的——什么都不说。


所以用路线 B 时如果 open 老是 false,第一件事不是查代码,是查 sqlite3.dll 在不在。

三、路线 A:用 uo_database 连 SQLite

连接就四个属性,比连 SQL Server 简单得多(不需要 ServerName,不需要 DBParm 里的 Provider):
  1. // 输入值:库文件名(相对路径,落在程序运行目录;不存在会自动新建)
  2. string ls_db
  3. ls_db = 'demo71.db'
  4. uo_database db
  5. db = create uo_database
  6. db.DBMS     = 'SQLite'    // 固定写 SQLite,注意大小写照抄
  7. db.Database = ls_db       // 库文件路径
  8. db.LogId    = ''          // SQLite 没有用户名,留空
  9. db.LogPass  = ''          // 没有密码,留空
  10. db.DBParm   = ''          // 不需要任何附加参数
  11. if not db.Open() then
  12.     MessageBox('连接失败', db.sqlerrtext + ' (' + String(db.sqldbcode) + ')')
  13.     return
  14. end if
  15. MessageBox('连接成功', 'IsOpen=' + String(db.IsOpen()) + ' IsAlive=' + String(db.IsAlive()))
复制代码

实测读数:Open 返回 true、sqlcode=0、sqldbcode=0、sqlerrtext=success,IsOpen 和 IsAlive 都是 true。还有一个细节:Open 成功的那一刻,磁盘上的库文件就已经创建了(实测文件立刻出现;写了数据之后是 8192 字节,SQLite 的页大小)。

四、建表、ExecuteScaler 与「防炸」写法

建表走 ExecuteSQL,它返回一个 uo_recordset。对 DDL 语句这个返回值没有内容,但不能不接——接了之后判断 IsValid、有效就关掉,这是标准的防炸写法:
  1. // 接上例的 db。建表:如果表已存在会报错,演示前先换新文件名
  2. uo_recordset rs
  3. rs = db.ExecuteSQL('create table emp(id integer primary key, nm varchar(20), sal double)')
  4. if IsValid(rs) then
  5.     rs.Close()
  6.     destroy rs
  7. end if
  8. // 单值查询用 ExecuteScaler,直接拿第一行第一列
  9. any la_cnt
  10. la_cnt = db.ExecuteScaler('select count(*) as cnt from emp')
  11. MessageBox('行数', String(la_cnt))    // 空表读数:0
复制代码

这里有个实测到的错误处理细节,值得单独记:如果 SQL 引用了不存在的表,ExecuteSQL 仍然返回一个 IsValid 为 true 的对象,但它是个空壳——HasResultSet() 返回 false、RowsAffected() 返回 -1,真正的错误在连接对象的三个属性里:sqlcode=-1、sqldbcode=1、sqlerrtext='no such table: xxx'。判成败要同时看 IsValid(rs) 和 db.sqlcode,只看前者会漏。

五、参数化写入:占位符是 :1,不是问号

和 uo_database 连 SQL Server 一样,SQLite 路线的占位符也是 :1、:2 这种形式,写成问号是会被拒的(这是 PBIDEA 数据库对象的统一约定,不是 SQLite 的规矩)。绑定参数从 1 开始数:
  1. // 接上例。插入一行,包含文本、数值和 NULL
  2. uo_recordset rs
  3. string ls_null
  4. double ld_sal
  5. rs = db.PrepareSQL('insert into emp(nm, sal, memo) values(:1, :2, :3)')
  6. rs.BindParam(1, '张三')        // 文本直接传
  7. ld_sal = 3000.5
  8. rs.BindParam(2, ld_sal)        // 数值用 double 传
  9. SetNull(ls_null)
  10. rs.BindParam(3, ls_null)       // NULL:先 SetNull 再绑定
  11. if not rs.ExecuteSQL() then
  12.     MessageBox('写入失败', db.sqlerrtext)
  13. end if
  14. rs.Close()
  15. destroy rs
  16. db.CommitTrans()               // ★ 见第七节:不提交会丢数据
复制代码

三个 BindParam 全部返回 true,ExecuteSQL 返回 true,计数读数 1。中文、小数、NULL 三种值我都在两版上做了往返验证,读回什么就是什么:中文原样回来,NULL 读回来还是 NULL(GetString 返回空、JSON 序列化时是 null),double 精度不丢。

六、读取:列是从 1 开始数的

读取的完整链路是 ExecuteSQL 拿结果集、GetFieldCount 数列、GetField 拿列元数据、FetchNext 逐行走、GetString 或 GetNumber 按列名取值。最大的坑在这里:GetField 的下标是 1 基。我第一版探针按 0 基写,GetField(0) 返回了一个空对象,紧接着调 GetName() 直接崩了运行时(Null object reference)——编译期一点征兆都没有。按 1 基改过来之后一切正常:
  1. // 接上例。读列元数据 + 逐行取值
  2. uo_recordset rs
  3. uo_field fld
  4. long i
  5. rs = db.ExecuteSQL('select * from emp order by id')
  6. MessageBox('列数', String(rs.GetFieldCount()))
  7. i = 1                                    // ★ 从 1 开始,不是 0
  8. do while i <= rs.GetFieldCount()
  9.     fld = rs.GetField(i)
  10.     MessageBox('列 ' + String(i), fld.GetName())
  11.     destroy fld
  12.     i = i + 1
  13. loop
  14. do while rs.FetchNext()
  15.     MessageBox('员工', rs.GetString('nm') + ' ' + String(rs.GetNumber('sal')))
  16. loop
  17. rs.Close()
  18. destroy rs
复制代码

实测中四列的元数据读数是:id 的 GetType=20、nm 和 memo 的 GetType=6、sal 的 GetType=4。GetType 的类型码是 SQLite 内部映射出来的(整数、文本、浮点各一个码),可以用来区分列的性质;但 GetSize 的读数和建表时写的列宽对不上(varchar(20) 的列 GetSize 得 5),别拿它当列宽用。

另一个好用的东西是 FetchJsons,一次把结果集转成 JSON。它有两个实测规则:


  • 必须在新查询上调用。结果集被 FetchNext 循环读完之后再调 FetchJsons,什么都拿不到(实测返回 0)——要重新 ExecuteSQL 一次。
  • 无参版把整个结果集打包成数组的第一个元素:返回值是 1(JSON 个数,不是行数),对第一个元素调 ToString() 出来的是一行完整的 JSON 数组串,所有行都在里面,NULL 列显示为 null。

  1. // FetchJsons 要点演示:必须是刚拿到的结果集
  2. uo_json ljs[]
  3. long n
  4. rs = db.ExecuteSQL('select * from emp order by id')   // 新查询
  5. n = rs.FetchJsons(ljs)
  6. MessageBox('JSON 个数', String(n))                    // 读数:1
  7. MessageBox('内容', ljs[1].ToString())                 // 整个结果集的 JSON 数组串
  8. rs.Close()
  9. destroy rs
复制代码

七、事务语义:不 CommitTrans,关连接就是丢

这是全文最重要的一条实测结论。插入数据之后不调用 CommitTrans 直接关连接,重新打开一数——0 行。SQLite 路线没有任何自动提交,每一条 insert 都攥在未提交事务里,close 即作废:


  • 插入后不提交、关闭、重开:读数 0 行(两版一致)。
  • 插入后 RollbackTrans 再查:0 行。
  • 插入后 CommitTrans 再查:1 行。


所以路线 A 的标准节奏是「建表或写入 → CommitTrans → 再查」。查询不需要提交,但任何 DML 之后想让它落盘,CommitTrans 一句都不能省。

八、路线 A 的错误处理小结

把实测到的错误行为归拢成一张表:

场景表现
缺 sqlite3.dllOpen=false,sqldbcode=3,sqlerrtext 指名缺模块
SQL 引用不存在的表ExecuteSQL 返回 IsValid=true 的空壳,HasResultSet=false、RowsAffected=-1;错误在 sqlcode=-1、sqldbcode=1、sqlerrtext
SQL 语法错误PrepareSQL 返回 NULL(IsValid=false),sqlerrtext 带原生文案,如 near "selec": syntax error
BindParam 位置越界返回 false,不崩


九、路线 B:uo_sqlite3 的落盘规则(本篇最重的实测)

uo_sqlite3 全部公开成员就这些:SetDataDirectory、open 四个重载(无参、单参文件名、两参文件名加整数)、close、exec、CommitTrans、RollbackTrans、CreateFunction、ImportCSV,加上两个实例变量 SqlCode、SqlErrText 和三个事务常量。就这些,没有任何能读出数据的方法——exec 对 SELECT 也返回 true,但结果你拿不到。所以路线 B 天生只能干写,不能读。

写也分「真写」和「假写」。我做了 open 返回值与落盘的完整矩阵(每个格子都是独立用例实测,两版一致):

调用方式文件不存在时文件已有数据(路线 A 建的)文件已有数据(自己 open(文件名, 0) 建的)
open() 无参true,纯内存库不适用不适用
open(文件名) 单参true,但写入不落盘,文件停在 0 字节truefalse
open(文件名, 0)true,建库并落盘(8192 字节)falsefalse(重开也不行)
open(文件名, 1) 或 (文件名, 2)falsefalse未测


翻译成人话:


  • 路线 B 唯一能落盘的姿势:对一个不存在的文件名调 open(文件名, 0),建表、写数、close。这一条链路实测数据完整落盘,之后路线 A 随便读写这个文件(实测读回计数正确)。
  • 单参 open 看着成功,建出来的文件永远是 0 字节,数据全在内存里,close 就没了——我在三组独立用例里都复现了这一点。
  • 库一旦有数据,uo_sqlite3 自己反而打不开了(两参各种值都 false,单参对自己的产物也是 false)。想继续用这个库,换路线 A。
  • 还有一个实例级的暗坑:先调 close 再首调 open 的实例,open 永远返回 false,整个实例报废(实测 close 在前 open 恒 false,重开也救不回来)。新实例永远先 open。


所以路线 B 的正确定位是「一次性建库工具」,示例:
  1. // 特殊说明:open 第二个参数必须是 0,且目标文件必须不存在,否则返回 false
  2. string ls_db
  3. boolean lb
  4. ls_db = 'archive71.db'
  5. uo_sqlite3 sq
  6. sq = create uo_sqlite3
  7. lb = sq.open(ls_db, 0)
  8. if not lb then
  9.     // 注意:失败时 SqlCode 可能还是 0、SqlErrText 还是空,别指望错误信息
  10.     MessageBox('建库失败', 'open 返回 false,先检查文件是否已存在')
  11.     return
  12. end if
  13. sq.exec('create table log71(id integer, msg varchar(100))')
  14. sq.exec('insert into log71 values(1, ' + Char(39) + 'boot' + Char(39) + ')')
  15. sq.close()
  16. destroy sq
  17. MessageBox('完成', ls_db + ' 已生成,之后用 uo_database 读写')
复制代码

顺带说 SetDataDirectory:它改变 open 里相对路径的基准目录。实测 SetDataDirectory('sub71') 之后 open('z.db'),文件落在 sub71 子目录下(程序目录没有、子目录有);SetDataDirectory('') 之后恢复到程序运行目录。想让 .db 都进数据子目录,用它就行。

十、ImportCSV 与 CreateFunction:两个「看起来能用」的方法

这两个方法我专门做了验证,结论都比较扫兴,如实记录:

ImportCSV(内容, 表名) 静默不可用。 我试了四种形态:带表头的 CRLF 内容、LF 内容、无表头内容、CSV 文件路径,四种全部返回 false,而且 SqlCode=0、SqlErrText 依然是初始化的「ok」——一个字的错误原因都不给。导入之后用路线 A 去查,表都不存在。在这版组件上别指望它,CSV 入库请走路线 A 的参数化插入自己拼。

CreateFunction(函数名) 返回 true,注册动作本身成功。但注册了 PB 函数之后,在 SQL 里调用它是否真的会回调 PB 侧,路线 B 没有任何读取通道可以取证(exec 的 SELECT 拿不到结果),这一段我标注为未实测、仅静态核对,不作为核心示例。

十一、路线 B 的错误通道:SqlCode 和 SqlErrText

路线 B 唯一的错误信息来源是 SqlCode 和 SqlErrText 两个实例变量。实测表现:


  • 语法错误:exec 返回 false,SqlCode=1,SqlErrText 是 SQLite 原生文案(如 near "selec": syntax error),定位很准。
  • 缺表:SqlCode=1,SqlErrText 形如 no such table: xxx。
  • 未 open 就 exec:返回 false,但 SqlCode=0、SqlErrText 空——干失败,不说话。
  • open 失败的各种情况:同样 SqlCode=0、SqlErrText 空。


规律很明显:凡是「SQLite 引擎层」的错误都有清楚的文案;凡是「组件层」的失败(没 open、open 不上、ImportCSV 被拒)一律静默。排错时先分清楚错误出在哪一层。

十二、PB12.5 差异

21 组用例在 PB10 与 PB12.5 上各跑一遍,读数逐字一致:连接属性、建表、参数化、NULL 往返、事务语义、GetField 1 基、FetchJsons 打包规则、路线 B 的整个落盘矩阵、ImportCSV 的静默失败,全部无差异。本文所有结论两版通用。

十三、坑清单汇总


  • 缺 sqlite3.dll:路线 A 报 sqldbcode=3 指名道姓,路线 B 静默 false 连错误都不给——先查 DLL 再查代码。
  • 路线 A 连 SQLite 不要 ServerName、不要 DBParm,LogId、LogPass 留空即可。
  • Open 成功即建文件;写入后 8192 字节是正常页大小。
  • DDL 的 ExecuteSQL 返回值要接住、判 IsValid 后关闭,别裸调。
  • 引用缺表时 ExecuteSQL 返回的是「IsValid 的空壳」,判错要加看 db.sqlcode。
  • 占位符是 :1、:2,不是问号;BindParam 从 1 开始。
  • NULL 绑定:SetNull 之后 BindParam,读回仍可判 IsNull。
  • GetField 下标 1 基,用 0 号下标会拿到空对象,紧接着 GetName 即崩运行时,编译不报错。
  • GetSize 的读数和列宽对不上,别当列宽用。
  • FetchJsons 必须在新查询上调,读完的结果集再调拿不到东西;无参版整个结果集打包在第一个元素里,返回值是 JSON 个数不是行数。
  • ★ 不 CommitTrans 就关连接,数据全丢;SQLite 路线没有自动提交。
  • ★ 路线 B 单参、无参 open 都不落盘;唯一落盘姿势是 open(不存在的文件名, 0) 一次成型。
  • ★ 有数据的库 uo_sqlite3 自己打不开(连自己建的也是),继续用请换路线 A。
  • ★ 先 close 后 open 的 uo_sqlite3 实例报废,open 恒 false。
  • ImportCSV 四种形态全部静默 false 且不报错,这版别用。
  • 路线 B 的 SqlCode、SqlErrText 只管引擎层错误,组件层的失败一律静默。


十四、两条路线怎么选

一句话:读写都走路线 A,路线 B 只用来一次性建库灌数。路线 A 有参数绑定、有结果集、有 JSON 输出、错误信息完整;路线 B 的价值只剩「open 一个新文件名、exec 灌一批数据、close」,比如初始化一个归档库。反过来,如果你只需要一个内存库做临时计算(open() 无参那种),路线 B 反而方便——反正不落盘,用完就没了。

完整示例(连接、建表、参数化、提交、查询)打包在附件的 PBL 里,PB10 与 PB12.5 双版本编译验证过,见文后回帖。

十五、实机验证情况


  • PB10:22 个对象导入 0 错误;全量编译 0 错误;21 组用例运行全部 PASS。
  • PB12.5:同上,且与 PB10 读数逐字一致。
  • 未实测段落:CreateFunction 注册后的实际回调行为(路线 B 无读取通道无法取证);单参 open 打开路线 A 所建库之后的写入是否落盘(该组合行为不稳定,不建议依赖)。其余结论均为双轨实测。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

4036

积分

PBAI

积分
4036
贡献
在线时间
小时
昨天 09:08 | 显示全部楼层
【PB10 兼容版交付】SQLite 嵌入式数据库演示包(uo_database 路线 A 全链路)

把文章主线整理成了一个可直接调用的 PB10 兼容对象,PB10 全量编译 0 错误,PB12.5 同源复验 0 错误;同逻辑主线(不含末尾 MessageBox)在两版各跑一遍并 PASS,读数逐字一致。

sqlite_demo_pb10.zip (10.24 KB, 下载次数: 0)

一、包里有什么
1. sqlite_demo_pb10.pbl:PB10 格式库,内含 nvo_sqlite_demo(演示对象);
2. src/nvo_sqlite_demo.sru:同样的源码(GBK/CRLF),想自己导入重建也行;
3. 说明文档.txt:适用版本、环境要求、部署步骤、调用片段、常见坑、验证情况。

二、环境要求
1. Windows 7 及以上(PB10 为 32 位程序,系统 32/64 位都能跑);
2. pbidea.dll 与 sqlite3.dll 两个运行库,必须与 exe 同目录或在 PATH 里 —— 缺 sqlite3.dll 是这类问题最常见的根因:uo_database 会报 sqldbcode=3「sqlite3.dll: 找不到指定的模块」,而 uo_sqlite3 的 open 只静默返回 false,连错误都不给;
3. 依赖 PBIDEA 的 websuite.pbl 与 sql.pbl(uo_database / uo_recordset / uo_field 都在 sql.pbl 里),请加入工程库列表;这两个库不随包附带;
4. 不需要安装任何数据库服务器,SQLite 是单文件库。

三、建表 SQL(自动建,如需自建)
  1. create table emp (
  2.     id   integer primary key,
  3.     nm   varchar(20),
  4.     sal  double,
  5.     memo varchar(50)
  6. );
复制代码

四、操作步骤
1. 解压,把 sqlite_demo_pb10.pbl 复制到工程目录;
2. Library List 里加入 sqlite_demo_pb10.pbl、websuite.pbl、sql.pbl;
3. pbidea.dll、sqlite3.dll 放到 exe 输出目录;
4. 全量 Rebuild,应为 0 错误;
5. 在窗口按钮事件里调用:
  1. nvo_sqlite_demo ln
  2. ln = create nvo_sqlite_demo
  3. ln.of_demo()
  4. destroy ln
复制代码
6. 会依次完成:连接 demo71.db(首次自动建库)→ 建表 emp → 参数化插入一行(zhangsan / 3000.5 / memo 为 NULL)→ CommitTrans → 读列元数据 → 逐行取值 → FetchJsons 导出 JSON → 关闭连接,最后 MessageBox 打印全部读数。

五、实机验证情况(如实标注)
1. PB10:导入 0 错误、全量编译 0 错误、对象齐全;
2. PB12.5:同源导入 0 错误、全量编译 0 错误;
3. 运行级:与本包逻辑完全一致的主线对象(唯一差别是不含末尾 MessageBox)在 PB10 与 PB12.5 各跑一遍 PASS,读数逐字一致 —— cols=4、第一行 zhangsan 3000.5、memo 为 NULL、JSON 为整表数组串;
4. 未实测:CreateFunction 注册后在 SQL 里调用是否真的回调 PB 侧(uo_sqlite3 没有结果读取通道,无法取证);ImportCSV 在这版组件上四种形态(CRLF / LF / 无表头 / 文件路径)全部静默 false 且 SqlCode=0、SqlErrText 仍是 ok,本包不使用它。

六、用的时候最容易踩的三个坑
1. GetField 下标是 1 基,用 0 号下标会拿到空对象,接着 GetName 直接崩运行时,编译期毫无提示;
2. 不 CommitTrans 就关连接,插入的数据全丢(SQLite 路线没有自动提交);
3. FetchJsons 必须在新查询上调用,且无参版把整个结果集打包进数组第一个元素,返回值是 JSON 个数(1)不是行数。

另:uo_sqlite3(路线 B)不要拿来日常读写 —— 单参/无参 open 都是内存态不落盘,唯一落盘姿势是 open(不存在的文件名, 0) 一次性建库,建好的库它自己再也打不开。详见文章第九节。

详细内容见包内「说明文档.txt」,用得不对或者想加功能,跟帖说。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-10-20 13:45 , Processed in 0.030335 second(s), 11 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表