核心定位与设计差异
Kysely 本质上是一款查询构建器,与对象关系映射(ORM)工具存在明显区别。从设计理念出发,它并不承担处理数据关系以及其他 ORM 工具常见功能的职责。反观 OrchidORM,尽管两者定位不同,但将它们进行对比依然具备重要意义。这两个库均在复杂查询构建方面表现出色,并且都高度重视类型安全,保障代码在数据操作过程中的准确性与可靠性。
OrchidORM 的设计在一定程度上借鉴了 Kysely 的思路。让 OrchidORM 拥有与 Kysely 同等水平的灵活性与类型安全性,是 OrchidORM 项目的重要目标之一。然而,目前 OrchidORM 尚未完全覆盖 Kysely 的全部功能,例如 Kysely 支持的 case-when
SQL 构建器,在 OrchidORM 中暂未实现。
在数据库支持范围上,两者也有显著差异。OrchidORM 仅适配 PostgreSQL 数据库,专注于挖掘该数据库的强大功能;而 Kysely 则展现出更广泛的兼容性,支持多种不同类型的数据库,为开发者提供了更多选择。
详细功能对比示例
数据查询操作
单列数据检索
在单列数据查询场景下,OrchidORM 具备独特的默认行为,它默认返回符合条件的多条记录,因此无需调用 execute()
方法执行查询操作。开发者可以使用 take
方法获取第一条记录(若记录不存在则抛出错误),或通过 takeOptional
方法获取第一条记录(记录不存在时返回 undefined
),这两个方法分别对应 Kysely 中的 executeTakeFirstOrThrow
和 executeTakeFirst
功能。此外,OrchidORM 还提供了 get
方法用于精准获取单个值,pluck
方法用于提取单列数据并生成扁平数组,满足多样化的数据获取需求。
// OrchidORM
import { db } from './tables';
const persons = await db.person.select('id').where({ firstName: 'Arnold' });
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person')
.select('id')
.where('first_name', '=', 'Arnold')
.execute();
多表关联列查询
当涉及从多个表中选取数据时,OrchidORM 有着严格的规范,明确禁止在未指定表名的情况下进行模糊的列选择操作,以此确保查询结果的准确性和唯一性。而 Kysely 在部分情况下允许不指定表名的模糊列选择,但这种操作方式可能会引发结果不确定性的风险,相比之下,OrchidORM 的设计更倾向于通过强制指定表名来保障查询逻辑的清晰性。
// OrchidORM
import { db } from './tables';
const persons = await db.$from([db.person, db.pet]).select('person.id');
// 模糊选择被禁止
// .select('id')
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom(['person', 'pet'])
.select('person.id')
// 模糊选择被允许
// .select('id')
.execute();
多列数据选取
在进行多列数据查询时,OrchidORM 和 Kysely 采用了不同的语法与操作模式。OrchidORM 可以直接在 select
方法中依次列出多个列名,简洁地完成多列选择;Kysely 则需要在 select
方法中明确指定每一列所属的表(若使用了表别名,必须正确引用),并通过调用 execute()
方法来触发查询执行,操作流程相对更为细致。
// OrchidORM
import { db } from './tables';
const persons = await db.person.select('id', 'firstName');
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person')
.select(['person.id', 'first_name'])
.execute();
列别名设置与使用
在列别名的设置与运用方面,两个库同样存在差异。OrchidORM 能够通过 as
方法为表设置别名,并在 select
操作中巧妙运用别名指定列,实现灵活的数据展示;Kysely 则是在 selectFrom
方法中为表赋予别名,并在 select
方法中借助 SQL 表达式,结合别名完成列的选择与别名效果的设置,两者在实现逻辑上各有特点。
// OrchidORM
import { db } from './tables';
const persons = await db.person.as('p').select({
fn: 'firstName',
ln: 'p.lastName',
});
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person as p')
.select(['first_name as fn', 'p.last_name as ln'])
.execute();
复杂数据筛选查询
面对复杂查询场景,例如涉及子查询、连接条件等操作时,q
通常作为 “query builder”(查询构建器)的标识,开发者也可根据习惯使用其他名称。在 OrchidORM 中,像 firstPetName
这类数据的查询,采用 LEFT JOIN LATERAL
方式实现,该方式不仅执行效率较高,还支持根据实际需求灵活切换为内连接操作,展现出强大的适应性。Kysely 同样能够实现复杂查询功能,通过合理构建子查询、灵活运用表达式构建器等手段,达成与 OrchidORM 相似的复杂数据筛选与获取效果,满足开发者多样化的业务需求。
// OrchidORM
import { db } from './tables';
const persons = await db.person.select({
// Select a correlated subquery
firstPetName: (q) =>
q.pets
.getOptional('pets.name')
.order('pets.name'),
// The same as above,
// but referencing the join condition explicitly
firstPetName2: (q) =>
db.pet
.getOptional('pet.name')
.where({ ownerId: q.ref('person.id') })
.order('pet.name'),
// Build and select an expression using
// the expression builder
isJenniferOrArnold: (q) =>
q.ref('firstName').equals('Jennifer').or(
q.ref('firstName').equals('Arnold')
),
// Select a raw sql expression
fullName: (q) =>
q.sql<string>`concat(first_name, ' ', last_name)`,
})
import { db } from './tables';
import { sql } from 'kysely';
const persons = await db.selectFrom('person')
.select(({ eb, selectFrom, or }) => [
// Select a correlated subquery
selectFrom('pet')
.whereRef('person.id', '=', 'pet.owner_id')
.select('pet.name')
.orderBy('pet.name')
.limit(1)
.as('first_pet_name'),
// Build and select an expression using
// the expression builder
or([
eb('first_name', '=', 'Jennifer'),
eb('first_name', '=', 'Arnold')
]).as('is_jennifer_or_arnold'),
// Select a raw sql expression
sql<string>`concat(first_name, ' ', last_name)`.as('full_name')
])
.execute()
非空数据查询
OrchidORM 的 take
方法与 Kysely 的 executeTakeFirstOrThrow
功能类似,但在 OrchidORM 中,take
方法还可应用于嵌套选择场景。在实际操作中,开发者可以利用这一特性,结合 where
条件筛选出非空数据,并通过 narrowType
方法移除结果中的 null
类型,确保获取到的数据准确有效。而 Kysely 则通过构建复杂的 select
语句,使用 $notNull()
等方法实现类似的非空数据查询与类型处理逻辑。
// OrchidORM
import { db } from './tables';
const persons = await db.person
.select('lastName', {
// `take()` 如果找不到 pet 会抛出异常
pet: (q) => q.pets.take(),
})
.where({ lastName: { not: null } })
// 使用 `narrowType` 移除结果中的 null 类型
.narrowType()<{ lastName: string }>();
// Kysely
import { db } from './tables';
import { NotNull } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
const persons = await db
.selectFrom('person')
.select((eb) => [
'last_name',
jsonObjectFrom(
eb
.selectFrom('pet')
.selectAll('pet')
.limit(1)
.whereRef('person.id', '=', 'pet.owner_id'),
)
.$notNull()
.as('pet'),
])
.where('last_name', 'is not', null)
.$narrowType<{ last_name: NotNull }>()
.execute();
函数调用查询
在函数调用查询方面,OrchidORM 和 Kysely 存在细微差别。Kysely 采用 .type((t) => t.string())
的方式对查询进行类型包裹,以便使用字符串专用的 where
过滤条件;而 OrchidORM 则通过 fn
方法调用函数,并指定相应的参数和返回类型,实现对数据的计算与筛选。例如,在计算宠物数量、拼接完整姓名、聚合宠物名字等操作中,两者均能通过函数调用完成复杂的数据处理任务,但具体的语法和操作方式有所不同。
// Orchid ORM
import { db } from './tables';
const result = await db.person
.join('pets') // join is equal to inner join
.select('person.id', {
petCount: (q) => q.count('pets.*'),
fullNameWithTitle: (q) =>
q.fn('concat', [
q.val('Ms. '),
'firstName',
q.val(' '),
'lastName',
]).type((t) => t.string()),
petNames: (q) =>
q.fn('array_agg', ['pets.name']).type((t) => t.array(t.string())),
fullName: (q) =>
q.sql<string>`concat(
${q.ref('firstName')},
' ',
${q.ref('lastName')}
)`,
})
.group('id')
.having((q) => q.count('pets.id').gt(10))
//Kysely
import { db } from './tables';
import { sql } from 'kysely'
const result = await db.selectFrom('person')
.innerJoin('pet', 'pet.owner_id', 'person.id')
.select(({ fn, val, ref }) => [
'person.id',
fn.count<number>('pet.id').as('pet_count'),
fn<string>('concat', [
val('Ms. '),
'first_name',
val(' '),
'last_name'
]).as('full_name_with_title'),
fn.agg<string[]>('array_agg', ['pet.name']).as('pet_names'),
sql<string>`concat(
${ref('first_name')},
' ',
${ref('last_name')}
)`.as('full_name')
])
.groupBy('person.id')
.having((eb) => eb.fn.count('pet.id'), '>', 10)
.execute()
去重查询
在去重查询操作上,OrchidORM 和 Kysely 的实现方式较为相似。OrchidORM 只需调用 distinct()
方法,并在 select
中指定要去重的列,即可轻松实现数据去重;Kysely 则是在 selectFrom
构建查询后,调用 distinct()
方法,并通过 execute()
执行查询,获取去重后的结果,两者都为开发者提供了便捷的去重查询功能。
// OrchidORM
import { db } from './tables';
const persons = await db.person.distinct().select('firstName');
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person')
.select('first_name')
.distinct()
.execute();
全列查询
在全列查询方面,OrchidORM 出于数据安全性和查询合理性的考量,不支持隐式选择所有表的所有字段,因为这种操作方式可能会带来性能损耗和数据冗余问题。而 Kysely 提供了 selectAll()
方法,方便开发者快速选择表中的所有列,适用于一些对数据完整性要求较高且性能影响在可接受范围内的场景。
// OrchidORM
import { db } from './tables';
// 不支持。
// 隐式选择所有表的所有字段不是一个好主意。
// Kysely
import { db } from './tables';
const persons = await db.selectFrom('person').selectAll().execute();
表全列查询
当需要查询表中的所有列时,OrchidORM 可以直接引用表对象,简洁地获取表的全部数据;Kysely 则需要在 selectFrom
方法中指定表名,并调用 selectAll('person')
方法,明确告知查询构建器要选择指定表的所有列,两者在实现方式上各有侧重。
// OrchidORM
import { db } from './tables';
const persons = await db.person;
// Kysely
import { db } from './tables';
const persons = await db.selectFrom('person').selectAll('person').execute();
嵌套数组查询
在处理嵌套数组查询时,OrchidORM 由于底层采用 JOIN LATERAL
技术,开发者可以灵活地将 q.pets
改为 q.join().pets
,以此过滤掉没有宠物的结果,精准获取所需数据。Kysely 则通过 jsonArrayFrom
方法,将子查询结果转换为 JSON 数组形式,并在 select
中进行合理配置,实现类似的嵌套数组查询效果,满足复杂数据结构的查询需求。
// OrchidORM
import { db } from './tables';
const result = await db.person.select('id', {
pets: (q) =>
q.pets.select('name', {
petId: 'id',
}),
});
// Kysely
import { db } from './tables';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
const result = await db
.selectFrom('person')
.select((eb) => [
'id',
jsonArrayFrom(
eb
.selectFrom('pet')
.select(['pet.id as pet_id', 'pet.name'])
.whereRef('pet.owner_id', '=', 'person.id')
.orderBy('pet.name'),
).as('pets'),
])
.execute();
嵌套对象查询
对于嵌套对象查询,OrchidORM 和 Kysely 同样提供了有效的解决方案。在 OrchidORM 中,开发者可以通过 select
方法结合子查询,筛选出符合条件的嵌套对象数据,并使用 takeOptional
等方法处理可选结果;Kysely 则借助 jsonObjectFrom
方法,将子查询结果转换为 JSON 对象,并在 select
中进行精确设置,两者都能准确地处理嵌套对象数据的查询与筛选。
// OrchidORM
import { db } from './tables';
const result = await db.person.select('id', {
favoritePet: (q) =>
q.pets
.select('name', { petId: 'id' })
.where({ isFavorite: true })
.takeOptional(),
});
// Kysely
import { db } from './tables';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
const result = await db
.selectFrom('person')
.select((eb) => [
'id',
jsonObjectFrom(
eb
.selectFrom('pet')
.select(['pet.id as pet_id', 'pet.name'])
.whereRef('pet.owner_id', '=', 'person.id')
.where('pet.is_favorite', '=', true),
).as('favorite_pet'),
])
.execute();
条件查询操作
基础条件过滤
在基础条件查询过程中,OrchidORM 针对不同的数据列类型,精心设计了相应的 where
辅助函数,确保开发者在设置查询条件时的准确性和规范性。例如,如果尝试在非数字列上使用 gt
(大于)操作,TypeScript 会立即报错提示,避免因条件设置错误导致查询结果异常。而 Kysely 则通过 where
方法直接设置查询条件,并调用 executeTakeFirst()
等方法执行查询,获取符合条件的第一条记录,两者在基础条件查询的实现上各有特色。
// OrchidORM
import { db } from './tables';
const person = await db.person
.where({
firstName: 'Jennifer',
age: { gt: 40 },
})
.takeOptional();
// Kysely
import { db } from './tables';
const person = await db
.selectFrom('person')
.selectAll()
.where('first_name', '=', 'Jennifer')
.where('age', '>', 40)
.executeTakeFirst();
IN 条件查询
OrchidORM 和 Kysely 均对 IN
条件查询提供了良好的支持,并且都允许使用子查询或原始查询结果作为 IN
操作的值。这一特性使得开发者能够根据实际业务需求,灵活地构建复杂的 IN
条件查询,实现精准的数据筛选。
// OrchidORM
import { db } from './tables';
const persons = await db.person.whereIn('id', [1, 2, 3]);
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person')
.selectAll()
.where('id', 'in', [1, 2, 3])
.execute();
多列 IN 条件查询
在多列 IN
条件查询场景下,两个库同样表现出色。OrchidORM 通过 whereIn
方法,传入包含多列的数组和对应的值数组,轻松实现多列 IN
条件查询;Kysely 则需要构建更为复杂的 where
条件,借助 refTuple
(引用元组)等方法,准确地设置多列 IN
条件,确保查询结果符合预期。
// OrchidORM
import { db } from './tables';
const persons = await db.person.whereIn(
['firstName', 'lastName'],
[
['Jennifer', 'Aniston'],
['Arnold', 'Schwarzenegger'],
],
);
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person')
.selectAll()
.where(({ eb, refTuple, tuple }) =>
eb(refTuple('first_name', 'last_name'), 'in', [
tuple('Jennifer', 'Aniston'),
tuple('Arnold', 'Schwarzenegger'),
]),
)
.execute();
对象条件过滤
在对象条件过滤方面,OrchidORM 允许开发者在 where
方法中以对象形式设置多个条件,并且支持使用子查询动态生成条件值,实现灵活的数据筛选。Kysely 则通过 where
方法结合 and
操作,在回调函数中设置多个条件,同样能够满足对象条件过滤的需求,两者在实现逻辑上有所不同,但都能有效完成数据筛选任务。
// OrchidORM
import { db } from './tables';
const persons = await db.person.where({
firstName: 'Jennifer',
lastName: (q) => q.ref('firstName'),
});
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person')
.selectAll()
.where((eb) =>
eb.and({
first_name: 'Jennifer',
last_name: eb.ref('first_name'),
}),
)
.execute();
逻辑或条件查询
OrchidORM 提供了 whereOneOf
和 orWhere
方法,分别表示 “并且其中之一为真” 和 “或者其中之一为真” 的逻辑关系。开发者可以通过链式调用这些方法,轻松构建复杂的逻辑或条件查询。Kysely 则通过 or
方法在回调函数中灵活组合多个条件,实现类似的逻辑或查询功能,为开发者提供了多样化的条件组合方式。
// OrchidORM
import { db } from './tables';
const persons = await db.person
// 1. 使用 `whereOneOf` 方法
.whereOneOf({ firstName: 'Jennifer' }, { firstName: 'Sylvester' })
// 2. 链式调用 `orWhere`
.where((q) =>
q.where({ lastName: 'Aniston' }).orWhere({ lastName: 'Stallone' }),
);
// Kysely
import { db } from './tables';
const persons = await db
.selectFrom('person')
.selectAll()
// 1. 使用 `or` 方法
.where((eb) =>
eb.or([
eb('first_name', '=', 'Jennifer'),
eb('first_name', '=', 'Sylvester'),
]),
)
// 2. 链式调用 `or`
.where((eb) =>
eb('last_name', '=', 'Aniston').or('last_name', '=', 'Stallone'),
)
.execute();
条件动态查询
在条件动态查询场景下,常见的需求是根据可选参数动态过滤查询结果。OrchidORM 在这方面表现出极大的便利性,它会自动忽略值为 undefined
的参数,开发者无需额外编写复杂的条件判断逻辑,即可保持代码的简洁性和可读性。而 Kysely 则需要开发者手动编写 if
语句,根据参数是否存在动态构建查询条件,相比之下,OrchidORM 的设计更贴合实际开发需求,减少了开发者的代码编写量。
// OrchidORM
import { db } from './tables';
const firstName: string | undefined = 'Jennifer';
const lastName: string | undefined = 'Aniston';
const under18 = true;
const over60 = true;
const persons = await db.person
.where({
// undefined 会被忽略
firstName,
lastName,
})
.whereOneOf(
...[under18 && { age: { lt: 60 } }, over60 && { age: { gt: 60 } }].filter(
Boolean,
),
);
// Kysely
import { db } from './tables';
const firstName: string | undefined = 'Jennifer';
const lastName: string | undefined = 'Aniston';
const under18 = true;
const over60 = true;
let query = db.selectFrom('person').selectAll();
if (firstName) {
query = query.where('first_name', '=', firstName);
}
if (lastName) {
query = query.where('last_name', '=', lastName);
}
if (under18 || over60) {
query = query.where((eb) =>
eb.or(
[under18 && eb('age', '<', 18), over60 && eb('age', '>', 60)].filter(
Boolean,
),
),
);
}
const persons = await query.execute();
复杂条件组合查询
对于复杂条件组合查询,OrchidORM 的 whereExists
和 whereNotExists
方法支持接受任意子查询作为条件,开发者可以利用这一特性构建复杂的嵌套条件,实现精准的数据筛选。Kysely 则通过在 where
方法中灵活运用 and
、or
、not
等逻辑操作符,结合子查询和表达式构建器,同样能够实现复杂条件组合查询,满足各种复杂业务场景的需求。
// OrchidORM
import { db } from './tables';
const firstName = 'Jennifer';
const maxAge = 60;
const persons = await db.person
.whereOneOf({ firstName }, { age: { lt: maxAge } })
.whereNotExists('pets');
// Kysely
import { db } from './tables';
const firstName = 'Jennifer';
const maxAge = 60;
const persons = await db
.selectFrom('person')
.selectAll('person')
.where(({ eb, or, and, not, exists, selectFrom }) =>
and([
or([eb('first_name', '=', firstName), eb('age', '<', maxAge)]),
not(
exists(
selectFrom('pet')
.select('pet.id')
.whereRef('pet.owner_id', '=', 'person.id'),
),
),
]),
)
.execute();
连接查询操作
简单内连接查询
在简单内连接查询时,当连接多个表并选择 id
列数据,OrchidORM 与 Kysely 存在明显差异。OrchidORM 会自动为列名添加表名前缀,避免因列名重复导致的歧义问题,确保查询结果的准确性;而 Kysely 则不会自动添加表名前缀,如果开发者不手动指定,可能会选择到错误表的 id
列,相比之下,OrchidORM 的设计更能保障查询的可靠性。
// OrchidORM
import { db } from './tables';
const result = await db.person
.join('pets')
// id 解析为 person.id,无歧义
.select('id', { petName: 'pets.name' });
// Kysely
import { db } from './tables';
const result = await db
.selectFrom('person')
.innerJoin('pet', 'pet.owner_id', 'person.id')
.select(['person.id', 'pet.name as pet_name'])
// 这会选择 pet 的 id
// .select(['id'])
.execute();
别名内连接查询
在使用别名进行内连接查询时,OrchidORM 和 Kysely 的操作方式和结果呈现也有所不同。OrchidORM 通过 join((q) => q.pets.as('p'))
为连接的表设置别名,并在后续的 where
条件中使用别名进行筛选,查询结果中的 id
明确指向 person
表的 id
列;Kysely 则在 innerJoin('pet as p', 'p.owner_id', 'person.id')
中设置别名,若不仔细处理,查询结果中的 id
可能会被误认为是 pet
表的 id
列,需要开发者更加谨慎地编写查询语句。
// OrchidORM
import { db } from './tables';
const result = await db.person
.join((q) => q.pets.as('p'))
.where({ 'p.name': 'Doggo' });
result[0]?.id; // 这是 person 的 id
// Kysely
import { db } from './tables';
const result = await db
.selectFrom('person')
.innerJoin('pet as p', 'p.owner_id', 'person.id')
.where('p.name', '=', 'Doggo')
.selectAll()
.execute();
result[0]?.id; // 这是 pet 的 id
复杂连接查询
面对复杂连接查询场景,OrchidORM 和 Kysely 的处理方式各有千秋。OrchidORM 在连接多个表时,通过在 join
方法的回调函数中设置详细的条件,能够精准地筛选出符合要求的数据,并且只选择主表(如 persons
)的数据,避免数据冗余;Kysely 则在 innerJoin
方法中通过 onRef
、on
等方法设置连接条件,可能会返回主表与关联表合并后的数据,存在列冲突的潜在风险,开发者需要根据实际需求进行数据处理和筛选。
// OrchidORM
import { db } from './tables';
// 只选择 persons
const result = await db.person.join('pets', (q) =>
q
.where({ name: 'Doggo' })
.whereOneOf({ 'person.age': { gt: 10 } }, { 'person.age': { lt: 10 } }),
);
// Kysely
import { db } from './tables';
// 选择 persons 与 pets 合并,可能有列冲突
const result = await db
.selectFrom('person')
.innerJoin('pet', (join) =>
join
.onRef('pet.owner_id', '=', 'person.id')
.on('pet.name', '=', 'Doggo')
.on((eb) =>
eb.or([eb('person.age', '>', 18), eb('person.age', '<', 100)]),
),
)
.selectAll()
.execute();
子查询连接查询
OrchidORM 在子查询连接查询方面具有独特的设计理念,它倾向于在 select
操作中使用隐式连接,同时也全面支持 join
、leftJoin
、fullJoin
、joinLateral
等多种连接方式,并且可以灵活地使用简单值、关系、子查询、CTE 表作为连接条件,展现出强大的兼容性和灵活性。Kysely 同样能够实现子查询连接查询功能,通过在 innerJoin
方法中合理构建子查询,并设置准确的连接条件,获取所需的查询结果,满足不同业务场景下的复杂数据连接需求。
// OrchidORM
import { db } from './tables';
const result = await db.person
.join(
db.pet
.as('doggo')
.select('name', { owner: 'ownerId' })
.where({ name: 'Doggo' }),
(q) => q.on('doggo.owner', 'person.id'),
)
// 'doggo' 会被选择为嵌套对象,避免 person 列与 pet 列冲突
.select('id', 'doggo.*');
// Kysely
import { db } from './tables';
const result = await db
.selectFrom('person')
.innerJoin(
(eb) =>
eb
.selectFrom('pet')
.select(['owner_id as owner', 'name'])
.where('name', '=', 'Doggo')
.as('doggos'),
(join) => join.onRef('doggos.owner', '=', 'person.id'),
)
.selectAll('doggos')
.execute();
数据插入操作
单行数据插入
在单行数据插入操作上,OrchidORM 和 Kysely 的行为和返回结果有所不同。OrchidORM 的 insert
方法默认返回插入的记录数量,而 create
方法默认返回完整的插入记录,开发者可以根据具体需求选择合适的方法。例如,使用 get
方法结合 insert
操作,可以获取插入记录的单个值(如自增主键 id
)。Kysely 则通过 insertInto
方法插入数据,并使用 returning
方法明确指定要返回的列(如 id
),最后调用 executeTakeFirst()
方法执行插入操作并获取结果,操作流程相对较为固定。
// OrchidORM
import { db } from './tables';
// `get` 返回单个值
const id = await db.person.get('id').insert({
firstName: 'Jennifer',
lastName: 'Aniston',
age: 40,
});
// Kysely
import { db } from './tables';
const result = await db
.insertInto('person')
.values({
first_name: 'Jennifer',
last_name: 'Aniston',
age: 40,
})
.returning(['id'])
.executeTakeFirst();
多行数据插入
对于多行数据插入,OrchidORM 的 insertMany
方法默认返回插入的记录总数,createMany
方法默认返回插入记录组成的数组,开发者还可以结合 pluck
方法获取插入记录中指定列的值组成的扁平数组。Kysely 则在 insertInto
方法中传入包含多个对象的数组,一次性插入多行数据,并通过调用 execute()
方法执行插入操作,实现高效的数据批量插入。
// OrchidORM
import { db } from './tables';
// `pluck` 返回值的扁平数组
const ids = await db.person.pluck('id').insertMany([
{
firstName: 'Jennifer',
lastName: 'Aniston',
age: 40,
},
{
firstName: 'Arnold',
lastName: 'Schwarzenegger',
age: 70,
},
]);
// Kysely
import { db } from './tables';
await db
.insertInto('person')
.values([
{
first_name: 'Jennifer',
last_name: 'Aniston',
age: 40,
},
{
first_name: 'Arnold',
last_name: 'Schwarzenegger',
age: 70,
},
])
.execute();
插入返回数据
在插入数据并返回特定结果方面,OrchidORM 的 select
方法在功能上等同于 Kysely 的 returning
方法,并且在 OrchidORM 中,select
方法可以灵活地放置在插入操作之前或之后。开发者可以使用 select
方法指定要返回的列,实现插入数据的同时获取相关结果;而 Kysely 则需要在 insertInto
方法后使用 returning
方法明确指定返回的列,两者都为开发者提供了插入数据并获取反馈结果的能力。
// OrchidORM
import { db } from './tables';
const result = await db.person.select('id', { name: 'firstName' }).insert({
firstName: 'Jennifer',
lastName: 'Aniston',
age: 40,
});
// Kysely
import { db } from './tables';
const result = await db
.insertInto('person')
.values({
first_name: 'Jennifer',
last_name: 'Aniston',
age: 40,
})
.returning(['id', 'first_name as name'])
.executeTakeFirstOrThrow();
复杂值插入
OrchidORM 和 Kysely 都支持在插入和更新操作中使用回调函数、子查询、原始 SQL 表达式等复杂值。在 OrchidORM 中,开发者可以在 create
方法中通过回调函数调用 sql
方法生成原始 SQL 表达式,或引用其他列的值,还可以使用聚合函数计算插入值;在 Kysely 中,开发者通过在 values
回调函数中使用 sql
方法和表达式构建器,同样能够实现复杂值的插入操作,满足各种复杂业务逻辑下的数据插入需求。
// OrchidORM
import { db } from './tables';
const ani = "Ani"
const ston = "ston"
const result = await db.person.create({
firstName: 'Jennifer',
lastName: (q) => q.sql<string>`concat(${ani}, ${ston})`,
middleName: (q) => q.ref('firstName'),
age: db.person.avg('age'),
})
// Kysely
import { db } from './tables';
import { sql } from 'kysely'
const ani = "Ani"
const ston = "ston"
const result = await db
.insertInto('person')
.values(({ ref, selectFrom, fn }) => ({
first_name: 'Jennifer',
last_name: sql<string>`>concat(${ani}, ${ston})`,
middle_name: ref('first_name'),
age: selectFrom('person')
.select(fn.avg<number>('age').as('avg_age')),
}))
.executeTakeFirst()
子查询插入
在子查询插入操作上,Kysely 提供了 lit
方法用于插入未参数化的值,而 OrchidORM 没有与之类似的方法。不过,OrchidORM 可以通过 insertManyFrom
方法,结合子查询从其他表中选取数据并插入到目标表中;Kysely 则在 insertInto
方法中使用 expression
回调函数,通过子查询和 lit
方法实现更灵活的数据插入操作,两者在子查询插入的实现方式上存在差异。
// OrchidORM
import { db } from './tables';
const count = await db.person.insertManyFrom(
db.pet.select({
firstName: 'pet.name',
lastName: (q) => q.val('Petson'),
age: (q) => q.val(7),
}),
);
// Kysely
import { db } from './tables';
const result = await db
.insertInto('person')
.columns(['first_name', 'last_name', 'age'])
.expression((eb) =>
eb
.selectFrom('pet')
.select((eb) => [
'pet.name',
eb.val('Petson').as('last_name'),
eb.lit(7).as('age'),
]),
)
.execute();
数据更新操作
单行数据更新
在单行数据更新操作中,OrchidORM 的 update
方法默认返回更新的记录数量,并且支持开发者使用 select
、get
等方法从更新操作中选择特定数据。例如,通过 find(1)
定位到特定记录后,调用 update
方法修改其字段值;Kysely 则通过 updateTable
方法设置要更新的表,使用 set
方法指定更新的字段和值,通过 where
方法设置更新条件,最后调用 executeTakeFirst()
方法执行更新操作并获取结果,实现对单行数据的精准更新。
// OrchidORM
import { db } from './tables';
const count = await db.person.find(1).update({
firstName: 'Jennifer',
lastName: 'Aniston',
});
// Kysely
import { db } from './tables';
const result = await db
.updateTable('person')
.set({
first_name: 'Jennifer',
last_name: 'Aniston',
})
.where('id', '=', 1)
.executeTakeFirst();
复杂值更新
在复杂值更新场景下,OrchidORM 的 increment
和 decrement
方法在 SQL 层面与 Kysely 的 eb('age', '+', 1)
类似,都用于实现数值型字段的递增或递减操作。在 OrchidORM 中,开发者可以在 update
操作中结合 increment
方法和其他字段更新操作,实现复杂的数据更新逻辑;在 Kysely 中,通过在 set
回调函数中使用表达式构建器,灵活地组合各种更新操作,满足复杂业务场景下
// OrchidORM
import { db } from './tables';
const count = await db.person
.find(1)
.increment('age')
.update({
firstName: db.pet.get('name'),
lastName: 'updated',
});
// Kysely
import { db } from './tables';
const result = await db
.updateTable('person')
.set((eb) => ({
age: eb('age', '+', 1),
first_name: eb.selectFrom('pet').select('name').limit(1),
last_name: 'updated',
}))
.where('id', '=', 1)
.executeTakeFirst();
删除操作
单行删除
在执行单行数据删除时,OrchidORM 和 Kysely 都提供了便捷的操作方式。OrchidORM 的 delete
方法默认返回删除的记录数量,开发者能够结合 select
、get
等方法,在删除数据的同时筛选并获取相关信息。例如,通过 find(1)
精准定位到某条记录后,调用 delete
方法即可将其从数据库中移除 。而 Kysely 则通过 deleteFrom
方法指定要删除数据的表,利用 where
方法设置删除条件(如 where('person.id', '=', 1)
),最后调用 executeTakeFirst()
方法执行删除操作并获取结果,整个流程清晰且易于理解。
// OrchidORM
import { db } from './tables';
const count = await db.person.find(1).delete();
// Kysely
import { db } from './tables';
const result = await db
.deleteFrom('person')
.where('person.id', '=', 1)
.executeTakeFirst();
事务处理
简单事务
在事务管理方面,忘记使用 trx
对象(事务对象)而误用普通数据库连接对象是开发者常见的错误。OrchidORM 为解决这一问题,引入了 AsyncLocalStorage 机制,能够自动为事务范围内的所有查询匹配正确的事务实例,极大地降低了出错概率。在实际应用中,相关记录可以以嵌套方式创建,此时事务会自动触发,确保数据操作的原子性。例如,在创建宠物与其主人记录时,通过 $transaction
方法开启事务,先创建主人记录获取 ownerId
,再基于此创建宠物记录,若其中任何一步失败,整个事务将回滚 。
反观 Kysely,则通过 transaction
方法开启事务,并在其回调函数中使用 trx
对象执行具体的数据库操作。如先在事务内插入主人记录并获取 id
,再利用该 id
插入对应的宠物记录,最后通过 execute
方法提交事务,若出现异常则可通过捕获错误实现事务回滚,保障数据的一致性和完整性 。
// OrchidORM
import { db } from './tables';
const catto = await db.$transaction(async () => {
const ownerId = await db.person.get('id').create({
firstName: 'Jennifer',
lastName: 'Aniston',
age: 40,
});
return await db.pet.create({
ownerId,
name: 'Catto',
species: 'cat',
isFavorite: false,
});
});
const catto2 = await db.pet.create({
owner: {
create: {
firstName: 'Jennifer',
lastName: 'Aniston',
age: 40,
},
},
name: 'Catto',
species: 'cat',
isFavorite: false,
});
// Kysely
import { db } from './tables';
const catto = await db.transaction().execute(async (trx) => {
const jennifer = await trx
.insertInto('person')
.values({
first_name: 'Jennifer',
last_name: 'Aniston',
age: 40,
})
.returning('id')
.executeTakeFirstOrThrow();
return await trx
.insertInto('pet')
.values({
owner_id: jennifer.id,
name: 'Catto',
species: 'cat',
is_favorite: false,
})
.returningAll()
.executeTakeFirst();
});
CTE(公用表表达式)
简单选择
OrchidORM 和 Kysely 均支持递归 CTE 操作,在简单 CTE 查询场景下,两者展现出不同的实现方式。OrchidORM 通过 $queryBuilder
结合 with
方法定义 CTE,首先定义名为 jennifers
的 CTE,筛选出名字为 Jennifer
的人员记录并选取 id
和 age
列;接着定义 adult_jennifers
CTE,基于 jennifers
进一步筛选出年龄大于 18 岁的记录;最后从 adult_jennifers
中查询年龄小于 60 岁的数据 。
Kysely 同样通过 with
方法定义 CTE,先创建 jennifers
CTE 进行初步筛选,再构建 adult_jennifers
CTE 进行二次过滤,最后在 selectFrom
方法中引用已定义的 CTE,并通过 where
条件筛选数据,调用 execute()
方法执行查询获取最终结果。两者虽然语法略有不同,但都能高效实现基于 CTE 的数据查询与处理 。
// OrchidORM
import { db } from './tables';
const result = await db.$queryBuilder
.with(
'jennifers',
db.person.where({ firstName: 'Jennifer' }).select('id', 'age'),
)
.with('adult_jennifers', (q) =>
q.from('jennifers').where({ age: { gt: 18 } }),
)
.from('adult_jennifers')
.where({ age: { lt: 60 } });
// Kysely
import { db } from './tables';
const result = await db
.with('jennifers', (db) =>
db
.selectFrom('person')
.where('first_name', '=', 'Jennifer')
.select(['id', 'age']),
)
.with('adult_jennifers', (db) =>
db.selectFrom('jennifers').where('age', '>', 18).select(['id', 'age']),
)
.selectFrom('adult_jennifers')
.where('age', '<', 60)
.selectAll()
.execute();