数据库关系型数据库嵌入式数据库【免费下载链接】lovefieldLovefield is a relational database for web apps. Written in JavaScript, works cross-browser. Provides SQL-like APIs that are fast, safe, and easy to use.项目地址https://gitcode.com/gh_mirrors/lo/lovefield点击查看免费下载Lovefield 是一个用纯 JavaScript 编写、运行在浏览器中的关系型数据库为 Web 应用提供 SQL-like 的查询 API。本文以仓库根目录 README.md 为核心骨架完整呈现其快速上手流程、Schema 定义、查询构建器、底层查询引擎与存储后端设计并结合仓库源码与规范文档深入讲解每一个 API 的用法、参数与原理。读完后你将能够独立完成 Lovefield 的安装、Schema 建模、增删改查、参数化查询与数据观察等完整实战开发并理解其内部查询计划与持久化机制。Lovefield 是什么为 Web 应用设计的纯 JavaScript 关系数据库Lovefield 的定位在 README.md 中描述得非常清楚用纯 JavaScript 编写的关系数据库relational database written in pure JavaScript提供 SQL-like 语法并且跨浏览器工作目前支持 Chrome 37、Firefox 31、IE 11、Edge 和 Safari 10。与同为浏览器端数据方案的 IndexedDB、WebSQL、Firebase 相比Lovefield 的核心差异在于内置关系查询引擎官方 FAQ 明确说明Lovefield 像 SQLite、MySQL 一样拥有关系查询引擎但它不接收原始 SQL 字符串而是通过 builder pattern构建器模式API 来构建查询。IndexedDB 与 Firebase 本身不提供关系查询能力而 WebSQL 已被视为过时且不能跨浏览器工作这正是 Lovefield 存在的价值。根据 docs/spec/00_intro.md 的设计假设Lovefield 面向数据量小于 2GB、但已大到需要结构化查询引擎的场景提供 SQL-03 标准的功能子集并使用现有的存储技术如 IndexedDB来持久化数据。文档还特别提醒开发者从 Lovefield 查询引擎取出的数据应被视为不安全数据在回传服务器前需要做消毒处理。官方 FAQ 提到截至 2016 年 5 月Inbox by Gmail 重度依赖 Lovefield 执行复杂的客户端结构化数据查询Google Play Movies TV 也已随附 Lovefield 近两年——这些都属于仓库官方文档中记载的项目事实。从仓库结构看Lovefield 的能力层次一目了然lib/schema/Schema 构建Builder、TableBuilder、约束与索引lib/query/SELECT/INSERT/UPDATE/DELETE 查询构建器与上下文lib/proc/查询引擎、逻辑/物理计划、各类优化 pass 与执行步骤lib/backstore/IndexedDB、WebSQL、Firebase、内存等存储后端lib/index/自研 B Tree 等索引实现。五分钟快速上手从零搭建第一个 Lovefield 应用README.md 将 demos/todo/README.md 作为官方 Quick Start 入口。下面这段代码就是一个完整的、可运行的 Lovefield 最小示例实现了建库、建表、插入、查询的全流程!doctype html html head meta charsetutf-8 / titleMinimal example of using Lovefield/title script srcbower_components/lovefield/dist/lovefield.min.js/script /head body script var schemaBuilder lf.schema.create(todo, 1); schemaBuilder.createTable(Item). addColumn(id, lf.Type.INTEGER). addColumn(description, lf.Type.STRING). addColumn(deadline, lf.Type.DATE_TIME). addColumn(done, lf.Type.BOOLEAN). addPrimaryKey([id]). addIndex(idxDeadline, [deadline], false, lf.Order.DESC); var todoDb; var item; schemaBuilder.connect().then(function(db) { todoDb db; item db.getSchema().table(Item); var row item.createRow({ id: 1, description: Get a cup of coffee, deadline: new Date(), done: false }); return db.insertOrReplace().into(item).values([row]).exec(); }).then(function() { return todoDb.select().from(item).where(item.done.eq(false)).exec(); }).then(function(results) { results.forEach(function(row) { console.log(row[description], before, row[deadline]); }); }); /script /body /html本地运行步骤该 todo 示例应用位于仓库 demos/todo/ 目录。要在本地运行它按 demos/todo/README.md 的指引执行以下步骤安装 gulp如果尚未安装npm install -g gulp安装 bower如果尚未安装npm install -g bower拉取 package.json 中的依赖npm install .拉取 bower.json 中的依赖bower install启动本地 Web 服务器gulp debug浏览器访问http://localhost:8000/todo.html其中gulp debug任务定义在 demos/todo/gulpfile.js默认在 8000 端口启动一个带 livereload 的静态资源服务器也支持通过--port参数自定义端口。通过 NPM 或 Bower 获取 Lovefield除了直接从仓库的 dist 目录下载外Lovefield 还支持 npm 与 bower 两种包管理方式npm info lovefield bower info lovefield将 Lovefield 声明为依赖后执行npm update或bower update即可自动拉取对应发行版本。当前仓库 package.json 中的版本为 2.1.12主入口为dist/lovefield.min.js采用 Apache-2.0 许可证bower.json同样将dist/lovefield.min.js作为 main 字段。代码逐段解读创建 Schema 构建器lf.schema.create(todo, 1)相当于 SQL 的CREATE DATABASE IF NOT EXISTS todo。注意 DDL 并不会立即执行Lovefield 采用 builder pattern 先把 Schema 构建好之后再完成数据库的打开/创建。定义表结构createTable(Item)依次声明了 4 个列id、description、deadline、done、主键id和索引deadline 上的降序索引idxDeadline。addIndex(idxDeadline, [deadline], false, lf.Order.DESC)的第三个参数false表示该索引不唯一。连接数据库schemaBuilder.connect()返回 Promise一旦连接成功Schema 便不可再修改。Lovefield 的connect()以及所有查询 API 都是基于 Promise 的异步 API这是为了避免长时间、高 CPU/I/O 消耗的查询阻塞主线程。创建行对象Lovefield 不直接接受普通对象作为行必须通过item.createRow({...})来创建。写入数据db.insertOrReplace().into(item).values([row]).exec()相当于INSERT OR REPLACE INTO Item VALUES rowexec()返回 Promise且所有写入都在隐式事务中保证提交。查询数据todoDb.select().from(item).where(item.done.eq(false)).exec()相当于SELECT * FROM Item WHERE Item.done false结果以行数组形式返回。读取列值直接用列名对行对象解引用如row[description]。Schema 定义数据库的骨架Lovefield 的核心概念是先定义数据库的schema结构再基于实现该 schema 的实例instance进行操作。完整规范见 docs/spec/01_schema.md。一个 schema 包含名称与版本号。程序的来源origin、数据库名称与版本号三者共同在持久化存储上唯一标识一个数据库实例。版本号必须是大于 0 的整数Lovefield 用它判断数据库是否需要升级开发者应当借助 docs/spec/03_life_of_db.md 描述的数据库升级机制做数据迁移。命名规则Lovefield 中所有命名都必须通过以下 JavaScript 正则检查否则抛出异常/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)构建器链式 APIlf.schema.create()返回 lib/schema/builder.js 中的lf.schema.Builder实例它提供两个核心方法createTable()在 schema 构建器内部实例化一个表构建器最终构造出表connect()收尾 schema 构建并在数据存储上连接数据库实例。lf.schema.Builder是有状态对象分为构建中与已收尾两种状态只有构建中的 schema 可以被修改一旦收尾便不再接受任何调用。createTable()返回的lf.schema.TableBuilder实现见 lib/schema/table_builder.js用于构建表级 schema其所有成员函数都返回构建器自身以支持链式调用。列与数据类型表至少包含一个列通过addColumn()添加。列名在表内必须唯一且每个列必须关联一个数据类型。Lovefield 支持的数据类型定义在 lib/enums.js 的lf.Type枚举中类型默认值默认可空说明lf.Type.ARRAY_BUFFERnull是JavaScriptArrayBuffer对象lf.Type.BOOLEANfalse否JavaScriptbooleanlf.Type.DATE_TIMEDate(0)否JavaScript Date内部会转换为时间戳整数lf.Type.INTEGER0否32 位整数lf.Type.NUMBER0否JavaScriptnumberlf.Type.STRING否JavaScriptstringlf.Type.OBJECTnull是JavaScriptObject原样存储与典型 SQL 引擎不同Lovefield 中除ARRAY_BUFFER与OBJECT外的列默认非空需要显式调用addNullable()才可空可空列的默认值始终为null。此外ARRAY_BUFFER与OBJECT类型列不可作为索引或约束的一部分ARRAY_BUFFER列不可参与WHERE条件OBJECT列仅可用于isNull/isNotNull谓词当BOOLEAN、DATE_TIME、INTEGER被用作索引/主键或唯一约束时Lovefield 会做隐式转换分别转为STRING、NUMBER、NUMBER。约束Lovefield 支持主键、外键、唯一、可空/非空四种约束主键每表仅一个通过addPrimaryKey()添加。与 SQL 一致主键蕴含唯一且非空。Lovefield 还支持自增主键它必须是整数列、默认升序值由 Lovefield 从 1 开始自动分配。外键通过addForeignKey()添加。主键/外键违反会导致事务被拒绝当外键设置了 cascade级联时查询引擎会执行级联删除与更新。唯一约束通过addUnique()添加会隐式建立索引跨列唯一约束要求这些列的取值组合唯一。可空通过addNullable()显式声明。索引Lovefield不依赖底层数据存储的索引而是实现了自己的索引见 lib/index/默认索引结构是 B Treelib/index/btree.js。索引通过addIndex()添加支持单列与跨列。与多数 SQL 引擎不同Lovefield 要求被索引列的值不允许为 null由于唯一约束会构建隐式索引因此创建作用域完全相同的索引会抛出异常。默认情况下Lovefield 在加载时于内存中构建表索引不将索引持久化到数据存储只有对表调用persistentIndex()后索引才会被持久化。Lovefield 不支持自定义索引即基于变换创建索引例如对邮箱字段做文本反转再建索引。原因正如规范所述受 Chrome Apps v2 限制无法持久化 JavaScript 函数后 eval正确的做法是开发者自己完成变换并存储变换后的数据。查询 APISQL-like 查询构建器查询 API 的完整规范见 docs/spec/04_query.md。一个查询的生命周期分为四步创建查询构建器创建查询上下文、绑定可选向参数化查询注入值、执行在事务上下文中执行、收尾结果提交或回滚。所有查询都在隐式或显式事务上下文中执行。SELECT 查询SELECT 是最常用的查询通过db.select()创建接口定义见 lib/query.jsds.connect().then(function(db) { var selectQueryBuilder db.select(); });SELECT 构建器接受数据源、搜索条件、限流limit、排序sorter与分组group条件。除orderBy()外所有函数只能调用一次否则抛出异常——orderBy()可以多次调用以实现多键排序db.select(). from(infoCard). from(infoCardDetails); // 异常from() 只能调用一次一个重要的使用原则把 SELECT 的返回结果视为只读。基于性能考虑Lovefield 不会主动克隆/冻结其内部缓存返回的对象因此直接修改结果会造成缓存与数据库不同步db.select().from(orders).exec().then(function(rows) { myCell.text rows[0].amount.toString(); // 正确只读使用 rows[0].amount 22; // 错误会导致缓存与 DB 失步 });投影与连接select()的参数决定投影列不传参表示SELECT *。单表上下文中返回的行直接以列名取字段多表上下文中返回行的每列会以表名做前缀var infoCard db.getSchema().table(InfoCard); var asset db.getSchema().table(Asset); var q3 db.select(infoCard.id, infoCard.itag, asset.timestamp). from(infoCard). innerJoin(asset, asset.id.eq(infoCard.id)). where(asset.id.eq(1)); q3.exec().then(function(rows) { // 双表上下文列带前缀 console.log( rows[0][InfoCard][id], rows[0][InfoCard][tag], rows[0][Asset][timestamp]); });连接方式上from()可接收一个或多个表多表即隐式内连接Lovefield 支持内连接inner join与左外连接left outer join分别用innerJoin()与leftOuterJoin()显式声明。四类连接的 SQL 对照如下场景SQLLovefield显式内连接SELECT * FROM photo INNER JOIN album ON photo.albumId album.id WHERE album.id 1db.select().from(p).innerJoin(a, p.albumId.eq(a.id)).where(a.id.eq(1)).exec();隐式内连接SELECT * FROM photo p, album a WHERE p.albumId a.id AND a.id 1db.select().from(p, a).where(lf.op.and(p.albumId.eq(a.id), a.id.eq(1))).exec();隐式自连接SELECT * FROM job j1, job j2 WHERE j1.minSalary j2.maxSalary用表别名table(Job).as(j1)后from(j1, j2).where(j1.minSalary.eq(j2.maxSalary))左外连接SELECT p.id, a.id, a.name FROM photo p LEFT OUTER JOIN album a ON p.albumId a.iddb.select(p.id, a.id, a.name).from(p).leftOuterJoin(a, p.albumId.eq(a.id)).exec();搜索条件谓词搜索条件由**谓词predicate**编排。Lovefield 的列对象同时实现了列接口与lf.PredicateProvider接口定义见 lib/predicate.js因此可以直接在列上构造谓词如infoCard.id.eq(1234)。谓词左侧操作数天然是该列的值。函数参数个数SQL 等价eq1标量或列neq1标量或列lt1标量或列lte1标量或列gt1标量或列gte1标量或列match1正则表达式SIMILARbetween2仅标量BETWEENin1标量数组INisNull0IS NULLisNotNull0IS NOT NULL特别地eq(null)与isNull()等价neq(null)与isNotNull()等价——这是为了让参数绑定更简单用户无需为 NULL 情况维护两套查询。match接收的是 JavaScript 正则表达式而非 SQL SIMILAR 的正则。复杂条件需要用lf.op命名空间下的组合函数实现见 lib/op.js函数参数个数SQL 等价lf.op.and可变ANDlf.op.or可变ORlf.op.not1NOT分页与排序Lovefield不支持游标cursor因此分页只能借助limit()与skip()db.select(). from(infoCard). limit(100). skip(100). orderBy(infoCard.lang, lf.Order.DESC). exec();与 SQL 的LIMIT/SKIP一样若多次 SELECT 不在同一事务内期间发生插入/删除时不保证两次分页结果不重叠或不错位。orderBy()默认升序lf.Order.ASC枚举定义见 lib/enums.js。Group By 与聚合函数聚合函数定义在lf.fn命名空间lib/fn.js可配合groupBy()使用函数SQL 等价参数合法类型lf.fn.avgAVGnumber,integerlf.fn.countCOUNT任意类型lf.fn.distinctDISTINCT任意类型lf.fn.geomean无number,integerlf.fn.maxMAXnumber,integer,string,datetimelf.fn.minMINnumber,integer,string,datetimelf.fn.stddevSTDDEVnumber,integerlf.fn.sumSUMnumber,integer聚合函数用于非法类型列会抛出SyntaxError多列ROLLUP与CUBE暂不支持。与 SQL 一致where()中不支持聚合函数且 Lovefield不支持HAVING需要用户用两次查询或在结果中过滤db.select(customer.name, lf.fn.count(order.id)). from(order, customer). where(order.customerId.eq(customer.id)). groupBy(customer.name).exec();聚合结果的列名以函数名命名例如lf.fn.count(order.id)的结果列名为COUNT(id)。注意groupBy()只允许可索引数据类型的列投影列表中未被分组的非聚合列会由 Lovefield 从组内任选一行填充。列别名与表别名每个被选列可以通过as()起别名所有带别名的列在返回结果中被展平不再带表名前缀var q3 db.select( infoCard.id.as(InfoCard Id), infoCard.itag, asset.timestamp.as(Timestamp)). from(infoCard). innerJoin(asset, asset.id.eq(infoCard.id)). where(asset.id.eq(1)); q3.exec().then(function(rows) { console.log( rows[0][InfoCard Id], // 别名列被展平 rows[0][InfoCard][tag], // 未别名列仍带前缀 rows[0][Timestamp]); });表别名通过db.getSchema().table(Job).as(j1)设置单表查询时表别名无效自连接必须使用表别名。查询结果的检索与其他 SQL 引擎不同Lovefield默认不对内连接结果做展平而是返回嵌套对象带前缀。原因是展平会带来性能开销展平 N 个对象到单一对象需要对结果集的每一行执行一次合并N 越大开销越显著。用户需要展平时可以用as()别名内部即通过类似的 merge 函数实现function(objectsToMerge) { var mergedObj {}; objectsToMerge.forEach(function(obj) { Object.keys(obj).forEach(function(key) { mergedObj[key] obj[key]; }); }); return mergedObj; }INSERT 查询Lovefield 提供两种插入构建器db.insert()只允许插入主键不冲突的新行与db.insertOrReplace()覆盖已存在的行。两者接口见 lib/query.js 的lf.query.Insert。插入前必须用table.createRow()创建行所有插入查询都假定一次插入多行因此即使只有一行也必须包裹在数组中。插入/替换成功的行会作为回调参数返回这在自增主键场景下尤为有用——可以得知自动分配的主键值var infoCard db.getSchema().table(InfoCard); var row infoCard.createRow({ id: something, lang: 140 }); db.insertOrReplace().into(infoCard).values([row]).exec().then( function(rows) { console.log(rows[0][id]); // something });UPDATE 查询更新构建器由db.update(table)创建接口见 lib/query.js更新值通过set()子句提供where()与 SELECT 共用搜索条件。除set()可多次调用外其余函数只能调用一次// UPDATE order SET amount 51, currency EUR // WHERE currency DEM AND amount 100; db.update(order). set(order.amount, 51). set(order.currency, EUR). where(lf.op.and( order.currency.eq(DEM), order.amount.eq(100))). exec(); // 返回 PromiseDELETE 查询删除构建器由db.delete()创建接口见 lib/query.js可带或不带搜索条件// DELETE FROM infoCard WHERE lang es; db.delete().from(infoCard).where(infoCard.lang.eq(es)).exec(); db.delete().from(infoCard).exec(); // 清空 infoCard参数化查询Lovefield 通过lf.bind(index)实现见 lib/bind.js在查询上下文中创建占位符随后用builder.bind(values)注入值。索引从 0 开始bind()不关心数组是否比实际需要更大var p db.getSchema().table(Photo); // SELECT FROM Photo WHERE id ?0; var q1 db.select().from(p).where(p.id.eq(lf.bind(0))); q1.bind([id1]).exec(); // 查找 id 为 id1 q1.bind([id2]).exec(); // 查找 id 为 id2 // UPDATE Photo SET timestamp ?1, local ?2 WHERE id ?0; var q2 db. update(p). set(p.timestamp, lf.bind(1)). set(p.local, lf.bind(2)). where(p.id.eq(lf.bind(0))); q2.bind([id3, 345, false]).exec(); // 无需重建查询即可更新参数LIMIT 与 SKIP 也可以参数化实现分页复用var e db.getSchema().table(Employee); var q3 db.select(). from(e). orderBy(e.lastName). limit(lf.bind(0)). skip(lf.bind(1)); q3.bind([100, 0]).exec(); // 第 1 页100 名员工 q3.bind([100, 1]).exec(); // 第 2 页各查询类型可参数化的子句如下查询类型可参数化子句SELECTwhere()、limit()、skip()INSERTvalues()UPDATEwhere()、set()DELETEwhere()注意出于性能考虑bind()不做类型检查用户必须自行保证绑定值的类型正确。INSERT 中绑定values()时无论绑定整个数组还是逐行绑定绑定的值都必须是createRow()创建的行对象。Observers数据观察Lovefield 支持对 SELECT 查询的数据观察通过db.observe(query, handler)注册、db.unobserve(query, handler)注销var p db.getSchema().table(Photo); var query db.select().from(p).where(p.id.eq(1)); var handler function(changes) { // 每次数据变化时被调用直到 db.unobserve 被调用 }; db.observe(query, handler); // 下面这条更新会触发对上述 SELECT 的观察回调 // 内部会在作用域重叠时重新执行查询复杂 SELECT 需注意性能 db.update(p).set(p.title, New Title).where(p.id.eq(1)).exec(); db.unobserve(query, handler); // 记得释放观察者避免泄漏值得注意的边界情况当被观察查询涉及OBJECT或ARRAY_BUFFER列时只有这些对象的引用发生变化假设其他列未变才会触发观察者原地修改对象内容不会触发因为 Lovefield 无法获知这类对象的内部结构来做前后比较且这样做会伤害性能。将参数化查询与 Observers 结合可以优雅地实现 MVC 场景下的数据联动刷新——例如两个文本框的值作为between谓词的上下界文本框变化时仅更新绑定值并重新exec()而 UI 已预先绑定查询结果无需手工刷新视图。底层原理查询引擎与存储后端Lovefield 在浏览器内执行查询架构细节记录在 docs/dd/04_query_engine.md 设计文档中。整个引擎与 SQLite、MySQL 的思路类似一条查询会先被解析为逻辑查询计划再经过逻辑计划重写器logical_plan_rewriter.js的一系列优化 pass如隐式连接、谓词下推、and 谓词合并、索引范围扫描、order by 走索引、limit/skip 下推等见 lib/proc/ 目录下的各类*_pass.js生成物理查询计划physical_plan_factory.js、physical_plan_rewriter.js最后按计划执行各步骤table_access_full_step.js、index_range_scan_step.js、join_step.js、project_step.js等。区别在于 Lovefield 不解析 SQL 字符串而是直接消费构建器产出的查询上下文。与执行机制配套的是存储后端抽象lib/backstore/Lovefield 默认使用浏览器原生存储来持久化数据支持 IndexedDB、WebSQL、Firebase 以及纯内存存储等表级访问通过object_store.js等统一抽象完成索引由库自身在内存中维护lib/index/这就是它跨浏览器、自带关系查询能力的根基。另一个架构性事实是DDL数据定义语言API 全部同步DML数据操作语言API 全部异步。所有异步 API 基于 Promise——Lovefield 使用goog.Promise实现跨浏览器 Promise 支持可对不支持 Promise 的浏览器如 IE10做 polyfill这也是 docs/spec/00_intro.md 中 Closure-compiler compliant、Chrome Apps v2 compliant、可复制部署、无副作用 等设计需求的一部分。从 lib/transaction.js 与 lib/proc/transaction.js 的源码结构看查询执行天然处于隐式或显式事务上下文中事务支持回滚与约束校验。项目配套设施规范、设计文档、演示与工具README 提供了完整的文档与示例入口均在仓库内部可逐一深入规范文档从 docs/spec_index.md 进入覆盖 Schema 定义docs/spec/01_schema.md、数据存储docs/spec/02_data_store.md、数据库生命周期docs/spec/03_life_of_db.md、查询docs/spec/04_query.md、事务docs/spec/05_transaction.md、库设施docs/spec/06_library.md、SPACdocs/spec/07_spac.md、引用完整性docs/spec/08_referential_integrity.md等主题。设计文档从 docs/dd_index.md 进入包括 Schemadocs/dd/01_schema.md、数据存储docs/dd/02_data_store.md、数据库生命周期docs/dd/03_life_of_db.md、查询引擎docs/dd/04_query_engine.md、事务docs/dd/05_transaction.md与索引docs/dd/06_indices.md。FAQdocs/FAQ.md 回答了生产可用性、与各框架Angular/Bootstrap/Cordova/D3 等的互操作性、性能验证方式、与 IndexedDB/WebSQL/Firebase 的关系、Safari 私有浏览模式下的存储限制建议改用内存存储等常见问题。演示项目demos/todo/快速上手、demos/moviedb/电影数据库含 jQuery/binding/pureidb 三种变体、demos/scrum/Scrum 面板含 Firebase 与 IndexedDB 数据模型变体、demos/olympia_db/Angular/Polymer 版本、demos/todo_typescript/TypeScript 版本等展示了与主流框架的集成方式。SPAC 代码生成器面向 Closure Compiler 高级优化的进阶流程——先在 YAML 文件中描述 Schema用 SPACSchema Parser And Code-generator实现见 spac/解析并生成 JavaScript 源码再与核心库一起编译打包对应 YAML 示例与测试数据位于 spac/testdata/。开发环境搭建与测试开发者环境要求与流程记录在 docs/dev_setup.md需要可用的 Java、git 与 Node.js版本要求见 package.json引擎要求 node 12.16.3并可从 PATH 搜索到。安装依赖Lovefield 使用 npm 管理依赖执行npm update即可。构建Lovefield 用 Closure JavaScript Compiler 做校验/压缩/混淆由 npm 自动拉取用 gulp 作为构建管理器运行gulp查看支持的命令。测试使用 Selenium WebDriver 跑自动化测试两种方式本地测试适合调试与快速反馈手动安装 WebDriver 后执行gulp test --targettests --browserbrowserSauce Labs 云测注册 Sauce Labs 账号并运行 Sauce Connect 后依次设置SAUCE_USERNAME、SAUCE_ACCESS_KEY与SELENIUM_BROWSER环境变量如 chrome/firefox/ie/safari再执行gulp test --targettests。贡献代码改动需通过gulp build --targetlib --modeopt、gulp build --targettests、gulp lint、gulp test --targetspac及四浏览器下的gulp test --targettests并严格遵守 Google JavaScript 风格指南。进一步阅读快速上手 demos/todo/README.md常见问题docs/FAQ.md规范索引docs/spec_index.md设计文档索引docs/dd_index.md开发者环境搭建docs/dev_setup.md运行测试说明docs/running_tests.md与 SQLite 对比docs/sqlite_comparison.md源码入口Schema 构建在 lib/schema/builder.js 与 lib/schema/table_builder.js类型枚举在 lib/enums.js查询构建器接口在 lib/query.js谓词组合在 lib/op.js 与 lib/predicate.js聚合函数在 lib/fn.js查询引擎各 pass 与步骤在 lib/proc/。赞分享数据库关系型数据库嵌入式数据库【免费下载链接】lovefieldLovefield is a relational database for web apps. Written in JavaScript, works cross-browser. Provides SQL-like APIs that are fast, safe, and easy to use.项目地址https://gitcode.com/gh_mirrors/lo/lovefield点击查看免费下载相关推荐深入解析Lovefield源码架构揭秘JavaScript关系数据库的设计哲学 深入解析Lovefield源码架构揭秘JavaScript关系数据库的设计哲学 Lovefield是一个专为Web应用设计的JavaScript关系数据数据库关系型数据库嵌入式数据库Lovefield 完全指南纯 JavaScript 关系型数据库的 Schema 建模、SQL 风格查询与跨浏览器存储实践Lovefield 完全指南纯 JavaScript 关系型数据库的 Schema 建模、SQL 风格查询与跨浏览器存储实践 Lovefield 是一个用纯关系型数据库数据库前端Lovefield完整指南如何在浏览器中构建强大的关系型数据库Lovefield完整指南如何在浏览器中构建强大的关系型数据库 Lovefield是一个纯JavaScript编写的关系型数据库专为Web应用设计。它提供S关系型数据库数据库前端上一篇Feast Hazelcast Online Store 实战指南配置、原理与源码解析下一篇Potpie Resource Manager 契约深度解析授权上下文租约Authorized Context Lease的边界、流程与实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考