一個由 Golang 撰寫且比起部分 ORM 還要讚的 MySQL 指令建置函式庫。彈性高、不需要建構體標籤。實際上,這就只是 PHP-MySQLi-Database-Class 不過是用在 Golang 而已(但還是多了些功能)。
這是一個 SQL 指令建構庫,本身不帶有任何 SQL 連線,適合用於某些套件的基底。
露西婭是一個由 Golang 撰寫的 MySQL 的指令建置函式庫(不是 ORM,永遠也不會是),幾乎所有東西都能操控於你手中。類似自己撰寫資料庫指令但是更簡單,JOIN 表格也變得比以前更方便了。
- 幾乎全功能的函式庫。
- 自動避免於 Goroutine 發生資料競爭的設計。
- 容易理解與記住、且使用方式十分簡單。
- SQL 指令建構函式。
- 資料庫表格建構協助函式。
- 彈性的建構體映射。
- 可串連的使用方式。
- 支援子指令(Sub Query)。
- 透過預置聲明(Prepared Statement),99.9% 避免 SQL 注入攻擊。
Gorm 已經是 Golang 裡的 ORM 典範,但實際上要操作複雜與關聯性高的 SQL 指令時並不是很合適,而 Rushia 解決了這個問題。Rushia 也試圖不要和建構體扯上關係,不希望使用者需要手動指定任何標籤在建構體中。
打開終端機並且透過 go get
安裝此套件即可。
$ go get gopkg.in/teacat/rushia.v1
在 Rushia 中為了配合 Golang 程式命名規範,我們建議你將所有事情以駝峰式大小寫命名,因為這能夠確保兩邊的風格相同。事實上,甚至連資料庫內的表格名稱、欄位名稱都該這麼做。當遇上 ip
、id
、url
時,請遵循 Golang 的命名方式皆以大寫使用,如 AddrIP
、UserID
、PhotoURL
,而不是 AddrIp
、UserId
、PhotoUrl
。
在 Golang 裏處理資料庫的 NULL 值向來都不是很方便,因此不建議允許資料庫中可有 NULL 欄位。
Rushia 的使用方式十分直覺與簡易,類似基本的 SQL 指令集但是更加地簡化了。
你能夠直接將一個建構體傳入 Insert
或是 Update
之中,其欄位名稱與值都會被自動轉換 (注意!這並不會轉換成 MySQL 最常用的 snake_case
!)。
type User struct {
Username string
Password string
}
u := User{
Username: "YamiOdymel",
Password: "test",
}
db.Table("Users").Insert(u)
// 等效於:INSERT INTO Users (Username, Password) VALUES (?, ?)
透過 Omit
,你可以省略建構體中的某些欄位。
type User struct {
Username string
Password string
}
u := User{
Username: "YamiOdymel",
Password: "test",
}
db.Table("Users").Omit("Username").Insert(u)
// 等效於:INSERT INTO Users (Password) VALUES (?)
透過 Rushia 你可以很輕鬆地透過建構體或是 map 來插入一筆資料。這是最傳統的插入方式。
db.Table("Users").Insert(map[string]interface{}{
"Username": "YamiOdymel",
"Password": "test",
})
// 等效於:INSERT INTO Users (Username, Password) VALUES (?, ?)
覆蓋的用法與插入相同,當有同筆資料時會先進行刪除,然後再插入一筆新的,這對有外鍵的表格來說十分危險。
db.Table("Users").Replace(map[string]interface{}{
"Username": "YamiOdymel",
"Password": "test",
})
// 等效於:REPLACE INTO Users (Username, Password) VALUES (?, ?)
插入時你可以透過 Rushia 提供的函式來執行像是 SHA1()
或者取得目前時間的 NOW()
,甚至將目前時間加上一年⋯等。
db.Table("Users").Insert(map[string]interface{}{
"Username": "YamiOdymel",
"Password": db.Func("SHA1(?)", "secretpassword+salt"),
"Expires": db.Now("+1Y"),
"CreatedAt": db.Now(),
})
// 等效於:INSERT INTO Users (Username, Password, Expires, CreatedAt) VALUES (?, SHA1(?), NOW() + INTERVAL 1 YEAR, NOW())
Rushia 支援了插入資料若重複時可以更新該筆資料的指定欄位,這類似「覆蓋」,但這並不會先刪除原先的資料,這種方式僅會在插入時檢查是否重複,若重複則更新該筆資料。
lastInsertID := "ID"
db.Table("Users").OnDuplicate([]string{"UpdatedAt"}, lastInsertID).Insert(map[string]interface{}{
"Username": "YamiOdymel",
"Password": "test",
"UpdatedAt": db.Now(),
})
// 等效於:INSERT INTO Users (Username, Password, UpdatedAt) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE ID=LAST_INSERT_ID(ID), UpdatedAt = VALUES(UpdatedAt)
db.Table("Users").OnDuplicate([]string{"UpdatedAt"}).Insert(map[string]interface{}{
"Username": "YamiOdymel",
"Password": "test",
"UpdatedAt": db.Now(),
})
// 等效於:INSERT INTO Users (Username, Password, UpdatedAt) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE UpdatedAt = VALUES(UpdatedAt)
Rushia 允許你透過 InsertMulti
同時間插入多筆資料(單指令插入多筆資料),這省去了透過迴圈不斷執行單筆插入的困擾,這種方式亦大幅度提升了效能。
data := []map[string]interface{}{
{
"Username": "YamiOdymel",
"Password": "test",
}, {
"Username": "Karisu",
"Password": "12345",
},
}
db.Table("Users").InsertMulti(data)
// 等效於:INSERT INTO Users (Username, Password) VALUES (?, ?), (?, ?)
Limit
能夠限制 SQL 執行的筆數,如果是 10,那就表示只處理最前面 10 筆資料而非全部(例如:選擇、更新、移除)。
db.Table("Users").Limit(10).Update(data)
// 等效於:UPDATE Users SET ... LIMIT 10
db.Table("Users").Limit(10, 20).Select(data)
// 等效於:SELECT * from Users LIMIT 10, 20
透過 Offset
能夠以 筆數, 上次索引編號
的方式取得資料,例如:10, 20
則會從 21
開始取得 10 筆資料(21, 22, 23...
)。
db.Table("Users").Offset(10, 20).Select()
// 等效於:SELECT * from Users LIMIT 10 OFFSET 20
更新一筆資料在 Rushia 中極為簡單,你只需要指定表格名稱還有資料即可。
db.Table("Users").Where("Username", "YamiOdymel").Update(map[string]interface{}{
"Username": "Karisu",
"Password": "123456",
})
// 等效於:UPDATE Users SET Username = ?, Password = ? WHERE Username = ?
當你希望某些欄位在零值的時候不要進行更新,那麼你就可以使用 Patch
來做片段更新(也叫小修補)。
db.Table("Users").Where("Username", "YamiOdymel").Patch(map[string]interface{}{
"Age": 0,
"Username": "",
"Password": "123456",
})
// 等效於:UPDATE Users SET Password = ? WHERE Username = ?
最基本的資料取得在 Rushia 中透過 Select
使用。
db.Table("Users").Select()
// 等效於:SELECT * FROM Users
在 Select
中傳遞欄位名稱作為參數,多個欄位由逗點區分,亦能是函式。
db.Table("Users").Select("Username", "Nickname")
// 等效於:SELECT Username, Nickname FROM Users
db.Table("Users").Select("COUNT(*) AS Count")
// 等效於:SELECT COUNT(*) AS Count FROM Users
如果只想要取得單筆資料,那麼就可以用上 SelectOne
,這簡單來說就是 .Limit(1)
的縮寫。
db.Table("Users").SelectOne("Username")
// 等效於:SELECT Username FROM Users LIMIT 1
Rushia 已經提供了近乎日常中 80% 會用到的方式,但如果好死不死你想使用的功能在那 20% 之中,我們還提供了原生的方法能讓你直接輸入 SQL 指令執行自己想要的鳥東西。一個最基本的生指令(Raw Query)就像這樣。
其中亦能帶有預置聲明(Prepared Statement),也就是指令中的問號符號替代了原本的值。這能避免你的 SQL 指令遭受注入攻擊。
var us []User
db.Bind(&us).RawQuery("SELECT * FROM Users WHERE ID >= ?", 10)
如果你對 SQL 指令夠熟悉,你也可以使用更進階且複雜的用法。
db.RawQuery("SELECT ID, FirstName, LastName FROM Users WHERE ID = ? AND Username = ?", 1, "admin")
params := []int{10, 1, 10, 11, 2, 10}
query := (`
(SELECT A FROM t1 WHERE A = ? AND B = ?)
UNION ALL
(SELECT A FROM t2 WHERE A = ? AND B = ?)
UNION ALL
(SELECT A FROM t3 WHERE A = ? AND B = ?)
`)
db.RawQuery(query, params...)
透過 Rushia 宣告 WHERE
條件也能夠很輕鬆。一個最基本的 WHERE AND
像這樣使用。
db.Table("Users").Where("ID", 1).Where("Username", "admin").Select()
// 等效於:SELECT * FROM Users WHERE ID = ? AND Username = ?
HAVING
能夠與 WHERE
一同使用。
db.Table("Users").Where("ID", 1).Having("Username", "admin").Select()
// 等效於:SELECT * FROM Users WHERE ID = ? HAVING Username = ?
如果你想要在條件中宣告某個欄位是否等於某個欄位⋯你能夠像這樣。
// 別這樣。
db.Table("Users").Where("LastLogin", "CreatedAt").Select()
// 這樣才對。
db.Table("Users").Where("LastLogin = CreatedAt").Select()
// 等效於:SELECT * FROM Users WHERE LastLogin = CreatedAt
在 Where
或 Having
中,你可以自訂條件的運算子,如 >=、<=、<>⋯等。
db.Table("Users").Where("ID", ">=", 50).Select()
// 等效於:SELECT * FROM Users WHERE ID >= ?
條件也可以用來限制數值內容是否在某數之間(相反之,也能夠限制是否不在某範圍內)。
db.Table("Users").Where("ID", "BETWEEN", 0, 20).Select()
// 等效於:SELECT * FROM Users WHERE ID BETWEEN ? AND ?
條件能夠限制並確保取得的內容不在(或者在)指定清單內。
db.Table("Users").Where("ID", "IN", 1, 5, 27, -1, "d").Select()
// 等效於:SELECT * FROM Users WHERE ID IN (?, ?, ?, ?, ?)
list := []interface{}{1, 5, 27, -1, "d"}
db.Table("Users").Where("ID", "IN", list...).Select()
// 等效於:SELECT * FROM Users WHERE ID IN (?, ?, ?, ?, ?)
通常來說多個 Where
會產生 AND
條件,這意味著所有條件都必須符合,有些時候你只希望符合部分條件即可,就能夠用上 OrWhere
。
db.Table("Users").Where("FirstNamte", "John").OrWhere("FirstNamte", "Peter").Select()
// 等效於:SELECT * FROM Users WHERE FirstName = ? OR FirstName = ?
如果你的要求比較多,希望達到「A = B 或者 (A = C 或 A = D)」的話,你可以嘗試這樣。
db.Table("Users").Where("A = B").OrWhere("(A = C OR A = D)").Select()
// 等效於:SELECT * FROM Users WHERE A = B OR (A = C OR A = D)
確定某個欄位是否為空值。
// 別這樣。
db.Table("Users").Where("LastName", "NULL").Select()
// 這樣才對。
db.Table("Users").Where("LastName", "IS", nil).Select()
// 等效於:SELECT * FROM Users WHERE LastName IS NULL
Unix Timestamp 是一項將日期與時間秒數換算成數字的格式(範例:1498001308
),這令你能夠輕易地換算其秒數,但當你要判斷時間是否為某一年、月、日,甚至範圍的時候就會有些許困難,而 Rushia 也替你想到了這一點。
需要注意的是 Rushia 中的 Timestamp
工具無法串聯使用,這意味著當你想要確認時間戳是否為某年某月時,你需要有兩個 Where
條件,而不行使用 IsYear().IsMonth()
。更多的用法可以在原生文件中找到,這裡僅列出不完全的範例供大略參考。
判斷是否為特定年、月、日、星期或完整日期。
t := rushia.NewTimestamp()
db.Table("Users").Where("CreatedAt", t.IsDate("2017-07-13")).Select()
// 等效於:SELECT * FROM Users WHERE DATE(FROM_UNIXTIME(CreatedAt)) = ?
db.Table("Users").Where("CreatedAt", t.IsYear(2017)).Select()
// 等效於:SELECT * FROM Users WHERE YEAR(FROM_UNIXTIME(CreatedAt)) = ?
db.Table("Users").Where("CreatedAt", t.IsMonth(1)).Select()
db.Table("Users").Where("CreatedAt", t.IsMonth("January")).Select()
// 等效於:SELECT * FROM Users WHERE MONTH(FROM_UNIXTIME(CreatedAt)) = ?
db.Table("Users").Where("CreatedAt", t.IsDay(16)).Select()
// 等效於:SELECT * FROM Users WHERE DAY(FROM_UNIXTIME(CreatedAt)) = ?
db.Table("Users").Where("CreatedAt", t.IsWeekday(5)).Select()
db.Table("Users").Where("CreatedAt", t.IsWeekday("Friday")).Select()
// 等效於:SELECT * FROM Users WHERE WEEKDAY(FROM_UNIXTIME(CreatedAt)) = ?
確定是否為特定時間。
t := rushia.NewTimestamp()
db.Table("Users").Where("CreatedAt", t.IsHour(18)).Select()
// 等效於:SELECT * FROM Users WHERE HOUR(FROM_UNIXTIME(CreatedAt)) = ?
db.Table("Users").Where("CreatedAt", t.IsMinute(25)).Select()
// 等效於:SELECT * FROM Users WHERE MINUTE(FROM_UNIXTIME(CreatedAt)) = ?
db.Table("Users").Where("CreatedAt", t.IsSecond(16)).Select()
// 等效於:SELECT * FROM Users WHERE SECOND(FROM_UNIXTIME(CreatedAt)) = ?
db.Table("Users").Where("CreatedAt", t.IsWeekday(5)).Select()
// 等效於:SELECT * FROM Users WHERE WEEKDAY(FROM_UNIXTIME(CreatedAt)) = ?
你也能夠直接在條件中輸入指令。
db.Table("Users").Where("ID != CompanyID").Where("DATE(CreatedAt) = DATE(LastLogin)").Select()
// 等效於:SELECT * FROM Users WHERE ID != CompanyID AND DATE(CreatedAt) = DATE(LastLogin)
生條件中可以透過 ?
符號,並且在後面傳入自訂變數。
db.Table("Users").Where("(ID = ? OR ID = ?)", 6, 2).Where("Login", "Mike").Select()
// 等效於:SELECT * FROM Users WHERE (ID = ? OR ID = ?) AND Login = ?
刪除一筆資料再簡單不過了。
db.Table("Users").Where("ID", 1).Delete()
// 等效於:DELETE FROM Users WHERE ID = ?
Rushia 亦支援排序功能,如遞增或遞減,亦能擺放函式。
db.Table("Users").OrderBy("ID", "ASC").OrderBy("Login", "DESC").OrderBy("RAND()").Select()
// 等效於:SELECT * FROM Users ORDER BY ID ASC, Login DESC, RAND()
也能夠從值進行排序,只需要傳入一個切片即可。
db.Table("Users").OrderBy("UserGroup", "ASC", "SuperUser", "Admin", "Users").Select()
// 等效於:SELECT * FROM Users ORDER BY FIELD (UserGroup, ?, ?, ?) ASC
簡單的透過 GroupBy
就能夠將資料由指定欄位群組排序。
db.Table("Users").GroupBy("Name").Select()
// 等效於:SELECT * FROM Users GROUP BY Name
Rushia 支援多種表格加入方式,如:InnerJoin
、LeftJoin
、RightJoin
、NaturalJoin
、CrossJoin
。
db.
Table("Products").
LeftJoin("Users", "Products.TenantID = Users.TenantID").
Where("Users.ID", 6).
Select("Users.Name", "Products.ProductName")
// 等效於:SELECT Users.Name, Products.ProductName FROM Products AS Products LEFT JOIN Users AS Users ON (Products.TenantID = Users.TenantID) WHERE Users.ID = ?
你亦能透過 JoinWhere
或 JoinOrWhere
擴展表格加入的限制條件。
db.
Table("Products").
LeftJoin("Users", "Products.TenantID = Users.TenantID").
JoinOrWhere("Users", "Users.TenantID", 5).
Select("Users.Name", "Products.ProductName")
// 等效於:SELECT Users.Name, Products.ProductName FROM Products AS Products LEFT JOIN Users AS Users ON (Products.TenantID = Users.TenantID OR Users.TenantID = ?)
Rushia 支援複雜的子指令,欲要建立一個子指令請透過 SubQuery
函式。將其帶入到一個正常的建置函式中即可成為子指令。
subQuery := db.SubQuery().Table("Users").Select()
// 等效於:SELECT * FROM Users
你能夠輕易地將子指令放置在選擇/取得指令中。
subQuery := db.SubQuery().Table("Products").Where("Quantity", ">", 2).Select("UserID")
db.Table("Users").Where("ID", "IN", subQuery).Select()
// 等效於:SELECT * FROM Users WHERE ID IN (SELECT UserID FROM Products WHERE Quantity > ?)
插入新資料時也可以使用子指令。
subQuery := db.SubQuery().Table("Users").Where("ID", 6).Select("Name")
db.Table("Products").Insert(map[string]interface{}{
"ProductName": "測試商品",
"UserID": subQuery,
"LastUpdated": db.Now(),
})
// 等效於:INSERT INTO Products (ProductName, UserID, LastUpdated) VALUES (?, (SELECT Name FROM Users WHERE ID = 6), NOW())
就算是加入表格的時候也可以用上子指令,但你需要為子指令建立別名。
subQuery := db.SubQuery("Users").Table("Users").Where("Active", 1).Select()
db.
Table("Products").
LeftJoin(subQuery, "Products.UserID = U.ID").
Select("Users.Username", "Products.ProductName")
// 等效於:SELECT Users.Username, Products.ProductName FROM Products AS Products LEFT JOIN (SELECT * FROM Users WHERE Active = ?) AS Users ON Products.UserID = Users.ID
你同時也能夠透過子指令來確定某筆資料是否存在。
subQuery := db.SubQuery().Table("Users").Where("Company", "測試公司").Select("UserID")
db.Table("Products").Where(subQuery, "EXISTS").Select()
// 等效於:SELECT * FROM Products WHERE EXISTS (SELECT UserID FROM Users WHERE Company = ?)
Rushia 有提供一些輔助用的函式協助你除錯、紀錄,或者更加地得心應手。
如果你想取得這個指令總共能夠取得多少筆資料,透過 WithTotalCount
就能夠啟用總筆數查詢,這可能會稍微降低一點資料庫效能。
db.Table("Users").WithTotalCount().Select()
// 等效於:SELECT SQL_CALC_FOUND_ROWS * FROM Users
你能夠手動鎖定資料表格,避免同時間寫入相同資料而發生錯誤。
db.Table("Users").SetLockMethod("WRITE").Lock()
// 呼叫其他的 Lock() 函式也會自動將前一個上鎖解鎖,當然你也可以手動呼叫 Unlock() 解鎖。
db.Unlock()
// 等效於:UNLOCK TABLES
// 同時間要鎖上兩個表格也很簡單。
db.Table("Users", "Logs").SetLockMethod("READ").Lock()
// 等效於:LOCK TABLES Users READ, Logs READ
Rushia 也支援設置指令關鍵字。
db.Table("Users").SetQueryOption("LOW_PRIORITY").Insert(data)
// 等效於:INSERT LOW_PRIORITY INTO Users ...
db.Table("Users").SetQueryOption("FOR UPDATE").Select()
// 等效於:SELECT * FROM Users FOR UPDATE
db.Table("Users").SetQueryOption("SQL_NO_CACHE").Select()
// 等效於:SELECT SQL_NO_CACHE * FROM Users
你亦能同時設置多個關鍵字給同個指令。
db.Table("Users").SetQueryOption("LOW_PRIORITY", "IGNORE").Insert(data)
// Gives: INSERT LOW_PRIORITY IGNORE INTO Users ...
Rushia 除了基本的資料庫函式可供使用外,還能夠建立一個表格並且規劃其索引、外鍵、型態。
migration := rushia.NewMigration()
migration.Table("Users").Column("Username").Varchar(32).Primary().Create()
// 等效於:CREATE TABLE Users (Username VARCHAR(32) NOT NULL PRIMARY KEY) ENGINE=INNODB
數值 | 字串 | 二進制 | 檔案資料 | 時間 | 浮點數 | 固組 |
---|---|---|---|---|---|---|
TinyInt | Char | Binary | Blob | Date | Double | Enum |
SmallInt | Varchar | VarBinary | MediumBlob | DateTime | Decimal | Set |
MediumInt | TinyText | Bit | LongBlob | Time | Float | |
Int | Text | Timestamp | ||||
BigInt | MediumText | Year | ||||
LongText |
這裡是 Rushia 受啟發,或是和資料庫有所關聯的連結。