嵌入式linux項目開發(一)——SQLite資料庫
一、SQLite資料庫簡介
SQLite是一個開源的嵌入式關系資料庫,是一種輕量級的、自給自足的、無伺服器的、無需配置的、事務性的SQL資料庫引擎,其特點是高度便攜、使用友善、結構緊湊、高效、可靠,體積小,支援ACID(原子性、一緻性、獨立性及持久性Atomicity、Consistency、Isolation、Durability)事物。
SQLite資料庫采用子產品化設計,由8個獨立的子產品構成,構成三個主要的子系統。SQLite的基本架構:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iNyUWNygjY1MGZhRDM0MTM2kDOwcjNxQDN2UTZ5MzM38CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
sqlite主要由8個構件子系統(子產品)組成,分為兩部分:前端解析系統和後端引擎。
前端:
前端預處理應用程式傳遞過來的SQL語句和SQLite指令。對擷取的編碼分析、優化,轉換為後端能夠執行的SQLite内部位元組編碼并執行。前端可分為5個子產品:
A、接口(Interface)
所有的SQLite API命名以sqlite3_為字首,接收SQL語句和sqlite指令
B、辨別分析(Tokenizer)
tokenizer是負責把SQL語句解析為一個個的“串”,将輸入的SQL語句分成辨別符。
C、文法分析(Parser)
Paser根據tokenizer分割的“串”的前後序列關系來生成相應的文法結構。解析器分析通過辨別器産生的辨別分析語句的結構,并且得到一顆文法樹。解析器同時也包含了重構文法樹的優化器,是以能夠找到一顆産生一個高效的位元組編碼程式的文法樹。
D、代碼生成器(Code Generator)
代碼生成器周遊文法樹,并且生成一個等價的位元組編碼程式,生成Virtual Machine可以執行的高效代碼
E、虛拟機(VM)
虛拟機(Virtual Machine)是為操作資料庫檔案而執行的一個抽象的計算機引擎。VM子產品是一個内部位元組編碼語言的解釋器,通過執行Code Generator 生成的代碼位元組編碼語句來實作SQL語句的工作,是資料庫中資料的最終的操作者。虛拟機把資料庫看成表和索引的集合,而表和索引則是一系列的元組或者記錄。
後端:
後端是用來解釋位元組編碼程式的引擎,實作資料庫處理工作。後端部分由3個子產品組成:
A、B/B+樹
SQLite資料庫檔案在磁盤中是以B樹的資料結構存儲。B樹結構,用于存儲資料庫到磁盤,可以通過減少磁盤的查找來達到快速通路資料的目的。B/B+樹子產品把每一個元組集組織進一個一次排好序的樹狀資料結構中,表和索引被分别置于單獨的B+和B樹中,幫助VM進行搜尋,插入和删除樹中的元組,幫助VM建立新的樹和删除舊的樹。
B、頁面排程程式(pager)
頁面排程程式子產品在原始檔案的上層實作了一個面向頁面的資料庫檔案抽象,用來管理B/B+樹使用的記憶體内緩存(資料庫頁的),也管理檔案的鎖定,并用日志來實作事物的ACID屬性。頁面緩沖主要處理讀、寫以及B樹存儲機制所需的數字緩沖,包括為了保證事務原子性的回退及送出操作所需的緩沖。
C、作業系統接口(system interface)
作業系統接口子產品提供了對應于不同本地作業系統的統一接口,作業系統接口主要是為了友善在不同平台的操作而執行的一個底層與作業系統有關的抽象層。
後端實作了sqlite3_bind_*,sqlite3_setp,sqlite3_coloumn_*,sqlite3_reset和sqlite3_finalize API函數。
SQLite體系結構的核心是虛拟資料庫引擎(VDBE)。VDBE完成與資料庫操作相關的全部操作并且是客戶和存儲之間資訊進行互動的中間單元。在SQL語句被分析之後,代碼生成器将分析樹翻譯成一個袖珍程式,袖珍程式又被組合成用VDBE的虛拟機器語言表示的一系列指令。VDBE執行每條指令,最終完成SQL語句指定的查詢請求。
二、SLQite資料庫移植
1、下載下傳SQLite源碼
tar -zxvf sqlite-amalgamation-3.6.1.tar.gz
2、配置
進入SQLite源碼工程頂層目錄sqlite-auto,建立安裝sqlite-arm目錄,執行配置腳本
mkdir sqlite-arm
./configure --host=arm-linux --prefix=/home/opensource/sqlite-auto/sqlite-arm/
--host: 指定交叉編譯工具,一般為arm-linux、arm-none-linux-gnueabi等,具體要和闆子用的交叉編譯工具對應。
--prefix: 指定安裝目錄,編譯後的檔案會全部放在安裝目錄中。必須是絕對路徑
3、編譯、安裝
make & make install
4、去除調試資訊
使用arm-linux-strip去除sqlite-arm目錄下的bin目錄和lib目錄下的檔案的調試資訊,即去除需要移植到開發闆的檔案的調試資訊,節省空間
5、sqlite3移植
将sqlite-arm目錄下的bin目錄下的sqlite3程式拷貝到開發闆/usr/bin目錄
将sqlite-arm目錄下的lib目錄下的所有檔案拷貝到開發闆/lib目錄或/usr/lib目錄
三、SQLite移植過程中錯誤的解決
将sqlite移植到開發闆後,執行sqlite3,報錯:-/bin/sh: sqlite3: not found
解決方案:将交叉編譯工具的動态連結庫拷貝到開發闆/lib或/usr/lib
檢視sqlite3所依賴的動态連結庫,readelf -d sqlite3
0x00000001 (NEEDED) Shared library: [libdl.so.2]
0x00000001 (NEEDED) Shared library: [libpthread.so.0]
0x00000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x00000001 (NEEDED) Shared library: [libc.so.6]
發現sqlite3依賴四個動态連結庫檔案,需要将這四個動态連結庫檔案(如果有連結檔案,包括連結檔案所指向的檔案)拷貝到開發闆/lib或/usr/lib。如果開發闆存儲空間允許,可以将交叉編譯工具鍊的所有動态連結庫檔案拷貝到開發闆。
注意所有對開發闆的移植必須包括ARM交叉編譯工具鍊的動态連結庫,否則将會報錯-/bin/sh: xxxx: not found
四、SQLite SQL語句
1、sqlite指令
建立或打開一個資料庫:sqlite3 dbname.db(所在目錄必須具有讀寫權限)
版本檢視:sqlite3 -version
建立資料庫:sqlite3 xxxx.db
退出sqlite指令行:.quit或.exit或.q
列出目前顯示格式的配置:.show
檢視資料庫中的表:.tables
顯示表結構:.schema
退出資料庫:ctrl+d
.dump:生成整個資料庫的腳本在終端顯示.outputstdout:将輸出列印到螢幕.output filename:導出資料庫到SQL檔案
.read filename:從SQL檔案導入資料庫
.outputfilename.csv:格式化輸出資料到CSV格式
.import [filename.csv ] newtable:從CSV檔案導入資料到表中
sqlite3 [database] .dump > [filename]:備份資料庫執行個體:sqlite3 mytable.db .dump > backup.sql
sqlite3 [database ] < [filename ]:恢複資料庫執行個體:sqlite3 mytable.db < backup.sql
2、字段類型
NULL: 空值
INTEGER: 整數,依據值的大小可以依次被存儲為1,2,3,4,5,6,7,8個位元組
REAL: 所有值都是浮動的數值,被存儲為8位元組的IEEE浮動标記序号
TEXT: 文本,值為文本字元串,使用資料庫編碼存儲(TUTF-8, UTF-16BE or UTF-16-LE).
BLOB: 值是BLOB資料,如何輸入就如何存儲,不改變格式
SQL語句中部分的帶雙引号或單引号的文字被定義為文本,如果文字沒帶引号并沒有小數點或指數則被定義為整數,如果文字沒帶引号但有小數點或指數則被定義為實數,如果值是空則被定義為空值,BLOB資料使用符号X'ABCD'來辨別。
smallint 16位的整數。
interger 32位的整數。
decimal(p,s) 精确值p是指全部有幾個十進制數,s是指小數點後可以有幾位小數。如果沒有特别指定,則系統會預設為p=5 s=0。
float 32位元的實數。
double 64位元的實數。
char(n) n 長度的字串,n不能超過254。
varchar(n) 長度不固定且其最大長度為n的字串,n不能超過4000。
graphic(n) 和char(n)一樣,不過其機關是兩個位元組,n不能超過127。這個形态是為了支援兩個位元組長度的字型,如中文字。
vargraphic(n) 可變長度且其最大長度為n的雙字元字串,n不能超過2000
date 包含了 年份、月份、日期。
time 包含了 小時、分鐘、秒。
timestamp 包含了 年、月、日、時、分、秒、千分之一秒。
3、表操作
建立表
create table table_name(field type1,fieldtype1,….);
table_name是要建立資料表的名稱,field是資料庫表内字段名字,type則是字段類型。例如:
CREATE TABLEstudent(
ID INTEGER PRIMARY KEY,
LastName varchar(255),
FirstName varchar(255)
);
删除表
DROP TABLE tableName;
檢視表
SELECT * FROM tablename WHERE someField = 'value' COLLATE NOCASE;
4、字段操作
A、向表中添加新記錄
insert intotabelnamevalues (value1, value2,…);
執行個體:
insert into people values(1,'A',10);
insert into people values(2,'B',13);
insert into people values(3,'C',9);
insert into people values(4,'C',15);
insert into people values(5,NULL,NULL);
字元串要用單引号括起來
B、查詢表中所有記錄
select * fromtablename;
執行個體:select * from people;
按某個字段查詢表中沒有重複的條目
SELECT distinct someField FROM table
C、按指定條件查詢表中記錄
select * fromtablenamewherefield=value;
執行個體:
在表中搜尋名字是A的項所有資訊
select * from people where name='A';
在表中搜尋年齡>=10并且<=15的項的所有資訊
select * from people where age>=10 and age<=15;
在表中搜尋名字是C的項,顯示其name和age
select name,age from people where name='C';
顯示表中的前2項所有資訊
select * from people limit 2;
顯示以年齡排序表中的資訊
select * from people order by age;
D、按指定條件删除表中記錄
delete from where
執行個體:
删除表中名字是'C'的項
delete from pople where name='C';
E、更新表中記錄
update set , … where ;
執行個體:
将表中年齡是15并且ID是4項,名字改為CYG
update people set name='CYG' where id=4 and age=15;
用一張表TableB裡的一個字段fieldB内容給另外一張表TableA裡的一個字段fieldA指派:
UPDATE TableA SET fieldA = TableB.fieldB
如果是同一張表TableA中,用一個字段field1的值給表中的另外一個字段field2值指派:
UPDATE TableA SET field2 = field1
如果需要把一個字元串('someString')和一個字段field1的值進行連接配接,然後指派給一個字段field2:
UPDATE TableA SET field2 = field1 || 'someString'
F、在表中添加字段
alter table add column ;
執行個體:
在people表中添加一個addr字段
alter table people add column addr;
G、删除表中的一個字段
删除people表中字段addr,操作流程如下:
将people表重命名為temp
重新建立people表
将temp表中的相應字段内容複制到people表中
删除temp表
SQL語句如下:
alter table people rename to temp;
create table people(id,name,age);
insert into people select id,name,age from temp;
drop tabletemp;
H、表的導入
把一張表TableA裡的資料導入到另外一張表TableB中(兩張表中的結構和字段必須一樣):
INSERT INTO TableB SELECT * FROM TableA
5、分組統計
CREATE TABLE COMPANY(ID INT NOT NULL, NAME VARCHAR(20),AGE INT,ADDRESS VARCHAR(20),SALARY DECIMAL(7,2));
GROUP BY 進行分組統計資料,指令如下:
SELECT NAME, SUM(SALARY) SALARY_SUM, COUNT(1) COUNT_NUM FROM COMPANY GROUP BY NAME;
6、排序
ORDER BY 進行排序,指令如下:
SELECT NAME, SUM(SALARY) SALARY_SUM, COUNT(1) COUNT_NUM FROM COMPANY GROUP BY NAME ORDER BY SALARY_SUM ASC;
7、從excel表中導入資料
A、将Excel之中存儲的資料另存為csv的格式bookroom.csv,注意不要帶表頭,隻要資料就行
B、根據要導入的表屬性建立表
create table bookroom(id integer, roomname nvarchar(20), mapfilename nvarchar(20));
C、設定資料的分隔符
.separator ',';
D、将資料導入表
.import bookroom.csv bookroom
.import
E、查閱插入的資料
select * from bookroom;
五、SQLite資料庫程式設計
SQLite資料庫程式設計最常用到的是sqlite3 *類型。從資料庫打開開始,sqlite就要為sqlite3 *類型準備好記憶體,直到資料庫關閉,整個過程都需要用到sqlite3 *類型。當資料庫打開時開始,sqlite3 *類型的變量就代表了要操作的資料庫,即句柄。
1、sqlite3_open
int sqlite3_open(char *path,sqlite3 **db);
功能:打開sqlite資料庫
path:資料庫檔案路徑(如果不存在,則建立)
db:指向sqlite句柄的指針
傳回值:如果是SQLITE_OK則表示操作正常。其他的傳回值參考sqlite3.h檔案定義的宏。
2、sqlite3_close
int sqlite3_close(sqlite3 *db);
功能:關閉sqlite資料庫
放回值:成功傳回0,失敗傳回錯誤碼
3、sqlite3_errmsg
const char *sqlite3_errmsg(sqlite3 *db);
傳回值:傳回錯誤資訊
4、執行SQL語句
typedef int (*sqlite3_callback)(void *, int ,char **, char **);
int sqlite3_exec(sqlite3 *db , const char *sql ,
sqlite3_callback callback ,void *arg, char **errmsg );
db參數是前面sqlite3_open函數得到的指針
sql參數是一條字元串格式sql語句,以\0結尾。
callback參數是回調函數,當這條語句執行之後,sqlite3會去調用你提供的這個函數。
arg參數是可以傳遞的指針參數,傳到回調函數裡面,如果不需要傳遞指針給回調函數,可以填NULL。
errmsg 參數是錯誤資訊。sqlite3裡面有很多固定的錯誤資訊。執行sqlite3_exec後,如果執行失敗可以查閱這個指針。
傳回值:成功傳回0,失敗傳回錯誤碼
如果調用sqlite3_exec失敗,printf("%s\n",errmsg)可以得到一串字元串錯誤資訊。
sqlite3_callback callback和void *arg都可以填NULL。NULL表示不需要回調。比如insert操作,delete操作,就沒有必要使用回調。而當做select時,就要使用回調,因為sqlite3把資料查出來,得通過回調告訴你查出了什麼資料。
typedef int (*sqlite3_callback)(void *para,int n_column,char **column_value,char **column_name);
para參數:傳入的特殊指針(比如類指針、結構指針),然後操作指針指向的資料。
n_column:是記錄有多少個字段(即記錄有多少列)。
char **column_value:是個關鍵值,查出來的資料都儲存在這裡,它實際上可以看做是一個一維的指針數組,每個元素都是一個char *值,是一個字段的内容(用字元串來表示,以\0結尾)。
char **column_nam:跟column_value是對應的,表示這個字段的字段名稱 。
程式執行個體sqlite3_callback.c:#include
#include
#include
#include
#define MAX 100
typedef int (*sqlite3_callback)(void *, int , char **, char **);
int show_table_info(void *arg, int n_column, char **column_value, char **column_name)
{
int i = 0;
for(i = 0; i
{
printf("%s\t", column_name[i]);
}
printf("\n*************************************\n");
for(i = 0; i
{
printf("%s\t", column_value[i]);
}
printf("\n\n");
return 0;
}
int exec_sql_string(char *sql_string, sqlite3 *db)
{
char *errmsg;
if(sqlite3_exec(db, sql_string, show_table_info, NULL, &errmsg) != 0)
{
fprintf(stderr, "Fail to exec sql(%s) : %s.\n", sql_string,errmsg);
return -1;
}
return 0;
}
int main(int argc,char *argv[])
{
sqlite3 *db;
int result;
char sql_buf[MAX];
if(argc
{
fprintf(stderr, "usage : %s argv[1].\n", argv[0]);
exit(EXIT_FAILURE);
}
result = sqlite3_open(argv[1], &db);
if(result != SQLITE_OK)
{
fprintf(stderr, "Fail to sqlite3_open %s : %s.\n", argv[1], sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
while(1)
{
printf("sqlite >");
fgets(sql_buf, sizeof(sql_buf), stdin);
sql_buf[strlen(sql_buf) - 1] = '\0';
if(strncmp(sql_buf, ".quit", 5) == 0)
break;
exec_sql_string(sql_buf,db);
}
result = sqlite3_close(db);
if(result != 0)
{
fprintf(stderr, "Fail to sqlite3_open %s : %s.\n", argv[1], sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
編譯:gcc -o sqlite3_callback sqlite3_callback.c -lsqlite3
5、不使用回調函數執行SQL語句
int sqlite3_get_table(sqlite3 *db, const char *sql, char ***resultp,
int *nrow, int *ncolumn,char **errmsg);
功能:執行sql操作
db : 資料庫句柄
sql : sql語句
resultp : 用來指向sql執行結果的指針
nrow : 滿足條件的記錄的數目
ncolumn : 每條記錄包含的字段數目
從第0索引到第ncolumn-1索引都是字段的名稱
從第ncolumn索引開始,後面都是字段的值
errmsg : 錯誤資訊指針的位址
傳回值:成功傳回0,失敗傳回錯誤碼
程式執行個體sqlite3_nocallback.c:#include
#include
#include
#include
#define MAX 100
int exec_sql_string(char *sql_string, sqlite3 *db)
{
char *errmsg, **dbResult;
int nRow, nColumn;
int result, i, j, index;
result = sqlite3_get_table(db, sql_string, &dbResult, &nRow, &nColumn, &errmsg);
if(0 != result)
{
fprintf(stderr, "Fail to exec sql(%s) : %s.\n", sql_string, errmsg);
return -1;
}
//字段名字
for(j = 0; j
{
printf("%s\t", dbResult[j]);
}
printf("\n");
index = nColumn;//從它開始是字段對應的值
for(i = 0; i
{
for(j = 0; j
{
printf("%s\t", dbResult[index]);
index++;
}
printf("\n");
}
//釋放查詢結果所配置設定的記憶體
sqlite3_free_table(dbResult);
return 0;
}
int main(int argc, char *argv[])
{
sqlite3 *db;
int result;
char sql_buf[MAX];
if(argc
{
fprintf(stderr, "usage : %s argv[1].\n", argv[0]);
exit(EXIT_FAILURE);
}
result = sqlite3_open(argv[1], &db);
if(result != SQLITE_OK)
{
fprintf(stderr, "Fail to sqlite3_open %s : %s.\n", argv[1], sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
while(1)
{
printf("sqlite >");
fgets(sql_buf, sizeof(sql_buf), stdin);
sql_buf[strlen(sql_buf) - 1] = '\0';
if(strncmp(sql_buf,".quit",5) == 0)
break;
exec_sql_string(sql_buf, db);
}
result = sqlite3_close(db);
if(result != 0)
{
fprintf(stderr, "Fail to sqlite3_open %s : %s.\n", argv[1], sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
編譯: gcc -o sqlite3_nocallback sqlite3_nocallback.c -lsqlite3