Skip to content

Commit 85924ad

Browse files
committed
Merge branch 'view'
2 parents 0488816 + 92a3393 commit 85924ad

6 files changed

Lines changed: 132 additions & 17 deletions

File tree

README.ja.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ $ open http://localhost:3939
3232

3333
[SQLiteアーカイブ](https://sqlite.org/sqlar.html)(通称:sqlar)とはファイルをBLOBとして保存するための標準テーブルスキーマ<sup>1</sup>のあるただのSQLiteデータベースである。そうする理由はメインページがいくつか説明する(私見では、主のはリレーショナルかインデックスされたデータとファイルが一緒に格納されて、同じORMが使えて、そしてファイルが外部に保存される場合に不可能の外部キー制約ができることだ<sup>2</sup>)でもsqlarフォーマットを特別に作ったテーブルの代わりに使用するメリットはsqlite3のCLIのtarみたいなオプション(そしてこれ)が使えることだ。
3434

35-
私がこれを作る動機は、大きいデータセットと付属オーディオファイルを持って、導入時にそれぞれElasticsearchとS3にプッシュするけどその時までどこかの中間の場所で保存しないとならなかった。私がすでにEntity Frameworkを使ってデータをsqliteファイルに書き込んでいて、最初はオーディオファイルをフォルダーに保存していたけど、ファイル名がSHA1ハッシュだと考えると、このようにアクセスできるようにするのは特に意味がなかった。逆に、Windowsファイルパスや存在しないファイル等を扱うことをかかえた。ファイルをデータベースに移動するのは主キーがS3のオブジェクト名と同じもので、そのテーブルを外部キーによって参照してデータ整合性を強化できるという意味した。それにラップトップとデスクトップを切り替えるために一つのファイルをコピーしていいから楽になった<sup>3</sup>。
35+
> [!TIP]
36+
> [ビュー](https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%A5%E3%83%BC_(%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9))を使用して、既存のテーブルをsqlarスキーマに合わせることができる(現在sqlarserverのみサポートする):
37+
> ```sql
38+
> CREATE VIEW sqlar(rowid, name, mode, mtime, sz, data) AS
39+
> SELECT rowid, filename, 33279, 0, length(content), content FROM files;
40+
> ```
41+
> サーバーはパフォーマンスのためにSQLiteのBlob APIを使うので、rowid(ビューに含まれた)と元になるテーブル名と列名を知る必要がある。実行時に`-e BlobTable=files -e BlobColumn=content`として渡す。
42+
43+
私がこれを作る動機は、大きいデータセットと付属オーディオファイルを持って、導入時にそれぞれElasticsearchとS3にプッシュするけどその時までどこかの中間の場所で保存しないとならなかった。最初はオーディオファイルをフォルダーに保存していたけど、ファイル名がSHA1ハッシュだと考えると、このようにアクセスできるようにするのは特に意味がなかった。逆に、Windowsファイルパスや存在しないファイル等を扱うことをかかえた。ファイルをデータベースに移動したため、主キーがS3のオブジェクト名と同じもので、そのテーブルを外部キーによって参照してデータ整合性を強化できるようになった。それにラップトップとデスクトップを切り替えるために一つのファイルをコピーしていいから楽になった<sup>3</sup>。
3644
3745
でも問題があった。開発環境ではローカルサーバーが本番のS3バケットではなくそのローカルのファイルに指してほしかった。別の開発バケットなんて不要だった。ディスク上のファイルだったら単純に静的ファイルとして提供できたけど、本番で要らないMicrosoft.Data.Sqliteへの依存関係を追加してDockerイメージを膨らましたくなかった。プリプロセッサ ディレクティブは醜い。どうしよう?BLOBを静的ファイルとして提供するだけの別のコンテイナーをComposeファイルに追加して開発でS3の代わりにしようか!そしてNginxやApacheみたいなディレクトリリスト機能も入れって、必要のないのにシンボリックリンク対応を実装しよう!それでそれで……FTPサーバーを!なんていいアイデアだ!けして全く別のプロジェクトに膨大してしまわないでしょう!
3846
@@ -52,13 +60,15 @@ $ open http://localhost:3939
5260
5361
|環&#8288;境&#8288;変&#8288;数|デ&#8288;フ&#8288;ォ&#8288;ル&#8288;ト&#8288;値&nbsp;&nbsp;|説&#8288;明|
5462
|---|---|---|
55-
|`TZ`|UTC|変更時刻を表示するためのタイムゾーン ([_List of tz database time zones_](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)を参照<!-- English only -->)|
63+
|`TZ`|UTC|変更時刻を表示するための[タイムゾーン](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)<!-- English only -->|
5664
|`LANG`|en_US|数値等をフォーマットするためのロケール|
5765
|`SizeFormat`|Binary|Bytes = ファイルサイズを書式なしでバイトで表示する<br />Binary = バイナリ単位を使う(KiB、MiB、GiB、TiB)<br />SI = SI単位を使う(KB、MB、GB、TB)|
5866
|`DirectoriesFirst`|true|ディレクトリをファイルの前にグループする|
5967
|`CaseInsensitive`|false|大文字小文字を区別しないファイルシステムとして扱う|
6068
|`StaticSite`|false|ディレクトリリストを無効して、存在するとindex.htmlを提供して、見つけられない場合/404.htmlを提供する|
6169
|`Charset`|utf-8|ファイルストリームのContent-Typeヘッダーの文字コードを設定する。無効にするには空の文字列に設定して。|
70+
|`BlobTable`|sqlar|BLOBを含むテーブル名<br />※ sqlarテーブルとしてビューを使用することに関する上記のTipを参照してください|
71+
|`BlobColumn`|data|BLOBを含む列名<br />※ sqlarテーブルとしてビューを使用することに関する上記のTipを参照してください|
6272
|`EnableFtp`|false|FTPサーバーを起動する|
6373
|`FtpPasvPorts`|10000-10009|受動モードのためのポート範囲。ホストとコンテイナーのポートは一致する必要がある。ポート数が大きい場合はDockerが遅くなる可能性があるので、広い範囲は推奨しない。|
6474
|`FtpPasvAddress`|127.0.0.1|FTPサーバーの外部IPアドレス|
@@ -75,7 +85,7 @@ $ open http://localhost:3939
7585
7686
<sup>rin.avifイラストは<a href="https://twitter.com/Noartnolife1227/status/1531168810098917376">としたのあ (@Noartnolife1227)</a>による</sup>
7787
78-
Junkerの[C#のFTPサーバー](https://github.com/FubarDevelopment/FtpServer/)がファイルシステムの抽象化のインタフェースがあるのお陰で、私はバックエンドとしてsqlarserverの内部用のファイルツリーを使う実装を作成できた。これでSQLiteアーカイブの内容をFTP経由で参照できる!
88+
FubarDevの[C#のFTPサーバー](https://github.com/FubarDevelopment/FtpServer/)がファイルシステムの抽象化のインタフェースがあるのお陰で、私はバックエンドとしてsqlarserverの内部用のファイルツリーを使う実装を作成できた。これでSQLiteアーカイブの内容をFTP経由で参照できる!
7989
8090
![だがなぜ](.github/images/but%20why.avif)
8191
@@ -95,7 +105,7 @@ Junkerの[C#のFTPサーバー](https://github.com/FubarDevelopment/FtpServer/)
95105
96106
ポート21は何でもにマッピングできるけど、FTPプロトコルの一部はサーバーがデータ転送のためにどのIPとポートに接続すべきだとクライアントに伝えることなので、PASV<sup>1</sup>のポート範囲の10000-10009はホストとコンテイナーが一致する必要がある。`FtpPasvPorts`の設定で変更できる。もしサーバーがlocalhostで実行してなければ`FtpPasvAddress`をFTPクライアントに入力すると同じIPアドレスに設定する必要がある。
97107

98-
> <sup>1</sup> PASVとはFTPの受動モード(英:passive mode)に指して、サーバーがデータ転送のためにポートを開けてクライアントに接続するように指し示すことだ。(ポート21はコントロールで、コマンド通信のためでけに使われる。)その反対はアクティブモードで、ファイアウォールの前の時代🦕からだしサーバーが直接にクライアントへの接続を確立することだった。
108+
> <sup>1</sup> PASVとはFTPの受動モード(英:passive mode)に指して、サーバーがデータ転送のためにポートを開けてクライアントに接続するように指し示すことだ。(ポート21はコントロールで、コマンド通信のためだけに使われる。)その反対はアクティブモードで、ファイアウォールの前の時代🦕からだしサーバーが直接にクライアントへの接続を確立することだった。
99109
100110
> [!WARNING]
101111
> Dockerはデフォルトで公開されたポートを0.0.0.0にバインドして外部からアクセスできるようにファイアウォール規則を作るのだ。信頼できないネットワークでは、またはマシンがインターネットに開けてる場合は、明示的にlocalhostにバインドする(例えば、`-p 127.0.0.1:21:21`)か[デフォルトのバインド・アドレスを変更](https://docs.docker.com/network/packet-filtering-firewalls/#setting-the-default-bind-address-for-containers)しないとならない。(DockerのLinux版を使用しているWSLユーザーはこれを気にする必要はないはずだ。)
@@ -119,7 +129,7 @@ $ docker run -it --rm -v .:/srv -p 3939:80 -e StaticSite=true ghcr.io/maxkagamin
119129
>
120130
> <sup>2</sup> 中身を覗いてみて物事を実行しているDockerコンテイナーを見ない限りよね。でもインフラを無視するこそがサーバーレスじゃない?
121131
>
122-
> <sup>3</sup> 2をご参照。でもこれを[AOTコンパイル](https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0)できるなら、SQLiteデータベースを実行可能に埋め込む方法があると思う…🤔
132+
> <sup>3</sup> 2をご参照。でもこれを[AOTコンパイル](https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/native-aot?view=aspnetcore-8.0)できるなら、SQLiteデータベースを実行ファイルに埋め込む方法があると思う…🤔
123133
124134
## ライセンス
125135

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ $ open http://localhost:3939
3232

3333
An [SQLite Archive](https://sqlite.org/sqlar.html), or _sqlar_ for short (probably pronounced /ˈɛs kjuː&#8202;ˈlɑːr/, but I sometimes pronounce it /sklɑːr/ because "sqlarball" sounds funnier that way), is simply an SQLite database that uses a standard table schema¹ for storing files as blobs. The main page goes over some reasons for doing this (the big ones IMO being that your relational/indexed data and files are kept together, use the same ORM, and can have foreign key constraints that simply aren't possible when files are stored externally²), but the advantage of using the sqlar format over an ad hoc table is that it enables use of the sqlite3 CLI's tar-like options &mdash; and now this as well.
3434

35-
My motivation for making this was I had a large dataset and accompanying audio files, where upon deployment these would be pushed to Elasticsearch and S3 respectively, but until then needed to be stored in some intermediate location. I was already using Entity Framework and dumping the data into an sqlite file; at first, I was saving the audio files to a folder, but given that their filenames were SHA1 hashes, there wasn't really any meaning in having them accessible in this way. Rather, it burdened me with having to deal with Windows file paths, potential for missing files, and so on. Moving them into the sqlite file itself meant that the primary key was literally the S3 object name, and I could reference this table via a foreign key to enforce data integrity. Switching between my laptop and desktop is easier, too, as I can just copy the file over.³
35+
> [!TIP]
36+
> You can use a [view](https://en.wikipedia.org/wiki/View_(SQL)) to adapt an existing table to the sqlar schema (only sqlarserver supports this currently):
37+
> ```sql
38+
> CREATE VIEW sqlar(rowid, name, mode, mtime, sz, data) AS
39+
> SELECT rowid, filename, 33279, 0, length(content), content FROM files;
40+
> ```
41+
> The server uses SQLite's blob API for performance, so it needs to know the rowid (which we've included in the view) and the name of the underlying table and its blob column, which should be passed when running the server as `-e BlobTable=files -e BlobColumn=content`.
42+
43+
My motivation for making this was I had a large dataset and accompanying audio files, where upon deployment these would be pushed to Elasticsearch and S3 respectively, but until then needed to be stored in some intermediate location. At first, I was saving the audio files to a folder, but given that their filenames were SHA1 hashes, there wasn't really any meaning in having them accessible in this way. Rather, it burdened me with having to deal with Windows file paths, potential for missing files, and so on. Moving them into the sqlite file itself meant that the primary key was literally the S3 object name, and I could reference this table via a foreign key to enforce data integrity. Switching between my laptop and desktop is easier, too, as I can just copy the file over.³
3644
3745
But this provided a challenge: for development, I wanted my local server to point to the local audio files, not the production S3 bucket. A separate dev bucket would be overkill. Had these been files on disk, I could simply serve them as static files, but I didn't want to add Microsoft.Data.Sqlite as a dependency and inflate the docker image when it wouldn't need that in production. Preprocessor directives are ugly. What to do? Why not add a separate container to the compose file that just serves the blobs as static files, and use that as a stand-in for S3 in dev! And then, let's add directory listing, like Nginx or Apache! And symlink support, too, even though I wouldn't need it! And, and... _an FTP server!_ What a great idea! Surely this won't balloon into a whole project!
3846
@@ -52,13 +60,15 @@ The following options can be passed as environment variables:
5260
5361
|Environment&nbsp;variable|Default&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|Description|
5462
|---|---|---|
55-
|`TZ`|UTC|Timezone for displaying date modified (see [_List of tz database time zones_](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones))|
63+
|`TZ`|UTC|[Timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for displaying date modified|
5664
|`LANG`|en_US|Locale used for formatting numbers etc.|
5765
|`SizeFormat`|Binary|Bytes = Display file sizes in bytes without formatting<br />Binary = Use binary units (KiB, MiB, GiB, TiB)<br />SI = Use SI units (KB, MB, GB, TB)|
5866
|`DirectoriesFirst`|true|Group directories before files|
5967
|`CaseInsensitive`|false|Treat the archive as a case-insensitive filesystem|
6068
|`StaticSite`|false|Disable directory listing and serve index.html files where present and /404.html when not found|
6169
|`Charset`|utf-8|Sets the charset in the Content-Type header of file streams. Empty string to disable.|
70+
|`BlobTable`|sqlar|Name of the table holding the blob<br />_See the tip above on using a view for the sqlar table_|
71+
|`BlobColumn`|data|Name of the column holding the blob<br />_See the tip above on using a view for the sqlar table_|
6272
|`EnableFtp`|false|Start the FTP server|
6373
|`FtpPasvPorts`|10000-10009|Port range used for passive mode. Host and container ports must match. Avoid too large a range, as many ports can make docker slow.|
6474
|`FtpPasvAddress`|127.0.0.1|The FTP server's external IP address|
@@ -75,7 +85,7 @@ The following options can be passed as environment variables:
7585
7686
<sup>rin.avif artwork by <a href="https://twitter.com/Noartnolife1227/status/1531168810098917376">としたのあ (@Noartnolife1227)</a></sup>
7787
78-
Thanks to Junker's [C# FTP server](https://github.com/FubarDevelopment/FtpServer/) having an abstracted file system interface, I was able to write an implementation that uses sqlarserver's internal file node tree as a backend. Now you can browse the contents of an SQLite Archive via FTP!
88+
Thanks to FubarDev's [C# FTP server](https://github.com/FubarDevelopment/FtpServer/) having an abstracted file system interface, I was able to write an implementation that uses sqlarserver's internal file node tree as a backend. Now you can browse the contents of an SQLite Archive via FTP!
7989
8090
![But why?](.github/images/but%20why.avif)
8191

SqliteArchive.Server/appsettings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"CaseInsensitive": false,
1414
"StaticSite": false,
1515
"Charset": "utf-8",
16+
"BlobTable": "sqlar",
17+
"BlobColumn": "data",
1618
"EnableFtp": false,
1719
"FtpPasvPorts": "10000-10009",
1820
"FtpPasvAddress": "127.0.0.1"

0 commit comments

Comments
 (0)