Skip to content

Commit

Permalink
packages/modeldb: enable composite indices
Browse files Browse the repository at this point in the history
  • Loading branch information
joeltg committed Jan 15, 2025
1 parent 833e058 commit 7412cc9
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 125 deletions.
29 changes: 11 additions & 18 deletions packages/modeldb-durable-objects/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,7 @@ export class ModelAPI {
readonly #primaryKeyName: string
columnNames: `"${string}"`[]

public constructor(
readonly db: SqlStorage,
readonly model: Model,
) {
public constructor(readonly db: SqlStorage, readonly model: Model) {
// in the cloudflare runtime, `this` cannot be used when assigning default values to private properties
this.#table = model.name
this.#properties = Object.fromEntries(model.properties.map((property) => [property.name, property]))
Expand Down Expand Up @@ -336,18 +333,17 @@ export class ModelAPI {
if (query.orderBy !== undefined) {
const orders = Object.entries(query.orderBy)
assert(orders.length === 1, "cannot order by multiple properties at once")
const [[name, direction]] = orders
const property = this.#properties[name]
assert(property !== undefined, "property not found")
assert(
property.kind === "primary" || property.kind === "primitive" || property.kind === "reference",
"cannot order by relation properties",
)
const [[indexName, direction]] = orders
const index = indexName.split("/")

assert(!index.some((name) => this.#properties[name]?.kind === "relation"), "cannot order by relation properties")

if (direction === "asc") {
sql.push(`ORDER BY "${name}" ASC`)
const orders = index.map((name) => `"${name}" ASC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else if (direction === "desc") {
sql.push(`ORDER BY "${name}" DESC`)
const orders = index.map((name) => `"${name}" DESC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else {
throw new Error("invalid orderBy direction")
}
Expand Down Expand Up @@ -601,14 +597,11 @@ export class RelationAPI {
readonly #insert: Method<{ _source: string; _target: string }>
readonly #delete: Method<{ _source: string }>

public constructor(
readonly db: SqlStorage,
readonly relation: Relation,
) {
public constructor(readonly db: SqlStorage, readonly relation: Relation) {
this.table = `${relation.source}/${relation.property}`
this.sourceIndex = `${relation.source}/${relation.property}/source`
this.targetIndex = `${relation.source}/${relation.property}/target`

const columns = [`_source TEXT NOT NULL`, `_target TEXT NOT NULL`]
db.exec(`CREATE TABLE IF NOT EXISTS "${this.table}" (${columns.join(", ")})`)

Expand Down
23 changes: 10 additions & 13 deletions packages/modeldb-pg/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,18 +388,18 @@ export class ModelAPI {
if (query.orderBy !== undefined) {
const orders = Object.entries(query.orderBy)
assert(orders.length === 1, "cannot order by multiple properties at once")
const [[name, direction]] = orders
const property = this.#properties[name]
assert(property !== undefined, "property not found")
assert(
property.kind === "primary" || property.kind === "primitive" || property.kind === "reference",
"cannot order by relation properties",
)

const [[indexName, direction]] = orders
const index = indexName.split("/")

assert(!index.some((name) => this.#properties[name]?.kind === "relation"), "cannot order by relation properties")

if (direction === "asc") {
sql.push(`ORDER BY "${name}" ASC NULLS FIRST`)
const orders = index.map((name) => `"${name}" ASC NULLS FIRST`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else if (direction === "desc") {
sql.push(`ORDER BY "${name}" DESC NULLS LAST`)
const orders = index.map((name) => `"${name}" DESC NULLS LAST`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else {
throw new Error("invalid orderBy direction")
}
Expand Down Expand Up @@ -663,10 +663,7 @@ export class RelationAPI {
public readonly sourceIndex: string
public readonly targetIndex: string

public constructor(
readonly client: pg.Client,
readonly relation: Relation,
) {
public constructor(readonly client: pg.Client, readonly relation: Relation) {
this.table = `${relation.source}/${relation.property}`
this.sourceIndex = `${relation.source}/${relation.property}/source`
this.targetIndex = `${relation.source}/${relation.property}/target`
Expand Down
31 changes: 12 additions & 19 deletions packages/modeldb-sqlite-expo/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,11 @@ export class ModelAPI {

columnNames: `"${string}"`[]

public constructor(
readonly db: SQLiteDatabase,
readonly model: Model,
) {
public constructor(readonly db: SQLiteDatabase, readonly model: Model) {
this.#table = model.name
this.#params = {}
this.#properties = Object.fromEntries(model.properties.map((property) => [property.name, property]))

const columns: string[] = []
this.columnNames = [] // quoted column names for non-relation properties
const columnParams: `:p${string}`[] = [] // query params for non-relation properties
Expand Down Expand Up @@ -313,18 +310,17 @@ export class ModelAPI {
if (query.orderBy !== undefined) {
const orders = Object.entries(query.orderBy)
assert(orders.length === 1, "cannot order by multiple properties at once")
const [[name, direction]] = orders
const property = this.#properties[name]
assert(property !== undefined, "property not found")
assert(
property.kind === "primary" || property.kind === "primitive" || property.kind === "reference",
"cannot order by relation properties",
)
const [[indexName, direction]] = orders
const index = indexName.split("/")

assert(!index.some((name) => this.#properties[name]?.kind === "relation"), "cannot order by relation properties")

if (direction === "asc") {
sql.push(`ORDER BY "${name}" ASC`)
const orders = index.map((name) => `"${name}" ASC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else if (direction === "desc") {
sql.push(`ORDER BY "${name}" DESC`)
const orders = index.map((name) => `"${name}" DESC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else {
throw new Error("invalid orderBy direction")
}
Expand Down Expand Up @@ -592,14 +588,11 @@ export class RelationAPI {
readonly #insert: Method<{ _source: string; _target: string }>
readonly #delete: Method<{ _source: string }>

public constructor(
readonly db: SQLiteDatabase,
readonly relation: Relation,
) {
public constructor(readonly db: SQLiteDatabase, readonly relation: Relation) {
this.table = `${relation.source}/${relation.property}`
this.sourceIndex = `${relation.source}/${relation.property}/source`
this.targetIndex = `${relation.source}/${relation.property}/target`

const columns = [`_source TEXT NOT NULL`, `_target TEXT NOT NULL`]
db.execSync(`CREATE TABLE IF NOT EXISTS "${this.table}" (${columns.join(", ")})`)

Expand Down
27 changes: 10 additions & 17 deletions packages/modeldb-sqlite-wasm/src/ModelAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,7 @@ export class ModelAPI {

columnNames: string[]

public constructor(
readonly db: OpfsDatabase,
readonly model: Model,
) {
public constructor(readonly db: OpfsDatabase, readonly model: Model) {
this.#table = model.name
this.#params = {}
this.#properties = Object.fromEntries(model.properties.map((property) => [property.name, property]))
Expand Down Expand Up @@ -343,18 +340,17 @@ export class ModelAPI {
if (query.orderBy !== undefined) {
const orders = Object.entries(query.orderBy)
assert(orders.length === 1, "cannot order by multiple properties at once")
const [[name, direction]] = orders
const property = this.#properties[name]
assert(property !== undefined, "property not found")
assert(
property.kind === "primary" || property.kind === "primitive" || property.kind === "reference",
"cannot order by relation properties",
)
const [[indexName, direction]] = orders
const index = indexName.split("/")

assert(!index.some((name) => this.#properties[name]?.kind === "relation"), "cannot order by relation properties")

if (direction === "asc") {
sql.push(`ORDER BY "${name}" ASC`)
const orders = index.map((name) => `"${name}" ASC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else if (direction === "desc") {
sql.push(`ORDER BY "${name}" DESC`)
const orders = index.map((name) => `"${name}" DESC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else {
throw new Error("invalid orderBy direction")
}
Expand Down Expand Up @@ -621,10 +617,7 @@ export class RelationAPI {
readonly #insert: Method<{ _source: string; _target: string }>
readonly #delete: Method<{ _source: string }>

public constructor(
readonly db: OpfsDatabase,
readonly relation: Relation,
) {
public constructor(readonly db: OpfsDatabase, readonly relation: Relation) {
this.table = `${relation.source}/${relation.property}`
this.sourceIndex = `${relation.source}/${relation.property}/source`
this.targetIndex = `${relation.source}/${relation.property}/target`
Expand Down
27 changes: 10 additions & 17 deletions packages/modeldb-sqlite/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,7 @@ export class ModelAPI {

columnNames: `"${string}"`[]

public constructor(
readonly db: Database,
readonly model: Model,
) {
public constructor(readonly db: Database, readonly model: Model) {
this.#table = model.name
this.#params = {}
this.#properties = Object.fromEntries(model.properties.map((property) => [property.name, property]))
Expand Down Expand Up @@ -311,18 +308,17 @@ export class ModelAPI {
if (query.orderBy !== undefined) {
const orders = Object.entries(query.orderBy)
assert(orders.length === 1, "cannot order by multiple properties at once")
const [[name, direction]] = orders
const property = this.#properties[name]
assert(property !== undefined, "property not found")
assert(
property.kind === "primary" || property.kind === "primitive" || property.kind === "reference",
"cannot order by relation properties",
)
const [[indexName, direction]] = orders
const index = indexName.split("/")

assert(!index.some((name) => this.#properties[name]?.kind === "relation"), "cannot order by relation properties")

if (direction === "asc") {
sql.push(`ORDER BY "${name}" ASC`)
const orders = index.map((name) => `"${name}" ASC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else if (direction === "desc") {
sql.push(`ORDER BY "${name}" DESC`)
const orders = index.map((name) => `"${name}" DESC`).join(", ")
sql.push(`ORDER BY ${orders}`)
} else {
throw new Error("invalid orderBy direction")
}
Expand Down Expand Up @@ -590,10 +586,7 @@ export class RelationAPI {
readonly #insert: Method<{ _source: string; _target: string }>
readonly #delete: Method<{ _source: string }>

public constructor(
readonly db: Database,
readonly relation: Relation,
) {
public constructor(readonly db: Database, readonly relation: Relation) {
this.table = `${relation.source}/${relation.property}`
this.sourceIndex = `${relation.source}/${relation.property}/source`
this.targetIndex = `${relation.source}/${relation.property}/target`
Expand Down
86 changes: 45 additions & 41 deletions packages/modeldb/test/indexes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,44 +91,48 @@ testOnModelDB("query (indexed order by)", async (t, openDB) => {
])
})

testOnModelDB("composite index", async (t, openDB) => {
const db = await openDB(t, {
user: {
id: "primary",
value: "integer",
$indexes: ["value/id"],
},
})

await db.set("user", { id: "a", value: 0 })
await db.set("user", { id: "b", value: 1 })
await db.set("user", { id: "c", value: 1 })
await db.set("user", { id: "d", value: 1 })
await db.set("user", { id: "e", value: 1 })
await db.set("user", { id: "f", value: 4 })

t.deepEqual(
await db.query("user", {
orderBy: { "value/id": "asc" },
where: { value: 1 },
}),
[
{ id: "b", value: 1 },
{ id: "c", value: 1 },
{ id: "d", value: 1 },
{ id: "e", value: 1 },
],
)

t.deepEqual(
await db.query("user", {
orderBy: { "value/id": "asc" },
where: { value: 1, id: { gt: "a", lte: "d" } },
}),
[
{ id: "b", value: 1 },
{ id: "c", value: 1 },
{ id: "d", value: 1 },
],
)
})
testOnModelDB(
"composite index",
async (t, openDB) => {
const db = await openDB(t, {
user: {
id: "primary",
value: "integer",
$indexes: ["value/id"],
},
})

await db.set("user", { id: "a", value: 0 })
await db.set("user", { id: "b", value: 1 })
await db.set("user", { id: "c", value: 1 })
await db.set("user", { id: "d", value: 1 })
await db.set("user", { id: "e", value: 1 })
await db.set("user", { id: "f", value: 4 })

t.deepEqual(
await db.query("user", {
orderBy: { "value/id": "asc" },
where: { value: 1 },
}),
[
{ id: "b", value: 1 },
{ id: "c", value: 1 },
{ id: "d", value: 1 },
{ id: "e", value: 1 },
],
)

t.deepEqual(
await db.query("user", {
orderBy: { "value/id": "asc" },
where: { value: 1, id: { gt: "a", lte: "d" } },
}),
[
{ id: "b", value: 1 },
{ id: "c", value: 1 },
{ id: "d", value: 1 },
],
)
},
// { sqlite: true },
)

0 comments on commit 7412cc9

Please sign in to comment.