目錄
- 入門
- 設置
- 漂移文件
- 入門
- 變量
- 數組
- 定義表
- 支持的列類型
- 漂移特有的功能
- 導入
- 嵌套結果
- LIST子查詢
- Dart 互操作
- SQL 中的 Dart 組件
- 類型轉換器
- 現有的行類
- Dart 文檔注釋
- 結果類名稱
- 支持的語句
入門
Drift 提供了一個dart_api來定義表和編寫 SQL 查詢。尤其當您已經熟悉 SQL 時,直接在 SQL 中使用CREATE TABLE語句定義表可能會更方便。得益于 Drift 內置的強大 SQL 解析器和分析器,您仍然可以運行類型安全的 SQL 查詢,并支持自動更新流和所有其他 Drift 功能。SQL 的有效性在構建時進行檢查,Drift 會為每個表和 SQL 語句生成匹配的方法。
設置
添加漂移依賴項的基本設置與 dart_apis 的設置一致。具體描述請參閱設置頁面。
不同之處在于表和查詢的聲明方式。為了讓Drift識別SQL,需要將其放入.drift文件中。在此示例中,我們.drift在數據庫類旁邊使用了一個名為的文件tables.drift:
-- this is the tables.drift file
CREATE TABLE todos (id INT NOT NULL PRIMARY KEY AUTOINCREMENT,title TEXT,body TEXT,category INT REFERENCES categories (id)
);CREATE TABLE categories (id INT NOT NULL PRIMARY KEY AUTOINCREMENT,description TEXT
) AS Category; -- see the explanation on "AS Category" below/* after declaring your tables, you can put queries in here. Justwrite the name of the query, a colon (:) and the SQL: */
todosInCategory: SELECT * FROM todos WHERE category = ?;/* Here's a more complex query: It counts the amount of entries per
category, including those entries which aren't in any category at all. */
countEntries:SELECTc.description,(SELECT COUNT(*) FROM todos WHERE category = c.id) AS amountFROM categories cUNION ALLSELECT null, (SELECT COUNT(*) FROM todos WHERE category IS NULL);
Drift 會為您的表生成 Dart 類,這些類的名稱基于表名。默認情況下,Drift 會去除s表尾的空格。這在大多數情況下都適用,但在某些情況下(例如categories上表)則不行。我們希望生成一個 Category類(而不是Categorie),所以我們告訴 Drift 生成一個不同的名稱,并AS 在末尾添加聲明。
將漂移文件集成到數據庫很簡單,只需將其添加到 注釋include的參數中即可**@DriftDatabase**。tables這里可以省略該參數,因為沒有 Dart 定義的表需要添加到數據庫中。
import 'dart:io';import 'package:drift/drift.dart';
// These imports are used to open the database
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;part 'database.g.dart';(// relative import for the drift file. Drift also supports `package:`// importsinclude: {'tables.drift'},
)
class AppDb extends _$AppDb {AppDb() : super(_openConnection()); int get schemaVersion => 1;
}LazyDatabase _openConnection() {// the LazyDatabase util lets us find the right location for the file async.return LazyDatabase(() async {// put the database file, called db.sqlite here, into the documents folder// for your app.final dbFolder = await getApplicationDocumentsDirectory();final file = File(p.join(dbFolder.path, 'db.sqlite'));return NativeDatabase.createInBackground(file);});
}
要生成database.g.dart包含**_$AppDb** 超類的文件,請dart run build_runner build在命令行上運行。
漂移文件
Drift 文件是一項新功能,允許您使用 SQL 編寫所有數據庫代碼。但與您傳遞給簡單數據庫客戶端的原始 SQL 字符串不同,Drift
文件中的所有內容都經過 Drift 強大的 SQL 分析器驗證。這使您能夠更安全地編寫 SQL 查詢:Drift
會在構建過程中發現其中的錯誤,并為其生成類型安全的 dart_api,這樣您就無需手動讀取結果。
入門
要使用此功能,我們需要創建兩個文件:database.dart和tables.drift。 Dart 文件僅包含設置數據庫的最少代碼:
import 'package:drift/drift.dart';
import 'package:drift/native.dart';part 'database.g.dart';(include: {'tables.drift'},
)
class MyDb extends _$MyDb {// This example creates a simple in-memory database (without actual// persistence).// To store data, see the database setups from other "Getting started" guides.MyDb() : super(NativeDatabase.memory()); int get schemaVersion => 1;
}
我們現在可以在漂移文件中聲明表和查詢:
CREATE TABLE todos (id INT NOT NULL PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL,content TEXT NOT NULL,category INTEGER REFERENCES categories(id)
);CREATE TABLE categories (id INT NOT NULL PRIMARY KEY AUTOINCREMENT,description TEXT NOT NULL
) AS Category; -- the AS xyz after the table defines the data class name-- You can also create an index or triggers with drift files
CREATE INDEX categories_description ON categories(description);-- we can put named SQL queries in here as well:
createEntry: INSERT INTO todos (title, content) VALUES (:title, :content);
deleteById: DELETE FROM todos WHERE id = :id;
allTodos: SELECT * FROM todos;
使用 運行構建運行器后dart run build_runner build,drift 將寫入database.g.dart 包含_$MyDb超類的文件。讓我們看看我們得到了什么:
- 生成的數據類(Todo和Category)以及用于插入的配套版本(更多信息請參閱Dart Interop)。默認情況下,drift
會從類的表名中去掉尾部的“s”。這就是為什么我們AS Category在第二個表上使用 —— 否則它就會被 Categorie這樣調用。 - 運行查詢的方法:
- 一個Future createEntry(String title, String
content)方法。它使用提供的數據創建一個新的待辦事項條目,并返回所創建條目的 ID。 - Future deleteById(int id):根據 ID 刪除待辦事項條目,并返回受影響的行數。
- Selectable
allTodos()。它可用于獲取或查看所有待辦事項。它可以與allTodos().get()和 一起
使用allTodos().watch()。 - 不匹配表的選擇語句的類。在上面的例子中,該類AllTodosResult包含所有字段 todos以及相關類別的描述。
變量
在命名查詢中,您可以像在 SQL 中一樣使用變量。我們支持常規變量 ( ?)、顯式索引變量 ( ?123) 和冒號命名變量 ( :id)。我們不支持使用 @ 或 $ 聲明的變量。編譯器將嘗試通過查看變量的上下文來推斷其類型。這使得 Drift 能夠為您的查詢生成類型安全的 API,變量將作為參數寫入您的方法。
當變量類型不明確時,分析器可能無法解析該變量的類型。對于這些情況,你也可以指定變量的顯式類型:
myQuery(:variable AS TEXT): SELECT :variable;
除了基類型之外,還可以聲明類型可為空:
myNullableQuery(:variable AS TEXT OR NULL): SELECT :variable;
最后,你可以在 Dart 中使用命名參數時聲明一個變量是必需的。為此,請添加一個REQUIRED關鍵字:
myRequiredQuery(REQUIRED :variable AS TEXT OR NULL): SELECT :variable;
named_parameters 請注意,這僅在啟用構建選項時才有效。此外,默認情況下需要非空變量。
數組
如果要檢查某個值是否在值數組中,可以使用IN ?。這不是有效的 SQL,但 Drift 會在運行時對其進行語法糖解析。因此,對于以下查詢:
entriesWithId: SELECT * FROM todos WHERE id IN ?;
Drift 會生成一個Selectable entriesWithId(List ids)方法。運行后entriesWithId([1,2])會生成SELECT * … id IN (?1, ?2)并綁定相應的參數。為了確保其按預期工作,Drift 施加了兩個小限制:
- 沒有顯式變量:WHERE id IN ?2將在構建時被拒絕。由于變量已擴展,因此為其提供單個索引是無效的。
- 變量后沒有更高的顯式索引:運行 WHERE id IN ? OR title = ?2也會被拒絕。擴展變量可能會與顯式索引沖突,這就是
Drift 禁止這樣做的原因。當然,它id IN ? OR title = ?會按預期工作。
定義表
在.drift文件中,您可以使用CREATE TABLE語句定義表,就像在 SQL 中編寫一樣。
支持的列類型
就像 sqlite 本身一樣,我們使用此算法 根據聲明的類型名稱來確定列類型。
此外,類型名為BOOLEAN或DATETIME的 列,其 Dart 對應類型為bool或DateTime。布爾值存儲為INTEGER(0或1)。日期時間存儲為 unix 時間戳(INTEGER)或 ISO-8601 時間戳(TEXT),具體取決于可配置的構建選項。對于在 Dart 中應表示為 的整數BigInt(即,為了在編譯為 JS 時更好地兼容大數),請使用 類型定義列INT64。
ENUM()Dart 枚舉可以通過使用引用 Dart 枚舉類的類型自動按其索引進行存儲:
enum Status {none,running,stopped,paused
}import 'status.dart';CREATE TABLE tasks (id INTEGER NOT NULL PRIMARY KEY,status ENUM(Status)
);
有關存儲枚舉的更多信息,請參閱類型轉換器頁面。除了使用按索引映射枚舉的整數之外,您還可以按名稱存儲它們。為此,請使用ENUMNAME(…)而不是ENUM(…)。
有關所有支持類型的詳細信息,以及如何在日期時間模式之間切換的信息,請參閱本節。
表達式中還支持其他特定于漂移的類型(BOOLEAN、和) DATETIME, 這對于視圖很有幫助:ENUMENUMNAMECAST
CREATE VIEW with_next_status ASSELECT id, CAST(status + 1 AS ENUM(Status)) AS statusFROM tasksWHERE status < 3;
漂移特有的功能
為了支持 Drift 的 dart_api,CREATE TABLEDrift 文件中的語句可以使用 Dart 特有功能的特殊語法。當然,Drift 會CREATE TABLE 在運行語句之前刪除這些特殊語法。
- 您可以通過附加到語句來為表或定義的查詢定義自定義行類。WITH YourDartClassCREATE TABLE
- 或者,您可以使用AS DesiredRowClassName來更改由漂移生成的行類的名稱。
- 自定義行類和自定義表名也適用于視圖,例如 CREATE VIEW my_view AS DartName AS SELECT …;。
- 在列定義中,MAPPED BY可用于將轉換器應用于 該列。
- 類似地,JSON KEY可以使用約束來定義在將該表的一行序列化為 JSON 時將使用的鍵漂移。
- 最后,AS getterName可以用作列約束來覆蓋 Dart 中該列的生成名稱。當 SQL
中該列的名稱所啟發的默認列名與生成的表類的其他成員沖突時,此功能非常有用。
導入
您可以將導入語句放在文件頂部drift:
import 'tables.drift'; -- single quotes are required for imports
所有可從其他文件訪問的表也將在當前文件及其數據庫中可見includes。如果您想對另一個漂移文件中定義的表聲明查詢,則還需要導入該文件以使這些表可見。請注意,漂移文件中的導入始終具有傳遞性,因此在上面的示例中,您也將擁有所有在可用文件中聲明的導入。漂移文件other.drift沒有機制。export
您也可以將 Dart 文件導入到漂移文件中,這樣,所有通過 Dart 表聲明的表都可以在查詢中使用。我們支持相對導入和package:您熟悉的 Dart 導入。
嵌套結果
許多查詢通常使用 SELECT table.*以下語法來獲取某個表的所有列。當應用于來自連接的多個表時,這種方法可能會變得有點繁瑣,如下例所示:
CREATE TABLE coordinates (id INTEGER NOT NULL PRIMARY KEY,lat REAL NOT NULL,long REAL NOT NULL
);CREATE TABLE saved_routes (id INTEGER NOT NULL PRIMARY KEY,name TEXT NOT NULL,"from" INTEGER NOT NULL REFERENCES coordinates (id),"to" INTEGER NOT NULL REFERENCES coordinates (id)
);routesWithPoints: SELECT r.id, r.name, f.*, t.* FROM saved_routes rINNER JOIN coordinates f ON f.id = r."from"INNER JOIN coordinates t ON t.id = r."to";
為了匹配返回的列名,同時避免 Dart 中的名稱沖突,drift 會生成一個包含、 、id、name和 一個字段的類。當然,這根本沒用——這又是從 還是 來的? 讓我們重寫查詢,這次使用嵌套結果:id1latlonglat1long1lat1fromto
routesWithNestedPoints: SELECT r.id, r.name, f.** AS "from", t.** AS "to" FROM saved_routes rINNER JOIN coordinates f ON f.id = r."from"INNER JOIN coordinates t ON t.id = r."to";
如您所見,我們只需使用特定于漂移的 table.**語法即可嵌套結果。對于此查詢,漂移將生成以下類:
class RoutesWithNestedPointsResult {final int id;final String name;final Point from;final Point to;// ...
}
太棒了!這個類比之前的平面結果類更符合我們的意圖。
這些嵌套結果列 ( **) 只能出現在頂級 select 語句中,復合 select 語句或子查詢尚不支持它們。但是,它們可以引用 SQL 中已連接到 select 語句的任何結果集,包括子查詢和表值函數。
你可能想知道它的內部工作原理,因為它不是有效的 SQL。在構建時,drift 的生成器會將其轉換為引用表的所有列的列表。例如,如果我們有一個foo包含id INT 和bar TEXT列的表。那么,SELECT foo.** FROM foo可能會被解析為 SELECT foo.id AS “nested_0.id”, foo.bar AS “nested_0”.bar FROM foo。
LIST子查詢
從 Drift 版本開始1.4.0,子查詢也可以作為完整列表進行選擇。只需將子查詢放在LIST()函數中,即可將子查詢的所有行包含在結果集中。
重新使用嵌套結果示例中介紹的coordinates和表,我們添加一個存儲沿路線坐標的新表:saved_routes
CREATE TABLE route_points (route INTEGER NOT NULL REFERENCES saved_routes (id),point INTEGER NOT NULL REFERENCES coordinates (id),index_on_route INTEGER,PRIMARY KEY (route, point)
);
現在,假設我們想要查詢一條包含沿途所有點信息的路線。雖然這需要兩條 SQL 語句,但我們可以將其寫成一條漂移查詢,然后自動拆分成兩條語句:
routeWithPoints: SELECTroute.**,LIST(SELECT coordinates.* FROM route_pointsINNER JOIN coordinates ON id = pointWHERE route = route.idORDER BY index_on_route) AS pointsFROM saved_routes route;
這將生成一個結果集,其中包含一個SavedRoute route字段以及 List points沿途所有點的列表。
在內部,Drift 會將此查詢拆分為兩個單獨的查詢:- 外部SELECT route.** FROM saved_routes routeSQL 查詢 -SELECT coordinates.* FROM route_points … ORDER BY index_on_route為外部查詢中的每一行運行一個單獨的查詢。route.id內部查詢中的引用將被替換為一個變量,Drift 會將該變量綁定到外部查詢中的實際值。
雖然LIST()子查詢是一個非常強大的功能,但當外部查詢有很多行時(因為內部查詢針對每個外部行執行),它們的成本可能很高。
Dart 互操作
Drift 文件與 Drift 現有的 dart_api 完美協同工作:
- 您可以為漂移文件中聲明的表編寫 Dart 查詢:
Future<void> insert(TodosCompanion companion) async {await into(todos).insert(companion);
}
- 通過將 Dart 文件導入到漂移文件中,您可以為 Dart 中聲明的表編寫 SQL 查詢。
- 生成的查詢方法可用于事務,它們與自動更新查詢等一起工作。
如果您在生成的 Dart 類中使用fromJson和toJson方法,并且需要更改 json 中列的名稱,則可以使用JSON KEY列約束來執行此操作,因此id INT NOT NULL JSON KEY userId 會在 json 中生成序列化為“userId”的列。
SQL 中的 Dart 組件
你可以使用“Dart 模板”來充分利用 SQL 和 Dart 語言的優勢。Dart 模板是一種 Dart 表達式,可以在運行時內聯到查詢語句中。要使用它們,請在查詢語句中聲明一個 $ 變量:
filterTodos: SELECT * FROM todos WHERE $predicate;
Drift 將生成一個Selectable帶有predicate參數的方法,可用于在運行時構建動態過濾器:
Stream<List<Todo>> watchInCategory(int category) {return filterTodos((todos) => todos.category.equals(category)).watch();
}
這讓你可以編寫單個 SQL 查詢并在運行時動態應用謂詞!此功能適用于
- 表達式,正如您在上面的示例中看到的那樣
- 單一排序項:SELECT * FROM todos ORDER BY $term, id ASC
將生成一個采用的方法OrderingTerm。 - 整個 order-by 子句:SELECT * FROM todos ORDER BY $order
- 限制條款:SELECT * FROM todos LIMIT $limit
- 插入語句的插入項:INSERT INTO todos $row生成一個Insertable row 參數
當用作表達式時,您還可以在查詢中提供默認值:
getTodos ($predicate = TRUE): SELECT * FROM todos WHERE $predicate;
這將使該參數在 Dart 中成為可選參數。如果未明確設置,predicate它將使用默認的 SQL 值(此處為)。TRUE
類型轉換器
您可以在漂移文件中導入并使用用 Dart 編寫的類型轉換器 。導入 Dart 文件需要使用常規import語句。要在列定義上應用類型轉換器,可以使用MAPPED BY列約束:
CREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,name TEXT,preferences TEXT MAPPED BY `const PreferenceConverter()`
);
引用帶有類型轉換器的表列的查詢或視圖也將繼承該轉換器。此外,查詢和視圖都可以為特定列指定類型轉換器:
CREATE VIEW my_view AS SELECT 'foo' MAPPED BY `const PreferenceConverter()`SELECTid,json_extract(preferences, '$.settings') MAPPED BY `const PreferenceConverter()`
FROM users;
使用類型轉換器時,我們推薦使用apply_converters_on_variables build 選項。這也會將轉換器從 Dart 應用于 SQL,例如,如果用于變量:SELECT * FROM users WHERE preferences = ?。使用該選項,變量將被推斷為 ,Preferences而不是String。
Drift 文件還對隱式枚舉轉換器有特殊支持:
import 'status.dart';CREATE TABLE tasks (id INTEGER NOT NULL PRIMARY KEY,status ENUM(Status)
);
當然,關于自動枚舉轉換器的警告也適用于漂移文件。
現有的行類
您可以使用自定義行類,而不必讓 Drift 為您生成一個。例如,假設您有一個 Dart 類定義為
class User {final int id;final String name;User(this.id, this.name);
}
然后,您可以指示漂移將該類用作行類,如下所示:
import 'row_class.dart'; --import for where the row class is definedCREATE TABLE users (id INTEGER NOT NULL PRIMARY KEY,name TEXT NOT NULL
) WITH User; -- This tells drift to use the existing Dart class
當使用在其他 Dart 文件中定義的自定義行類時,您還需要將該文件導入到定義數據庫的文件中。有關此功能的更多常規信息,請查看此頁面。
自定義行類可應用于文件SELECT中定義的查詢.drift。要使用自定義行類,WITH可在查詢名稱后添加語法。
例如,假設我們row_class.dart通過添加另一個類來擴展現有的 Dart 代碼:
class UserWithFriends {final User user;final List<User> friends;UserWithFriends(this.user, {this.friends = const []});
}
現在,我們可以使用新類為其行添加相應的查詢:
-- table to demonstrate a more complex select query below.
-- also, remember to add the import for `UserWithFriends` to your drift file.
CREATE TABLE friends (user_a INTEGER NOT NULL REFERENCES users(id),user_b INTEGER NOT NULL REFERENCES users(id),PRIMARY KEY (user_a, user_b)
);allFriendsOf WITH UserWithFriends: SELECT users.** AS user, LIST(SELECT * FROM users a INNER JOIN friends ON user_a = a.id WHERE user_b = users.id OR user_a = users.id
) AS friends FROM users WHERE id = :id;
該WITH UserWithFriends語法將使 Drift 考慮UserWithFriends類。對于構造函數中的每個字段,Drift 都會檢查查詢中的列,并驗證其是否具有兼容類型。然后,Drift 會在內部生成查詢代碼,將行映射到該類的實例 UserWithFriends。
有關使用自定義行類進行查詢的更完整概述,請參閱 查詢部分。
Dart 文檔注釋
漂移文件中列前添加的注釋將作為 Dart 文檔注釋添加到生成的行類中:
CREATE TABLE friends (-- The user original sending the friendship requestuser_a INTEGER NOT NULL REFERENCES users(id),-- The user accepting the friendship request from [userA].user_b INTEGER NOT NULL REFERENCES users(id),PRIMARY KEY (user_a, user_b)
);
漂移生成的類中生成的userA和字段將會有這些注釋作為文檔注釋。userBFriend
結果類名稱
對于大多數查詢,漂移會生成一個新的類來保存結果。該類以查詢名稱加上后綴命名Result,例如,一個myQuery查詢會得到一個MyQueryResult類。
您可以像這樣更改結果類的名稱:
routesWithNestedPoints AS FullRoute: SELECT r.id, -- ...
這樣,多個查詢也可以共享一個結果類。只要它們具有相同的結果集,您就可以為它們分配相同的自定義名稱,并且漂移只會生成一個類。
對于僅選擇表中所有列的查詢,drift 不會生成新的類,而是會重用它生成的數據類。同樣,對于只有一列的查詢,drift 會直接返回該列,而不是將其包裝在結果類中。目前無法覆蓋此行為,因此,如果查詢包含匹配的表或只有一列,則您無法自定義該查詢的結果類名稱。
支持的語句
目前,.drift文件中可以出現以下語句。
- import ‘other.drift’:將另一個文件中聲明的所有表和查詢導入到當前文件中。
- DDL 語句:您可以將CREATE TABLE、、CREATE VIEW和CREATE INDEX語句CREATE
TRIGGER放入漂移文件中。 - 查詢語句:我們支持INSERT、、SELECT和UPDATE語句DELETE。
所有導入都必須位于 DDL 語句之前,并且這些語句必須位于命名查詢之前。
如果您需要另一個語句的支持,或者如果漂移拒絕您認為有效的查詢,請創建一個問題!