使用 Sequelize.js 和 PostgreSQL 查询关联模型上的 JSONB 字段

alj*_*zen 4 postgresql node.js sequelize.js typescript

我有我的两个模型FooBarFoo有一个 field barId,因此有一个Bar与之关联的对象。我可以查询所有Foo对象并包含它们的关联Bar对象(我将 TypeScript 与sequ​​elize-typescript一起使用):

Foo.findAll<Foo>({
  include: [{ model: Bar }]
});
Run Code Online (Sandbox Code Playgroud)

Barjsonb_field对象有一个带有结构的JSONB 字段

{ inner_field1: 'some text', inner_field2: 'some more text' }
Run Code Online (Sandbox Code Playgroud)

我可以查询Bar对象并按inner_field1如下方式进行过滤:

Bar.findAll<Bar>({
  where: { 'jsonb_field': { inner_field1: 'text to find' } }
});
Run Code Online (Sandbox Code Playgroud)

这会产生以下 SQL 查询:

SELECT ... FROM "Bar" AS "Bar" 
WHERE ("Bar"."jsonb_field"#>>'{inner_field1}') = 'text to find'
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。现在让我们尝试查询Foo对象,包括Bar对象并按以下条件过滤inner_field1

Foo.findAll<Foo>({
  where: { '$bar.jsonb_field$': { inner_field1: 'text to find' } },
  include: [{ model: Bar }]
});
Run Code Online (Sandbox Code Playgroud)

现在这会引发异常:

Error: Invalid value [object Object]
    at Object.escape ({project_root}\node_modules\sequelize\lib\sql-string.js:50:11)
    at Object.escape ({project_root}\node_modules\sequelize\lib\dialects\abstract\query-generator.js:917:22)
    at Object.whereItemQuery ({project_root}\node_modules\sequelize\lib\dialects\abstract\query-generator.js:2095:41)
    at _.forOwn ({project_root}\node_modules\sequelize\lib\dialects\abstract\query-generator.js:1937:25)
    ...
Run Code Online (Sandbox Code Playgroud)

作为记录,我正确包含了 Bar 对象,因为我可以按其他非 JSONB 属性进行过滤,如下所示:

Foo.findAll<Foo>({
  where: { '$bar.number_field$': 5 },
  include: [{ model: Bar }]
});
Run Code Online (Sandbox Code Playgroud)

据我所知,问题在于 Sequelize 不知道 的类型,jsonb_field因此当对象传递到 where 查询时它会抛出错误。

有没有办法解决这个错误,也许使用sequelize.literal()or sequelize.json()

alj*_*zen 6

用途sequelize.cast$contains操作者:

Foo.findAll<Foo>({
  where: { '$bar.jsonb_field$': {
    $contains: sequelize.cast('{ "inner_field1": "text to find" }', 'jsonb')
  },
  include: [{ model: Bar }]
});
Run Code Online (Sandbox Code Playgroud)

或者按照您的建议使用sequelize.literal

Foo.findAll<Foo>({
  where: { '$bar.jsonb_field$': {
    $contains: sequelize.literal(`'{ "inner_field1": "text to find" }'::json`)
  },
  include: [{ model: Bar }]
});
Run Code Online (Sandbox Code Playgroud)

这两种解决方案都容易受到SQL 注入的攻击,以确保转义或删除所有可能导致问题的字符("、、'...)

是的,我刚刚回答了我自己的问题。不客气。