aspsql源码
打开 aspsql 的源码源码目录,第一眼看到的源码是 `aspsql.asp` 这个文件。不到两千行 VBScript,源码却塞进了连接池管理、源码SQL 拼接、源码分页逻辑、源码事务封装,源码甚至一套简易的源码 ORM 映射。这东西放在今天看像个手工作坊,源码但在 ASP 时代,源码它解决了一个真问题:让写惯了 `Recordset` 的源码人不用每次都手写 `Conn.Execute`。
源码里最值得琢磨的源码是 `BuildSQL` 函数。它接收表名、源码字段数组、源码条件字典,源码然后拼出一串 SQL。问题也出在这儿——条件值直接内插,没有参数化。`WHERE id = " & value` 这种写法遍地都是。作者显然知道有注入风险,在 `EscapeString` 里做了单引号替换,把 `'` 变成 `''`。但数字型字段呢?直接原样拼进去。你传个 `1 OR 1=1`,它照单全收。这不是疏忽,是那个年代 ASP 开发者的普遍习惯:能跑就行,安全靠 WAF。
连接池的实现倒是有意思。`aspsql` 没有用 ADO 自带的连接池,而是自己在 Application 对象里维护了一个数组,存着若干 `ADODB.Connection` 对象。`GetConnection` 从数组尾部弹一个出来,`ReleaseConnection` 再塞回去。表面看是省了创建连接的开销,实际上 Application 对象是进程级的,多个用户同时取连接时,VBScript 没有真正的锁机制,只能靠 `Application.Lock` 硬扛。高并发下这里就是瓶颈,但当年跑在 IIS 5 上的小站点,一天几百个请求,根本碰不到这个天花板。
分页那块代码我盯着看了很久。`GetRecordsetByPage` 先用 `SELECT COUNT(*)` 拿总数,再根据页码算 `TOP` 和 `NOT IN` 的偏移量。SQL Server 2000 没有 `OFFSET`,只能这么写。作者在注释里留了一行:`-- 数据量超过十万行时建议换存储过程`。这行注释比代码本身更有信息量,说明他清楚这个方案的边界在哪,只是没动力去改。够用就好,是那个年代很多 ASP 组件的生存哲学。
事务封装是另一个亮点。`BeginTrans`、`CommitTrans`、`RollbackTrans` 三个方法,内部调用 `Conn.BeginTrans` 等原生方法,但加了一层错误捕获。如果 `CommitTrans` 失败,它会自动回滚并记录到 `Application("aspsql_error_log")` 里。这个日志是个字符串数组,只保留最近五十条。我试着模拟了一次主键冲突,日志确实记下了错误号和描述,但回滚是否真的生效,得看调用方有没有在 `On Error Resume Next` 之后检查 `Err.Number`。源码里没强制检查,这是个坑。
ORM 映射部分最简陋。`MapRecord` 接收一个 `Recordset` 和一个类名,用 `Execute` 动态创建对象,然后遍历字段赋值。VBScript 没有反射,它靠的是 `Eval("new " & className)` 这种字符串执行。性能差,但写起来快。一个 `User` 类对应 `users` 表,字段名一致就能自动填充。不一致?手动写 `MapField` 覆盖。这种半自动的设计,比纯手写 SQL 省事,又比全自动 ORM 可控。作者显然在灵活和效率之间选了中间路线。
翻完整个源码,最深的感受是:它不优雅,但实用。每个函数都带着那个年代的烙印——没有单元测试,没有依赖注入,错误处理靠 `On Error Resume Next` 加 `Err.Description` 输出。可它确实让一批 ASP 开发者从重复的数据库操作里解放了出来。今天用 .NET 或 Node.js 的人看这东西会觉得粗糙,但把时间拨回 2003 年,能有个封装好的 `aspsql` 用,已经算幸福了。