<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Soul Of Free Loop &#187; 数据库</title>
	<atom:link href="https://zohead.com/archives/tag/database/feed/" rel="self" type="application/rss+xml" />
	<link>https://zohead.com</link>
	<description>Uranus Zhou&#039;s Blog</description>
	<lastBuildDate>Sat, 19 Jul 2025 15:42:46 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8</generator>
	<item>
		<title>使用 Lade 云开发平台部署容器应用</title>
		<link>https://zohead.com/archives/lade-cloud/</link>
		<comments>https://zohead.com/archives/lade-cloud/#comments</comments>
		<pubDate>Mon, 28 Apr 2025 15:35:39 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[Lade]]></category>
		<category><![CDATA[Miniflux]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[云平台]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[数据库]]></category>

		<guid isPermaLink="false">https://zohead.com/?p=1948</guid>
		<description><![CDATA[关于 Lade 云开发平台 最近了解到一个比较新的 Lade 云开发平台，Lade 是专为开发者设计的云端平台，可以帮助开发者在云端部署测试应用，并提供基础的免费应用额度，还有多处数据中心服务器可供选择，还默认直接支持 HTTPS 域名以方便连接访问容器应用。 Lade 开发平台默认支持以下编程语言： Go Node.js PHP Python Ruby 以及以下这些常用的数据库： MariaDB Memcached MongoDB MySQL PostgreSQL Redis 对于其它编程语言，Lade 还支持以 Dockerfile 形式运行测试应用容器。 首先可以在 Lade 官方网站注 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2 id="about-lade-cloud">关于 Lade 云开发平台</h2>
<p>最近了解到一个比较新的 <a href="https://www.lade.io/">Lade</a> 云开发平台，Lade 是专为开发者设计的云端平台，可以帮助开发者在云端部署测试应用，并提供基础的免费应用额度，还有多处数据中心服务器可供选择，还默认直接支持 HTTPS 域名以方便连接访问容器应用。</p>
<p>Lade 开发平台默认支持以下编程语言：</p>
<ul>
<li>Go</li>
<li>Node.js</li>
<li>PHP</li>
<li>Python</li>
<li>Ruby</li>
</ul>
<p>以及以下这些常用的数据库：</p>
<ul>
<li>MariaDB</li>
<li>Memcached</li>
<li>MongoDB</li>
<li>MySQL</li>
<li>PostgreSQL</li>
<li>Redis</li>
</ul>
<p>对于其它编程语言，Lade 还支持以 Dockerfile 形式运行测试应用容器。</p>
<p>首先可以在 Lade 官方网站注册账户，然后需要绑定并验证 GitHub 账号后才能创建部署容器应用。</p>
<h2 id="lade-cli">Lade CLI 部署应用</h2>
<p>Lade 网站目前没有提供任何容器应用的部署和管理的功能，容器应用的部署和管理都需要使用 <a href="https://www.lade.io/docs/platform/cli">Lade CLI</a> 工具来完成。</p>
<p>这里以 Linux 系统为例进行简单的介绍，按照页面介绍来安装 Lade CLI：</p>
<pre class="brush: bash; title: ; notranslate">
~# curl -L https://github.com/lade-io/lade/releases/latest/download/lade-linux-amd64.tar.gz | tar xz
~# sudo mv lade /usr/local/bin
</pre>
<p>首先使用 CLI 登录 Lade 账户：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade login
Enter your Lade credentials:
? Username or email: zohead
? Password: 
Logged in as zohead*
</pre>
<p>输入用户名和密码登录成功之后就可以进行创建部署容器应用了，这次我准备先部署一个 <a href="https://miniflux.app/">Miniflux</a> 应用来试试效果。</p>
<p>Miniflux 是一个极简的 RSS 阅读器应用，基于 Go 语言开发，使用 Postgres 数据库保存数据，与其它 RSS 阅读器应用相比功能和界面都很简洁，不过关键还支持抓取 RSS 文章全文、全文检索等功能。</p>
<p>Lade 的收费方式可以参考官网 <a href="https://www.lade.io/pricing">Pricing</a> 页面，并有基础的免费额度，当然免费的额度也是随时有可能取消的：</p>
<ul>
<li>应用：
<ul>
<li>128 MB 内存；</li>
<li>1 CPU；</li>
</ul>
</li>
<li>数据库：
<ul>
<li>128 MB 内存；</li>
<li>1 GB 存储空间；</li>
</ul>
</li>
<li>磁盘：
<ul>
<li>1 GB 磁盘。</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>备注：</strong></p>
<p>2025-06-07 开始 Lade 平台正式取消了免费额度，目前最便宜的 128 MB 内存 / 1 CPU 的应用价格为每月 $1.25，大家自行酌情选择合适的容器收费配置计划。</p>
</blockquote>
<h3 id="app-container">应用容器</h3>
<p>我们首先使用 <code>lade apps create</code> 命令创建新的应用容器，按照提示输入应用容器名称，并选择容器配置和区域即可完成创建，这里就选择免费配置计划和日本东京区域：</p>
<blockquote>
<p><strong>提示：</strong></p>
<ul>
<li>容器名称非常关键，后面很多命令都需要用到，请谨慎输入；</li>
<li>请根据实际应用程序对内存和 CPU 的要求，选择合适的应用配置计划；</li>
<li>免费额度随时有可能取消，如果使用免费配置计划，请注意备份应用数据。</li>
</ul>
</blockquote>
<pre class="brush: bash; title: ; notranslate">
~# lade apps create
? App Name: miniflux
? Plan: 128mb
? Region: Tokyo
</pre>
<p>创建成功之后，可以运行 <code>lade apps list</code> 命令查看容器列表：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade apps list
NAME        PLAN     CREATED           STATUS
miniflux    128mb    43 minutes ago    running
</pre>
<p>另外使用 <code>lade apps show</code> 命令可以查看具体某个应用容器的详细信息和状态，下方输出数据最后的 <code>Web URL</code> 就是 Lade 云开发平台为容器应用自动分配的 HTTPS 访问域名：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade apps show miniflux
Owner:        XXX@XXX.com
Processes:    web: 0/1
Plan:         128mb
Region:       Tokyo
Status:       running
Web URL:      miniflux-zohead.ladeapp.com
</pre>
<p>接着我们就需要在容器上部署实际的应用了，对于 Miniflux 可以从 GitHub 检出最新 v2 版本的代码：</p>
<pre class="brush: bash; title: ; notranslate">
~# mkdir miniflux
~# cd miniflux
~/miniflux# git clone --depth 1 https://github.com/miniflux/v2.git
</pre>
<p>然后使用 <code>lade deploy</code> 命令进行容器应用部署：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade deploy --app miniflux
time=&quot;2025-04-24T20:28:59+08:00&quot; level=error msg=&quot;Can't add file internal/reader/readability/testdata to tar: archive/tar: unknown file mode ?rw-rw-rw-&quot;
Sending build context to Docker daemon  4.563MB
Step 1/11 : FROM golang:1.23.0
1.23.0: Pulling from library/golang
Successfully built e7ae4ac3677b
Successfully tagged registry.lade.io/zohead/miniflux:build-1njzwaheu4
Pushing miniflux build to registry
Build finished use &quot;lade logs -a miniflux -f&quot; to view app logs
</pre>
<p>容器应用部署成功之后，我们可以使用 <code>lade ps</code> 命令确认容器内的进程是否运行正常，可以看到 Lade 部署生成的 <code>miniflux.app</code> 应用程序进程正在运行：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade ps -a miniflux
NAME     PLAN     STARTED         COMMAND
web.1    128mb    14 hours ago    miniflux.app
</pre>
<p>我们还可以使用 <code>lade run</code> 命令实现在应用容器中运行需要的命令，这里就直接运行一个 Bash Shell，运行自定义命令也需要指定一个配置计划，这里还是使用免费配置计划：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade run &quot;bash&quot; -a miniflux -p 128mb
</pre>
<p>容器内的 Bash Shell 启动成功之后，我们可以查看并确认 Miniflux 应用信息，对于 Miniflux 这种 Go 语言应用，Lade 默认部署生成的应用程序在 <code>/go/bin</code> 目录下：</p>
<pre class="brush: bash; title: ; notranslate">
~$ ls -l /go/bin/miniflux.app
-rwxr-xr-x 1 web web 19411204 Apr 24 12:30 /go/bin/miniflux.app
~$ miniflux.app -i
Version: dev
Commit: HEAD
Build Date: undefined
Go Version: go1.23.0
Compiler: gc
Arch: amd64
OS: linux
</pre>
<p>另外根据上面部署命令输出中的提示信息，使用 <code>lade logs -a miniflux -f</code> 命令可以跟踪显示容器应用的运行日志，实际上就是显示容器内应用程序的标准输出和错误输出：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade logs -a miniflux -f
web.1 | level=INFO msg=&quot;The default value for DATABASE_URL is used&quot;
web.1 | dial tcp [::1]:5432: connect: connection refused
</pre>
<p>由于还没有为 Miniflux 应用配置数据库，会出现类似上面的报错信息。</p>
<h3 id="database-addon">数据库扩展</h3>
<p>接着我们需要使用 <code>lade addons create</code> 命令为容器应用配置数据库扩展，下面的 <code>lade addons create postgres</code> 命令即为创建 PostgreSQL 数据库，我仍然使用免费配置计划，并选择和应用相同位置的日本东京区域，以加快数据库访问速度：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade addons create postgres
? Addon Name: miniflux-postgres
? Plan: 128mb
? Region: Tokyo
? Version: 17
? Public: No
</pre>
<p>简单说明一下命令参数：</p>
<ul>
<li><code>Addon Name</code> 为数据库扩展名称，后续扩展管理相关命令也会用到；</li>
<li><code>Version</code> 为数据库版本，<code>lade</code> 命令会列出支持的数据库版本；</li>
<li><code>Public</code> 表示数据库是否允许外部公共访问，我只需要 PostgreSQL 数据库被 Miniflux 应用访问，并不需要公共访问，这里就选 <strong>No</strong> 了。</li>
</ul>
<p>创建成功之后，就可以使用 <code>lade addons show</code> 命令查看数据库扩展的详细信息和状态了：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade addons show miniflux-postgres
Owner:        XXX@XXX.com
Service:      PostgreSQL
Plan:         128mb
Region:       Tokyo
Version:      17
Public:       No
Status:       running
Addon URI:    postgresql://USERNAME:PASSWORD@tyo-sw1.lade.io:30066/XXXX?sslmode=require
</pre>
<p>最后的 <code>Addon URI</code> 就是最关键的数据库连接地址，我们需要为应用容器配置数据库连接地址，地址中包含数据库服务器地址、用户名、密码、数据库名称等数据。</p>
<p>确认数据库运行正常之后，就可以将 PostgreSQL 数据库扩展附加到 Miniflux 应用容器：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade addons attach miniflux-postgres --app miniflux
? Env Name: POSTGRES_URL
</pre>
<p>然后使用 <code>lade env list</code> 命令检查应用容器的环境变量：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade env list -a miniflux
DATABASE_URL=postgresql://USERNAME:PASSWORD@tyo-sw1.lade.io:30066/XXXX?sslmode=require
POSTGRES_URL=postgresql://USERNAME:PASSWORD@tyo-sw1.lade.io:30066/XXXX?sslmode=require
</pre>
<p>可以看到数据库扩展附加之后，会自动为应用容器配置数据库环境变量，包含通用的 <code>DATABASE_URL</code> 和 PostgreSQL 专用的 <code>POSTGRES_URL</code> 环境变量。</p>
<h2 id="miniflux-app-issue">Miniflux 应用容器问题</h2>
<p>我原以为按照上面的步骤为 Miniflux 应用容器配置好 PostgreSQL 数据库扩展之后，Miniflux 应用就可以正常运行了，而实际上容器仍然有报错：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade logs -a miniflux -f
web.1 | the database schema is not up to date: current=v0 expected=v108
</pre>
<p>参考 Miniflux 项目 <a href="https://github.com/miniflux/v2/issues/1680">You must run the SQL migrations, the database schema is not up to date: current=v0 expected=v62</a> Issue 的说明，对于类似 Docker 容器的环境 Miniflux 需要开启 <code>RUN_MIGRATIONS</code> 环境变量。</p>
<p>我们可以使用 <code>lade env set</code> 命令为容器配置或修改环境变量：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade env set RUN_MIGRATIONS=1 -a miniflux
</pre>
<p>稍等一会容器应用自动重启后，就可以看到 Miniflux 应用已经正常运行了：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade logs -a miniflux -f
web.1 | level=INFO msg=&quot;Running database migrations&quot; current_version=0 latest_version=108 driver=postgresql
web.1 | level=INFO msg=&quot;Starting HTTP server&quot; listen_address=:3000
</pre>
<p>现在我们其实就已经可以通过 Lade 云平台为容器应用分配的域名访问 Miniflux Web 管理界面了，当然还是需要用 HTTPS 访问的：</p>
<p><img src="https://images.weserv.nl/?url=https://res.cloudinary.com/digwht2y0/image/upload/v1752420850/lade-miniflux.png" alt="Miniflux Web 管理界面"></p>
<p>不过由于我们还没有为 Miniflux 配置管理员账户，还不能直接登录。</p>
<p>这个时候就需要借助 <code>lade run</code> 命令在应用容器中运行命令创建管理员账户了，下面输出中的 <code>miniflux.app</code> 程序是在容器内运行的：</p>
<pre class="brush: bash; title: ; notranslate">
~$ miniflux.app -create-admin
Enter Username: admin
Enter Password:
level=INFO msg=&quot;Created new admin user&quot; username=admin user_id=1
</pre>
<p>管理员账户创建成功之后，我们就可以通过容器域名访问 Miniflux Web 管理界面进行 RSS 源的配置和订阅管理了。</p>
<p>当然我们也可以开始就通过指定环境变量让 Miniflux 容器自动创建管理员账户，与上面的 <code>RUN_MIGRATIONS</code> 环境变量配合起来就类似：</p>
<pre class="brush: bash; title: ; notranslate">
~# lade env set RUN_MIGRATIONS=1 CREATE_ADMIN=1 ADMIN_USERNAME=admin ADMIN_PASSWORD=password -a miniflux
</pre>
<p>这样通过环境变量指定管理员账户名称和密码也很方便。</p>
<h2 id="summary ">总结</h2>
<p>本文简单介绍了如何使用 Lade 云开发平台的 <code>lade</code> CLI 命令部署配置容器应用，我部署的 Miniflux RSS 阅读器容器应用也运行了一段时间，Lade 云平台还是比较稳定的，只是没有国内的数据中心，访问速度倒是一般。</p>
<p>最后 Lade 云平台的开发者已经确认，目前提供的免费额度仅供开发人员测试使用，免费额度后续随时有可能取消，希望大家不要滥用，如果觉得 Lade 好用也可以考虑付费使用哦，祝大家玩的开心。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/lade-cloud/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>lighttpd的SQLite问题</title>
		<link>https://zohead.com/archives/lighty-sqlite-err/</link>
		<comments>https://zohead.com/archives/lighty-sqlite-err/#comments</comments>
		<pubDate>Sun, 07 Sep 2014 14:55:19 +0000</pubDate>
		<dc:creator><![CDATA[Uranus Zhou]]></dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[数据库]]></category>
		<category><![CDATA[lighttpd]]></category>
		<category><![CDATA[SQLite]]></category>
		<category><![CDATA[sqlite3_prepare]]></category>
		<category><![CDATA[WebDAV]]></category>

		<guid isPermaLink="false">http://zohead.com/?p=786</guid>
		<description><![CDATA[最近在使用 lighttpd 的 WebDAV 插件拷贝文件时遇到比较奇怪的 SQLite 库报错问题，WebDAV 插件需要使用 SQLite 数据库保存文件锁、文件属性等信息，报错信息如下： (mod_webdav.c.2182) sql-set failed: SQL logic error or missing database(mod_webdav.c.2182) sql-set failed: not an error(mod_webdav.c.2182) sql-set failed: not an error(mod_webdav.c.2182) sql-set failed: [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>最近在使用 lighttpd 的 WebDAV 插件拷贝文件时遇到比较奇怪的 SQLite 库报错问题，WebDAV 插件需要使用 SQLite 数据库保存文件锁、文件属性等信息，报错信息如下：</p>
<p>(mod_webdav.c.2182) sql-set failed: SQL logic error or missing database<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2182) sql-set failed: not an error<br />(mod_webdav.c.2511) remove lock: bind or column index out of range<br />(mod_webdav.c.2511) remove lock: bind or column index out of range<br />(mod_webdav.c.2511) remove lock: bind or column index out of range</p>
<p>我们先看看 mod_webdav.c 中对应的 SQLite 处理代码：</p>
<p><pre class="brush: cpp; highlight: [17,18,19,20,43]; title: mod_webdav.c; notranslate">
sqlite3_stmt *stmt;

stmt = (0 == xmlStrcmp(cmd-&gt;name, BAD_CAST &quot;remove&quot;)) ?
	p-&gt;conf.stmt_delete_prop : p-&gt;conf.stmt_update_prop;

for (props = cmd-&gt;children; props; props = props-&gt;next) {
	if (0 == xmlStrcmp(props-&gt;name, BAD_CAST &quot;prop&quot;)) {
		xmlNode *prop;
		int r;

		prop = props-&gt;children;

		sqlite3_reset(stmt);

		/* bind the values to the insert */

		sqlite3_bind_text(stmt, 1,
				  con-&gt;uri.path-&gt;ptr,
				  con-&gt;uri.path-&gt;used - 1,
				  SQLITE_TRANSIENT);
		sqlite3_bind_text(stmt, 2,
				  (char *)prop-&gt;name,
				  strlen((char *)prop-&gt;name),
				  SQLITE_TRANSIENT);
		if (prop-&gt;ns) {
			sqlite3_bind_text(stmt, 3,
					  (char *)prop-&gt;ns-&gt;href,
					  strlen((char *)prop-&gt;ns-&gt;href),
					  SQLITE_TRANSIENT);
		} else {
			sqlite3_bind_text(stmt, 3,
					  &quot;&quot;,
					  0,
					  SQLITE_TRANSIENT);
		}
		if (stmt == p-&gt;conf.stmt_update_prop) {
			sqlite3_bind_text(stmt, 4,
				  (char *)xmlNodeGetContent(prop),
				  strlen((char *)xmlNodeGetContent(prop)),
				  SQLITE_TRANSIENT);
		}

		if (SQLITE_DONE != (r = sqlite3_step(stmt))) {
			log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;,
					&quot;sql-set failed:&quot;, sqlite3_errmsg(p-&gt;conf.sql));
		}
	}
}
</pre>
</p>
<p>这里用到了 SQLite 的绑定数据 sqlite3_bind_text 和 sqlite3_step 执行函数。</p>
<p>同时我们可以看看 mod_webdav.c 中打开 SQLite 数据库以及 SQLite 变量绑定方式的 SQL 处理代码：</p>
<p><pre class="brush: cpp; highlight: [11,12,31,32,44,45,65,66]; title: mod_webdav.c; notranslate">
const char *next_stmt;
char *err;

if (SQLITE_OK != sqlite3_open(s-&gt;sqlite_db_name-&gt;ptr, &amp;(s-&gt;sql))) {
	log_error_write(srv, __FILE__, __LINE__, &quot;sbs&quot;, &quot;sqlite3_open failed for&quot;,
			s-&gt;sqlite_db_name,
			sqlite3_errmsg(s-&gt;sql));
	return HANDLER_ERROR;
}

if (SQLITE_OK != sqlite3_exec(s-&gt;sql,
		&quot;CREATE TABLE properties (&quot;
		&quot;  resource TEXT NOT NULL,&quot;
		&quot;  prop TEXT NOT NULL,&quot;
		&quot;  ns TEXT NOT NULL,&quot;
		&quot;  value TEXT NOT NULL,&quot;
		&quot;  PRIMARY KEY(resource, prop, ns))&quot;,
		NULL, NULL, &amp;err)) {

	if (0 != strcmp(err, &quot;table properties already exists&quot;)) {
		log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;can't open transaction:&quot;, err);
		sqlite3_free(err);

		return HANDLER_ERROR;
	}
	sqlite3_free(err);
}

...

if (SQLITE_OK != sqlite3_prepare(s-&gt;sql,
	CONST_STR_LEN(&quot;REPLACE INTO properties (resource, prop, ns, value) VALUES (?, ?, ?, ?)&quot;),
	&amp;(s-&gt;stmt_update_prop), &amp;next_stmt)) {
	/* prepare failed */

	log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;sqlite3_prepare failed:&quot;, sqlite3_errmsg(s-&gt;sql));
	return HANDLER_ERROR;
}

...

/* LOCKS */

if (SQLITE_OK != sqlite3_exec(s-&gt;sql,
		&quot;CREATE TABLE locks (&quot;
		&quot;  locktoken TEXT NOT NULL,&quot;
		&quot;  resource TEXT NOT NULL,&quot;
		&quot;  lockscope TEXT NOT NULL,&quot;
		&quot;  locktype TEXT NOT NULL,&quot;
		&quot;  owner TEXT NOT NULL,&quot;
		&quot;  depth INT NOT NULL,&quot;
		&quot;  timeout TIMESTAMP NOT NULL,&quot;
		&quot;  PRIMARY KEY(locktoken))&quot;,
		NULL, NULL, &amp;err)) {

	if (0 != strcmp(err, &quot;table locks already exists&quot;)) {
		log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;can't open transaction:&quot;, err);
		sqlite3_free(err);

		return HANDLER_ERROR;
	}
	sqlite3_free(err);
}

if (SQLITE_OK != sqlite3_prepare(s-&gt;sql,
	CONST_STR_LEN(&quot;INSERT INTO locks (locktoken, resource, lockscope, locktype, owner, depth, timeout) VALUES (?,?,?,?,?,?, CURRENT_TIME + 600)&quot;),
	&amp;(s-&gt;stmt_create_lock), &amp;next_stmt)) {
	/* prepare failed */
	log_error_write(srv, __FILE__, __LINE__, &quot;ss&quot;, &quot;sqlite3_prepare failed&quot;, sqlite3_errmsg(s-&gt;sql));

	return HANDLER_ERROR;
}

...
</pre>
</p>
<p>从上面的代码可以看出，lighttpd 打开 SQLite 数据库之后先创建 properties 表格，然后使用 sqlite3_prepare 函数进行 properties 表格的 SQL 语句预处理，接着创建 locks 表格，同时也使用 sqlite3_prepare 对 locks 表格进行预处理。</p>
<p>使用 sqlite3_prepare 函数而不直接使用 sqlite3_exec 函数的好处是可以直接将 SQL 语句转换为 SQLite 内部的字节码，后面使用时不需要再直接使用冗长的 SQL 语句，直接绑定数据就可以查询或者更新数据了。这里就比较奇怪了，stmt_update_prop 的 SQL 语句看起来也没有什么问题。首先把疑点放在 sqlite3_bind_text 上，怀疑是不是数据中有没有什么特殊字符导致 sqlite3_step 函数执行出错了，但我把 sqlite3_bind_text 的内容全部改成普通字符串还是一样的报错。</p>
<p>搜寻一番之后终于在 sqlite3.h 头文件中发现了端倪：</p>
<p><pre class="brush: cpp; title: sqlite3.h; notranslate">
** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
** recommended for all new programs. The two older interfaces are retained
** for backwards compatibility, but their use is discouraged.、
**
** In the &quot;v2&quot; interfaces, the prepared statement
** that is returned (the [sqlite3_stmt] object) contains a copy of the
** original SQL text. This causes the [sqlite3_step()] interface to
** behave differently in three ways:
**
** If the database schema changes, instead of returning [SQLITE_SCHEMA] as it
** always used to do, [sqlite3_step()] will automatically recompile the SQL
** statement and try to run it again. As many as [SQLITE_MAX_SCHEMA_RETRY]
** retries will occur before sqlite3_step() gives up and returns an error.
**
** When an error occurs, [sqlite3_step()] will return one of the detailed
** [error codes] or [extended error codes]. The legacy behavior was that
** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code
** and the application would have to make a second call to [sqlite3_reset()]
** in order to find the underlying cause of the problem. With the &quot;v2&quot; prepare
** interfaces, the underlying reason for the error is returned immediately.
</pre>
</p>
<p>sqlite3.h 中建议使用新的 sqlite3_prepare_v2 函数替代老的 sqlite3_prepare 函数，而且中间的注释中还提到使用 sqlite3_prepare 函数之后如果数据库的结构发生了变动，sqlite3_step 函数将会出错。这时我们看看 mod_webdav.c 中初始化数据库的代码可以发现 sqlite3_prepare 函数是在两个 sqlite3_exec 函数创建数据库之间运行的，这样 sqlite3_prepare 生成的字节码就不是正确的，才导致后续的 SQL 更新操作失败。</p>
<p>知道原因之后修改也比较简单了，我们可以修改 mod_webdav.c 中的数据库初始化代码，将两个 sqlite3_exec 创建数据库的操作放在最前面，将所有 sqlite3_prepare 替换为 sqlite3_prepare_v2 函数放在创建数据库之后，这样就不会出现 SQLite 数据库错误问题了。</p>
<p>本文为个人使用分析的结果，其中有任何问题欢迎提出指正，另外 lighttpd 最新版本库代码中这个问题似乎仍然没有修正。</p>
]]></content:encoded>
			<wfw:commentRss>https://zohead.com/archives/lighty-sqlite-err/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
