Skip to content

Commit 632bca3

Browse files
committed
Add gen prefixes to columns and add transactions
Added the "gen_" prefix to generated columns to convey that these are basically readonly values. Also, being able to make generated columns from generated columns is amazing. And switching setup() and storeChunkData() to transactions.
1 parent 26bae6b commit 632bca3

File tree

1 file changed

+82
-78
lines changed

1 file changed

+82
-78
lines changed

server/src/database.ts

Lines changed: 82 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ export interface Database {
1818
world: string;
1919
chunk_x: number;
2020
chunk_z: number;
21-
region_x: kysely.Generated<number>;
22-
region_z: kysely.Generated<number>;
23-
region_coord: kysely.Generated<string>;
21+
gen_region_x: kysely.Generated<number>;
22+
gen_region_z: kysely.Generated<number>;
23+
gen_region_coord: kysely.Generated<string>;
2424
uuid: string;
2525
ts: number;
2626
hash: Buffer;
@@ -43,62 +43,71 @@ export function get() {
4343

4444
export async function setup() {
4545
await get()
46-
.schema.createTable("chunk_data")
47-
.ifNotExists()
48-
.addColumn("hash", "blob", (col) => col.notNull().primaryKey())
49-
.addColumn("version", "integer", (col) => col.notNull())
50-
.addColumn("data", "blob", (col) => col.notNull())
51-
.execute();
52-
await get()
53-
.schema.createTable("player_chunk")
54-
.ifNotExists()
55-
.addColumn("world", "text", (col) => col.notNull())
56-
.addColumn("chunk_x", "integer", (col) => col.notNull())
57-
.addColumn("chunk_z", "integer", (col) => col.notNull())
58-
.addColumn("region_x", "integer", (col) =>
59-
col
60-
.generatedAlwaysAs(kysely.sql<number>`floor(chunk_x / 32.0)`)
61-
.notNull(),
62-
)
63-
.addColumn("region_z", "integer", (col) =>
64-
col
65-
.generatedAlwaysAs(kysely.sql<number>`floor(chunk_z / 32.0)`)
66-
.notNull(),
67-
)
68-
.addColumn("region_coord", "text", (col) => {
69-
return col
70-
.generatedAlwaysAs(kysely.sql<string>`cast(floor(chunk_x / 32.0) as int) || '_' || cast(floor(chunk_z / 32.0) as int)`)
71-
.notNull();
72-
})
73-
.addColumn("uuid", "text", (col) => col.notNull())
74-
.addColumn("ts", "bigint", (col) => col.notNull())
75-
.addColumn("hash", "blob", (col) => col.notNull())
76-
.addPrimaryKeyConstraint("PK_coords_and_player", [
77-
"world",
78-
"chunk_x",
79-
"chunk_z",
80-
"uuid",
81-
])
82-
.addForeignKeyConstraint(
83-
"FK_chunk_ref",
84-
["hash"],
85-
"chunk_data",
86-
["hash"],
87-
(fk) => fk.onUpdate("no action").onDelete("no action"),
88-
)
89-
.execute();
46+
.transaction()
47+
.execute(async (db) => {
48+
await db.schema
49+
.createTable("chunk_data")
50+
.ifNotExists()
51+
.addColumn("hash", "blob", (col) => col.notNull().primaryKey())
52+
.addColumn("version", "integer", (col) => col.notNull())
53+
.addColumn("data", "blob", (col) => col.notNull())
54+
.execute();
55+
await db.schema
56+
.createTable("player_chunk")
57+
.ifNotExists()
58+
.addColumn("world", "text", (col) => col.notNull())
59+
.addColumn("chunk_x", "integer", (col) => col.notNull())
60+
.addColumn("chunk_z", "integer", (col) => col.notNull())
61+
.addColumn("gen_region_x", "integer", (col) =>
62+
col
63+
.generatedAlwaysAs(
64+
kysely.sql<number>`floor(chunk_x / 32.0)`,
65+
)
66+
.notNull(),
67+
)
68+
.addColumn("gen_region_z", "integer", (col) =>
69+
col
70+
.generatedAlwaysAs(
71+
kysely.sql<number>`floor(chunk_z / 32.0)`,
72+
)
73+
.notNull(),
74+
)
75+
.addColumn("gen_region_coord", "text", (col) => {
76+
return col
77+
.generatedAlwaysAs(
78+
kysely.sql<string>`gen_region_x || '_' || gen_region_z`,
79+
)
80+
.notNull();
81+
})
82+
.addColumn("uuid", "text", (col) => col.notNull())
83+
.addColumn("ts", "bigint", (col) => col.notNull())
84+
.addColumn("hash", "blob", (col) => col.notNull())
85+
.addPrimaryKeyConstraint("PK_coords_and_player", [
86+
"world",
87+
"chunk_x",
88+
"chunk_z",
89+
"uuid",
90+
])
91+
.addForeignKeyConstraint(
92+
"FK_chunk_ref",
93+
["hash"],
94+
"chunk_data",
95+
["hash"],
96+
(fk) => fk.onUpdate("no action").onDelete("no action"),
97+
)
98+
.execute();
99+
});
90100
}
91101

92102
/**
93-
* Converts the entire database of player chunks into regions, with each region
94-
* having the highest (aka newest) timestamp.
103+
* Gets the timestamps for ALL regions stored.
95104
*/
96105
export async function getRegionTimestamps(dimension: string) {
97106
return await get()
98107
.selectFrom("player_chunk")
99108
.select([
100-
"region_x as regionX",
101-
"region_z as regionZ",
109+
"gen_region_x as regionX",
110+
"gen_region_z as regionZ",
102111
(eb) => eb.fn.max("ts").as("timestamp"),
103112
])
104113
.where("world", "=", dimension)
@@ -107,9 +116,6 @@ export async function getRegionTimestamps(dimension: string) {
107116
.execute();
108117
}
109118

110-
/**
111-
* Converts an array of region coords into an array of timestamped chunk coords.
112-
*/
113119
export async function getChunkTimestamps(dimension: string, regions: Pos2D[]) {
114120
return await get()
115121
.selectFrom("player_chunk")
@@ -119,7 +125,7 @@ export async function getChunkTimestamps(dimension: string, regions: Pos2D[]) {
119125
(eb) => eb.fn.max("ts").as("timestamp"),
120126
])
121127
.where(
122-
"region_coord",
128+
"gen_region_coord",
123129
"in",
124130
regions.map((region) => region.x + "_" + region.z),
125131
)
@@ -171,21 +177,25 @@ export async function storeChunkData(
171177
data: Buffer,
172178
) {
173179
await get()
174-
.insertInto("chunk_data")
175-
.values({ hash, version, data })
176-
.onConflict((oc) => oc.column("hash").doNothing())
177-
.execute();
178-
await get()
179-
.replaceInto("player_chunk")
180-
.values({
181-
world: dimension,
182-
chunk_x: chunkX,
183-
chunk_z: chunkZ,
184-
uuid,
185-
ts: timestamp,
186-
hash,
187-
})
188-
.execute();
180+
.transaction()
181+
.execute(async (db) => {
182+
await db
183+
.insertInto("chunk_data")
184+
.values({ hash, version, data })
185+
.onConflict((oc) => oc.column("hash").doNothing())
186+
.execute();
187+
await db
188+
.replaceInto("player_chunk")
189+
.values({
190+
world: dimension,
191+
chunk_x: chunkX,
192+
chunk_z: chunkZ,
193+
uuid,
194+
ts: timestamp,
195+
hash,
196+
})
197+
.execute();
198+
});
189199
}
190200

191201
/**
@@ -196,10 +206,6 @@ export async function getRegionChunks(
196206
regionX: number,
197207
regionZ: number,
198208
) {
199-
const minChunkX = regionX << 4,
200-
maxChunkX = minChunkX + 16;
201-
const minChunkZ = regionZ << 4,
202-
maxChunkZ = minChunkZ + 16;
203209
return await get()
204210
.selectFrom("player_chunk")
205211
.innerJoin("chunk_data", "chunk_data.hash", "player_chunk.hash")
@@ -211,10 +217,8 @@ export async function getRegionChunks(
211217
"chunk_data.data as data",
212218
])
213219
.where("player_chunk.world", "=", dimension)
214-
.where("player_chunk.chunk_x", ">=", minChunkX)
215-
.where("player_chunk.chunk_x", "<", maxChunkX)
216-
.where("player_chunk.chunk_z", ">=", minChunkZ)
217-
.where("player_chunk.chunk_z", "<", maxChunkZ)
220+
.where("player_chunk.gen_region_x", "=", regionX)
221+
.where("player_chunk.gen_region_z", "=", regionZ)
218222
.groupBy(["chunk_x", "chunk_z", "version", "data"])
219223
.orderBy("player_chunk.ts", "desc")
220224
.execute();

0 commit comments

Comments
 (0)