1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CSVファイル群をSQL文で操作できるCライブラリを作ってみた

Last updated at Posted at 2025-09-24

まだ開発中なんですがCSVファイル群をSQL文で操作できるCライブラリを作りました。

使用風景

	CsvTomatoError error = {0};
	CsvTomato *db;
	CsvTomatoStmt *stmt;

	db = csvtmt_open("test_db", &error);
	if (error.error) {
		csvtmt_error_show(&error);
		return;
    }

    // execute関数の実行
	csvtmt_exec(
		db,
		"CREATE TABLE IF NOT EXISTS users ("
		"	id INTEGER PRIMARY KEY AUTOINCREMENT,"
		"	name TEXT NOT NULL,"
		"	age INTEGER"
		");",
		&error
	);
	csvtmt_exec(
		db,
		"INSERT INTO users (name, age) VALUES (\"Alice\", 20);",
		&error
	);

    // プレースホルダの使用
	// stmtのメモリを確保
	csvtmt_prepare(
		db,
		"INSERT INTO users (name, age) VALUES (?, ?);",
		&stmt,
		&error
	);

	// プレースホルダに値を紐づける
	csvtmt_bind_text(
		stmt,
		1,
		"Bob",
		-1,
		CSVTMT_TRANSTENT,
		&error
	);
	csvtmt_bind_int(
		stmt,
		2,
		30,
		&error
	);
	
	// 実行
	csvtmt_step(stmt, &error);

	// stmtのメモリを解放
	csvtmt_finalize(stmt);

    csvtmt_close(db);

開発動機

CSVファイルをSQL文で操作出来たら面白いのでは? というありがちなアイデアを思いつきました。
実際に作ってみてどんなもんか確認してみることにしました。

ライブラリの設計

インターフェースはSQLiteを踏襲するようにしました。

いまのところ実装している文は

  • CREATE TABLE文
  • INSERT文
  • SELECT文
  • UPDATE文
  • DELETE文

のみです。
またSQLiteに比べると機能は貧弱で、実装していない文法が多いです。

エラーハンドリング

エラーハンドリングはCsvTomatoErrorという構造体で行います。
関数内でエラーが起こるとメンバのerrortrueになります。

	db = csvtmt_open("test_db", &error);
	if (error.error) { // エラー発生!
        // エラーメッセージを出力
		csvtmt_error_show(&error);
		return;
    }

データベースのオープン

csvtmt_open()の第1引数に指定する文字列はデータベース名です。
このライブラリではデータベースはフォルダになります。
CSVファイルはこの場合、実行時フォルダのtest_dbフォルダ以下に作成されます。

	db = csvtmt_open("test_db", &error);
	if (error.error) {
		csvtmt_error_show(&error);
		return;
    }

CREATE TABLE

	csvtmt_exec(
		db,
		"CREATE TABLE IF NOT EXISTS users ("
		"	id INTEGER PRIMARY KEY AUTOINCREMENT,"
		"	name TEXT NOT NULL,"
		"	age INTEGER"
		");",
		&error
	);

テーブル(CSVファイル)を作成します。
IF NOT EXISTSが付いている場合はすでにテーブルが存在する場合は何もしません。
IF NOT EXISTSが付いてなくてテーブルが既に存在する場合はエラーになります。

INSERT

	csvtmt_exec(
		db,
		"INSERT INTO users (name, age) VALUES (\"Alice\", 20);",
		&error
	);

テーブル(CSVファイル)の末尾にレコードを追記します。
上記ではtest_db/users.csvファイルにレコードを追記しています。

SELECT

	db = csvtmt_open(db_dir, &error);
	csvtmt_prepare(db, "SELECT id, age FROM users;", &stmt, &error);

	while (csvtmt_step(stmt, &error) == CSVTMT_ROW) {
        int id = csvtmt_column_int(stmt, 0);
        const char *name = csvtmt_column_text(stmt, 1);
        printf("%d %s\n", id, name);
    }

	csvtmt_finalize(stmt);
	csvtmt_close(db);

SELECT文の仕様もSQLiteと同じにしました。
csvtmt_prepare()でクエリを準備し、csvtmt_step()で順次実行します。
csvtmt_step()の返り値がCSVTMT_ROWの場合はstmtに行データが入っているので、csvtmt_column_int()などでカラムを取り出します。

UPDATE

    csvtmt_exec(
        db,
        "UPDATE users SET age = 30 WHERE name = \"Alice\;",
        &error
    );

レコードを更新します。
WHEREで該当するとレコードのカラムを変更できます。
UPDATEは内部的にレコードを論理削除してから追記する、と言う仕様になっています。

DELETE

    csvtmt_exec(
        db,
        "DELETE FROM users WHERE age = 30;",
        &error
    );

レコードを削除します。
DELETEはレコードを論理削除します。

ここまで作ってみて思ったこと

CSVファイルへのCRUDの設計をどうしたもんか考えましたが、前述のような仕様になりました。
UPDATE, DELETEはCSVファイル全体の置き換えが必要になるため、コストが高くなります。
そこで論理削除とmmapを使ってコストを下げられないかなーと実装してみました。
テキストファイルは追記はコストが低いんですが、そのほかの編集がコストが高くなる性質があるので、今回の実装をしてみました。
まだ性能は計測してません。

CSVファイルは表計算ソフトで編集できるので、データベースを表計算ソフトで閲覧&編集できるというアドバンテージがあります(多分)。
もっとも、大量データを扱うのには向いていないので、ごくごく小規模なアプリのデータベースに使うという前提で妄想してました。

更新履歴

2025-09-26: SELECT文を実装しました。

おわりに

まだバグとか変なところはあると思いますが、実用性が出てきたら自分のアプリにも使ってみたいです。
テストはちゃんと書かないといけませんね。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?