kopia lustrzana https://github.com/cloudflare/wildebeest
Merge pull request #353 from cloudflare/sven/introduce-qb
introduce simple SQL query builderpull/354/head
commit
efdf8eed0e
|
@ -1,6 +1,27 @@
|
|||
import { type Database } from 'wildebeest/backend/src/database'
|
||||
import { type Database, QueryBuilder } from 'wildebeest/backend/src/database'
|
||||
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||
|
||||
export default function make({ DATABASE }: Pick<Env, 'DATABASE'>): Database {
|
||||
return DATABASE
|
||||
const qb: QueryBuilder = {
|
||||
jsonExtract(obj: string, prop: string): string {
|
||||
return `json_extract(${obj}, '$.${prop}')`
|
||||
},
|
||||
|
||||
jsonExtractIsNull(obj: string, prop: string): string {
|
||||
return `${qb.jsonExtract(obj, prop)} IS NULL`
|
||||
},
|
||||
|
||||
set(array: string): string {
|
||||
return `(SELECT value FROM json_each(${array}))`
|
||||
},
|
||||
|
||||
epoch(): string {
|
||||
return '00-00-00 00:00:00'
|
||||
},
|
||||
}
|
||||
|
||||
export default function make({ DATABASE }: Pick<Env, 'DATABASE'>): Database {
|
||||
const db = DATABASE as any
|
||||
db.qb = qb
|
||||
|
||||
return db as Database
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ export interface Database {
|
|||
dump(): Promise<ArrayBuffer>
|
||||
batch<T = unknown>(statements: PreparedStatement[]): Promise<Result<T>[]>
|
||||
exec<T = unknown>(query: string): Promise<Result<T>>
|
||||
qb: QueryBuilder
|
||||
}
|
||||
|
||||
export interface PreparedStatement {
|
||||
|
@ -23,6 +24,13 @@ export interface PreparedStatement {
|
|||
raw<T = unknown>(): Promise<T[]>
|
||||
}
|
||||
|
||||
export interface QueryBuilder {
|
||||
jsonExtract(obj: string, prop: string): string
|
||||
jsonExtractIsNull(obj: string, prop: string): string
|
||||
set(array: string): string
|
||||
epoch(): string
|
||||
}
|
||||
|
||||
export async function getDatabase(env: Pick<Env, 'DATABASE'>): Promise<Database> {
|
||||
return d1(env)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export async function getHomeTimeline(domain: string, db: Database, actor: Actor
|
|||
`
|
||||
SELECT
|
||||
actor_following.target_actor_id as id,
|
||||
json_extract(actors.properties, '$.followers') as actorFollowersURL
|
||||
${db.qb.jsonExtract('actors.properties', 'followers')} as actorFollowersURL
|
||||
FROM actor_following
|
||||
INNER JOIN actors ON actors.id = actor_following.target_actor_id
|
||||
WHERE actor_id=? AND state='accepted'
|
||||
|
@ -60,9 +60,9 @@ INNER JOIN objects ON objects.id = outbox_objects.object_id
|
|||
INNER JOIN actors ON actors.id = outbox_objects.actor_id
|
||||
WHERE
|
||||
objects.type = 'Note'
|
||||
AND outbox_objects.actor_id IN (SELECT value FROM json_each(?2))
|
||||
AND json_extract(objects.properties, '$.inReplyTo') IS NULL
|
||||
AND (outbox_objects.target = '${PUBLIC_GROUP}' OR outbox_objects.target IN (SELECT value FROM json_each(?3)))
|
||||
AND outbox_objects.actor_id IN ${db.qb.set('?2')}
|
||||
AND ${db.qb.jsonExtractIsNull('objects.properties', 'inReplyTo')}
|
||||
AND (outbox_objects.target = '${PUBLIC_GROUP}' OR outbox_objects.target IN ${db.qb.set('?3')})
|
||||
GROUP BY objects.id
|
||||
ORDER by outbox_objects.published_date DESC
|
||||
LIMIT ?4
|
||||
|
@ -101,7 +101,7 @@ export enum LocalPreference {
|
|||
function localPreferenceQuery(preference: LocalPreference): string {
|
||||
switch (preference) {
|
||||
case LocalPreference.NotSet:
|
||||
return '1'
|
||||
return 'true'
|
||||
case LocalPreference.OnlyLocal:
|
||||
return 'objects.local = 1'
|
||||
case LocalPreference.OnlyRemote:
|
||||
|
@ -136,7 +136,7 @@ INNER JOIN actors ON actors.id=outbox_objects.actor_id
|
|||
LEFT JOIN note_hashtags ON objects.id=note_hashtags.object_id
|
||||
WHERE objects.type='Note'
|
||||
AND ${localPreferenceQuery(localPreference)}
|
||||
AND json_extract(objects.properties, '$.inReplyTo') IS NULL
|
||||
AND ${db.qb.jsonExtractIsNull('objects.properties', 'inReplyTo')}
|
||||
AND outbox_objects.target = '${PUBLIC_GROUP}'
|
||||
${hashtagFilter}
|
||||
GROUP BY objects.id
|
||||
|
|
|
@ -76,12 +76,14 @@ describe('Mastodon APIs', () => {
|
|||
})
|
||||
|
||||
test('GET /apps is bad request', async () => {
|
||||
const db = await makeDB()
|
||||
const vapidKeys = await generateVAPIDKeys()
|
||||
const request = new Request('https://example.com')
|
||||
const ctx: any = {
|
||||
next: () => new Response(),
|
||||
data: null,
|
||||
env: {
|
||||
DATABASE: db,
|
||||
VAPID_JWK: JSON.stringify(vapidKeys),
|
||||
},
|
||||
request,
|
||||
|
|
|
@ -81,7 +81,7 @@ describe('Mastodon APIs', () => {
|
|||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
json_extract(properties, '$.content') as content,
|
||||
${db.qb.jsonExtract('properties', 'content')} as content,
|
||||
original_actor_id,
|
||||
original_object_id
|
||||
FROM objects
|
||||
|
@ -758,7 +758,7 @@ describe('Mastodon APIs', () => {
|
|||
const row = await db
|
||||
.prepare(
|
||||
`
|
||||
SELECT json_extract(properties, '$.inReplyTo') as inReplyTo
|
||||
SELECT ${db.qb.jsonExtract('properties', 'inReplyTo')} as inReplyTo
|
||||
FROM objects
|
||||
WHERE mastodon_id=?
|
||||
`
|
||||
|
|
|
@ -9,6 +9,7 @@ import * as path from 'path'
|
|||
import { BetaDatabase } from '@miniflare/d1'
|
||||
import * as SQLiteDatabase from 'better-sqlite3'
|
||||
import { type Database } from 'wildebeest/backend/src/database'
|
||||
import d1 from 'wildebeest/backend/src/database/d1'
|
||||
|
||||
export function isUrlValid(s: string) {
|
||||
let url
|
||||
|
@ -32,7 +33,8 @@ export async function makeDB(): Promise<Database> {
|
|||
db.exec(content)
|
||||
}
|
||||
|
||||
return db2 as unknown as Database
|
||||
const env = { DATABASE: db2 } as any
|
||||
return d1(env)
|
||||
}
|
||||
|
||||
export function assertCORS(response: Response) {
|
||||
|
|
|
@ -140,7 +140,7 @@ FROM outbox_objects
|
|||
INNER JOIN objects ON objects.id=outbox_objects.object_id
|
||||
INNER JOIN actors ON actors.id=outbox_objects.actor_id
|
||||
WHERE objects.type='Note'
|
||||
${withReplies ? '' : "AND json_extract(objects.properties, '$.inReplyTo') IS NULL"}
|
||||
${withReplies ? '' : 'AND ' + db.qb.jsonExtractIsNull('objects.properties', 'inReplyTo')}
|
||||
AND outbox_objects.target = '${PUBLIC_GROUP}'
|
||||
AND outbox_objects.actor_id = ?1
|
||||
AND outbox_objects.cdate > ?2
|
||||
|
@ -161,7 +161,7 @@ LIMIT ?3 OFFSET ?4
|
|||
return new Response(JSON.stringify(out), { headers })
|
||||
}
|
||||
|
||||
let afterCdate = '00-00-00 00:00:00'
|
||||
let afterCdate = db.qb.epoch()
|
||||
if (url.searchParams.has('max_id')) {
|
||||
// Client asked to retrieve statuses after the max_id
|
||||
// As opposed to Mastodon we don't use incremental ID but UUID, we need
|
||||
|
|
Ładowanie…
Reference in New Issue