サンプルプログラムの実行
「shell.c」は、SQLiteを操作するユーティリティとして便利ではありますが、結構きっちり作られていて、ソースを読むには、ちょっと長いかも知れません。そこで、もっと短いサンプルプログラムを作りましたので、そちらを使ってライブラリの使い方を説明します。
// sample.c // written by mnagaku @ 2006/04/27 // id, worker, change_line_count, commit_date // の形式のデータをDBに取り込み、 // change_line_countの総計の多いworkerのtop10と // commit数(データ数)の総計の多いworkerのtop10を調べます #include <stdlib.h> #include <stdio.h> #include <string.h> #include "sqlite3.h" #define MAX_STR 256 #define MAX_COLUMN 4 // SQL文 #define CREATE_TABLE "CREATE TABLE sample (id INTEGER PRIMARY KEY, \ worker TEXT NOT NULL, change_line_count INTEGER NOT NULL, \ commit_date TEXT NOT NULL)" #define INSERT_TABLE "INSERT INTO sample (id, worker, change_line_count, \ commit_date) values (%s, '%s', %s, '%s')" #define SELECT_TABLE "SELECT worker, sum(change_line_count) \ AS total_change_line_count FROM sample GROUP BY worker \ ORDER BY total_change_line_count DESC LIMIT 10" #define SELECT_TABLE2 "SELECT worker, count(id) AS total_commit_count \ FROM sample GROUP BY worker ORDER BY total_commit_count DESC LIMIT 10" // DBの検索結果を大域変数で保持 typedef struct { char values[MAX_COLUMN][MAX_STR]; } RAW; RAW *sqlResult; int rawSize, columnSize; // 検索結果を1行取得する度に呼ばれるコールバック関数 static int callback(void *option, int columnCount, char **columnValues, char **columnNames) { int columnCounter; if(sqlResult == NULL) { sqlResult = (RAW *)malloc(MAX_COLUMN * MAX_STR * 2); columnSize = columnCount; } else if((sqlResult = (RAW *)realloc(sqlResult, MAX_COLUMN * MAX_STR * (rawSize + 2))) == NULL) return -1; for(columnCounter = 0; columnCounter < MAX_COLUMN && columnCounter < columnCount; columnCounter++) { if(rawSize == 1) strncpy(sqlResult[0].values[columnCounter], columnNames[columnCounter], MAX_STR); strncpy(sqlResult[rawSize + 1].values[columnCounter], columnValues[columnCounter], MAX_STR); } rawSize++; return 0; } // SQLの実行 void sqlExec(sqlite3 *db, const char *sql, sqlite3_callback cb) { char *pStr; free(sqlResult); sqlResult = NULL; rawSize = 0; if(sqlite3_exec(db, sql, cb, NULL, &pStr) != SQLITE_OK) { fprintf(stderr, "Error %s %d: %s\n%s\n", __FILE__, __LINE__, pStr, sql); sqlite3_close(db); exit(0); } } // メイン void main() { char str[MAX_STR], data[4][MAX_STR], *pStr; int lineCount, dataCount; FILE *fp; sqlite3 *db; // メモリ上にDBを構築 if(sqlite3_open(":memory:", &db)) { fprintf(stderr, "Error %s %d: Can't open database %s\n", __FILE__, __LINE__, sqlite3_errmsg(db)); sqlite3_close(db); return; } // テーブル作成 sqlExec(db, CREATE_TABLE, NULL); // データファイル読込み if((fp = fopen("sample.csv", "rt")) == NULL) { fprintf(stderr, "Error %s %d: Can't open datafile.\n", __FILE__, __LINE__); sqlite3_close(db); return; } for(lineCount = 0; fgets(str, MAX_STR, fp) != NULL; lineCount++) { for(pStr = strtok(str, ","), dataCount = 0; dataCount < 4 && strlen(pStr) > 0 && pStr[0] != '\n'; pStr = strtok(NULL, ","), dataCount++) strncpy(data[dataCount], pStr, MAX_STR); if(dataCount < 4) { printf("Warnning %s %d: Find illegal data at %d\n", __FILE__, __LINE__, lineCount); break; } // 読み込んだデータをDBに投入 sprintf(str, INSERT_TABLE, data[0], data[1], data[2], data[3]); sqlExec(db, str, NULL); } fclose(fp); // DBの検索 puts("total_change_line_count top10"); sqlExec(db, SELECT_TABLE, callback); for(lineCount = 0; lineCount <= rawSize; lineCount++) { for(dataCount = 0; dataCount < columnSize; dataCount++) printf("%s,", sqlResult[lineCount].values[dataCount]); puts(""); } puts(""); puts("total_commit_count top10"); sqlExec(db, SELECT_TABLE2, callback); for(lineCount = 0; lineCount <= rawSize; lineCount++) { for(dataCount = 0; dataCount < columnSize; dataCount++) printf("%s,", sqlResult[lineCount].values[dataCount]); puts(""); } sqlite3_close(db); }
「sample.c」が、そのサンプルプログラムですが、「sample.c」で処理を行うためのデータが必要になりますので、サンプルデータを生成する「makedata.c」も作成しました。
// makedata.c // written by mnagaku @ 2006/04/27 // ダミーデータを生成します // データ形式は次のとおり // id, worker, change_line_count, commit_date #include <stdlib.h> #include <stdio.h> #include <time.h> #define MAX_LINE 100000 void main() { int i; int year = 2000; int month = 1; int day = 1; int hour = 0; int minutes = 0; int second = 0; srand(time(NULL)); for(i = 0; i < MAX_LINE; i++) { second = (second + rand()) % 60; minutes += rand() % 60 + 1; if(minutes >= 60) { minutes %= 60; hour++; } if(hour >= 24) { hour %= 24; day++; } if(day >= 29) { day = 1; month++; } if(month >= 13) { month = 1; year++; } printf("%d,%c%c,%d,%04d-%02d-%02d %02d:%02d:%02d,\n", i + 1, (rand() % 2) ? ('a' + rand() % 26) : ('A' + rand() % 26), (rand() % 2) ? ('a' + rand() % 26) : ('A' + rand() % 26), rand() % 100 + 1, year, month, day, hour, minutes, second); } }
まず、「makedata.c」をコンパイル・実行し、サンプルデータを作成してください。
> cl makedata.c > makedata > sample.csv
作成されたサンプルデータは、オープンソースプロジェクトのソースリポジトリに対するコミットの履歴だと考えてください。データは、通し番号を示すid、コミットした作業者名を示すworker、ソースの変更行数を示すchange_line_count、コミットした日時を示すcommit_dateで構成され、コミット10万件分のデータとなっています。カンマ区切りテキストではありますが、10万件もあるので、Excelでは開けません。
次に、「sample.c」をコンパイル・実行し、サンプルデータから、ソースの変更行数の総計が多い作業者top10と、コミットした回数が多い作業者top10を調べます。
> cl /D_CRT_SECURE_NO_DEPRECATE sample.c sqlite335.lib > sample total_change_line_count top10 worker,total_change_line_count, Kx,3138, zJ,3035, QQ,2979, OD,2901, ga,2848, xJ,2824, HH,2820, qS,2810, DJ,2803, Uw,2799, total_commit_count top10 worker,total_commit_count, Kx,61, Qm,56, Jp,55, HW,54, HJ,53, HN,53, Kt,53, uw,53, xR,53, AT,52,
「sample.c」はSQLiteの機能を使うので、「shell.c」同様に、ライブラリとリンクさせます。
実行すると数分で結果が表示されるでしょう。最近のそこそこのスペックのPCであれば、1分程度で結果が出るでしょう。
