forked from fusion32/tibia-querymanager
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema.sql
More file actions
333 lines (301 loc) · 11 KB
/
schema.sql
File metadata and controls
333 lines (301 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
-- NOTE(fusion): Everything is inside a transaction to avoid errors from leaving
-- the database in some partial state. Also, notice we don't use `IF NOT EXISTS`
-- because it could also leave the database in a bad state if for example, some
-- old version of a table already existed.
--==============================================================================
BEGIN;
-- IMPORTANT(fusion): PosgreSQL doesn't have a defined `NOCASE` collation like
-- SQLite and doesn't use a default case-insensitive collation like MySQL. The
-- simplest alternative is to create a custom non-deterministic ICU collation.
-- ICU as a collation provider is supported with PostgreSQL 10+, while
-- non-deterministic collations are supported with PostgreSQL 12+. Either way,
-- it should be safe to assume that it's widely supported, given that version
-- 12 is roughly 6 years old and well past its end-of-life.
-- There are also drawbacks from using these types of collations but I won't
-- go into details. For more information see:
-- https://www.postgresql.org/docs/current/collation.html
CREATE COLLATION NOCASE (
provider = icu,
deterministic = false,
locale = 'und-u-ks-level2'
);
-- IMPORTANT(fusion): Since we're already assuming PostgreSQL 12+ for collations,
-- it's probably a good idea to use IDENTITY columns instead of SERIAL. They're
-- supported with PostgreSQL 10+ and are supposed so fix some shortcommings of
-- SERIAL.
-- TODO(fusion): SQLite tables didn't use foreign key constraints so I'm also not
-- using them here. It might be a good idea for consistency, but it's not a hard
-- requirement.
-- NOTE(fusion): A table with schema information. It currently only contains the
-- schema version which is checked at startup against `POSTGRESQL_SCHEMA_VERSION`,
-- defined in `database_postgres.cc`. The query manager will only startup if the
-- versions match.
CREATE TABLE SchemaInfo (
Key TEXT NOT NULL COLLATE NOCASE,
Value TEXT NOT NULL,
PRIMARY KEY (Key)
);
INSERT INTO SchemaInfo (Key, Value) VALUES ('VERSION', '1');
-- Primary Tables
--==============================================================================
CREATE TABLE Worlds (
WorldID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
Name TEXT NOT NULL COLLATE NOCASE,
Type SMALLINT NOT NULL,
RebootTime SMALLINT NOT NULL,
Host TEXT NOT NULL,
Port INTEGER NOT NULL,
MaxPlayers SMALLINT NOT NULL,
PremiumPlayerBuffer SMALLINT NOT NULL,
MaxNewbies SMALLINT NOT NULL,
PremiumNewbieBuffer SMALLINT NOT NULL,
OnlinePeak SMALLINT NOT NULL DEFAULT 0,
OnlinePeakTimestamp TIMESTAMPTZ NOT NULL DEFAULT 'epoch',
LastStartup TIMESTAMPTZ NOT NULL DEFAULT 'epoch',
LastShutdown TIMESTAMPTZ NOT NULL DEFAULT 'epoch',
PRIMARY KEY (WorldID),
UNIQUE (Name)
);
CREATE TABLE Accounts (
AccountID INTEGER NOT NULL,
Email TEXT NOT NULL COLLATE NOCASE,
Auth BYTEA NOT NULL,
PremiumEnd TIMESTAMPTZ NOT NULL DEFAULT 'epoch',
PendingPremiumDays SMALLINT NOT NULL DEFAULT 0,
Deleted BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (AccountID),
UNIQUE (Email)
);
CREATE TABLE Characters (
WorldID INTEGER NOT NULL,
CharacterID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
AccountID INTEGER NOT NULL,
Name TEXT NOT NULL COLLATE NOCASE,
Sex SMALLINT NOT NULL,
Level SMALLINT NOT NULL DEFAULT 0,
Profession TEXT NOT NULL DEFAULT '',
Residence TEXT NOT NULL DEFAULT '',
LastLoginTime TIMESTAMPTZ NOT NULL DEFAULT 'epoch',
TutorActivities INTEGER NOT NULL DEFAULT 0,
IsOnline SMALLINT NOT NULL DEFAULT 0,
Deleted BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (CharacterID),
UNIQUE (Name)
);
CREATE INDEX CharactersWorldIndex ON Characters(WorldID, IsOnline);
CREATE INDEX CharactersAccountIndex ON Characters(AccountID, IsOnline);
-- NOTE(fusion): It seems `RIGHT` is a reserved keyword and trying to use
-- it as a column name will generate an error.
CREATE TABLE CharacterRights (
CharacterID INTEGER NOT NULL,
Name TEXT NOT NULL COLLATE NOCASE,
PRIMARY KEY(CharacterID, Name)
);
CREATE TABLE CharacterDeaths (
CharacterID INTEGER NOT NULL,
Level SMALLINT NOT NULL,
OffenderID INTEGER NOT NULL,
Remark TEXT NOT NULL,
Unjustified BOOLEAN NOT NULL,
Timestamp TIMESTAMPTZ NOT NULL
);
CREATE INDEX CharacterDeathsCharacterIndex ON CharacterDeaths(CharacterID, Timestamp);
CREATE INDEX CharacterDeathsOffenderIndex ON CharacterDeaths(OffenderID, Timestamp);
CREATE INDEX CharacterDeathsTimeIndex ON CharacterDeaths(Timestamp);
CREATE TABLE Buddies (
WorldID INTEGER NOT NULL,
AccountID INTEGER NOT NULL,
BuddyID INTEGER NOT NULL,
PRIMARY KEY (WorldID, AccountID, BuddyID)
);
CREATE TABLE WorldInvitations (
WorldID INTEGER NOT NULL,
CharacterID INTEGER NOT NULL,
PRIMARY KEY (WorldID, CharacterID)
);
CREATE TABLE LoginAttempts (
AccountID INTEGER NOT NULL,
IPAddress INET NOT NULL,
Timestamp TIMESTAMPTZ NOT NULL,
Failed BOOLEAN NOT NULL
);
CREATE INDEX LoginAttemptsAccountIndex ON LoginAttempts(AccountID, Timestamp);
CREATE INDEX LoginAttemptsAddressIndex ON LoginAttempts(IPAddress, Timestamp);
-- Guild Tables
--==============================================================================
CREATE TABLE Guilds (
WorldID INTEGER NOT NULL,
GuildID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
Name TEXT NOT NULL COLLATE NOCASE,
LeaderID INTEGER NOT NULL,
Created TIMESTAMPTZ NOT NULL,
PRIMARY KEY (GuildID),
UNIQUE (Name),
UNIQUE (LeaderID)
);
CREATE TABLE GuildRanks (
GuildID INTEGER NOT NULL,
Rank SMALLINT NOT NULL,
Name TEXT NOT NULL,
PRIMARY KEY (GuildID, Rank)
);
CREATE TABLE GuildMembers (
GuildID INTEGER NOT NULL,
CharacterID INTEGER NOT NULL,
Rank SMALLINT NOT NULL,
Title TEXT NOT NULL,
Joined TIMESTAMPTZ NOT NULL,
PRIMARY KEY (CharacterID)
);
CREATE INDEX GuildMembersGuildIndex ON GuildMembers(GuildID, Rank);
CREATE TABLE GuildInvites (
GuildID INTEGER NOT NULL,
CharacterID INTEGER NOT NULL,
RecruiterID INTEGER NOT NULL,
Timestamp TIMESTAMPTZ NOT NULL,
PRIMARY KEY (GuildID, CharacterID)
);
CREATE INDEX GuildInvitesCharacterIndex ON GuildInvites(CharacterID);
CREATE INDEX GuildInvitesRecruiterIndex ON GuildInvites(RecruiterID);
-- House Tables
--==============================================================================
CREATE TABLE Houses (
WorldID INTEGER NOT NULL,
HouseID INTEGER NOT NULL,
Name TEXT NOT NULL,
Rent INTEGER NOT NULL,
Description TEXT NOT NULL,
Size INTEGER NOT NULL,
PositionX INTEGER NOT NULL,
PositionY INTEGER NOT NULL,
PositionZ INTEGER NOT NULL,
Town TEXT NOT NULL,
GuildHouse BOOLEAN NOT NULL,
PRIMARY KEY (WorldID, HouseID)
);
CREATE TABLE HouseOwners (
WorldID INTEGER NOT NULL,
HouseID INTEGER NOT NULL,
OwnerID INTEGER NOT NULL,
PaidUntil TIMESTAMPTZ NOT NULL,
PRIMARY KEY (WorldID, HouseID)
);
-- NOTE(fusion): Auctions with a NULL `FinishTime` aren't active, to avoid running
-- multiple times with no actual bidder. It should be set after the first bid along
-- with `BidderID` and `BidAmount`.
CREATE TABLE HouseAuctions (
WorldID INTEGER NOT NULL,
HouseID INTEGER NOT NULL,
BidderID INTEGER DEFAULT NULL,
BidAmount INTEGER DEFAULT NULL,
FinishTime TIMESTAMPTZ DEFAULT NULL,
PRIMARY KEY (WorldID, HouseID)
);
CREATE TABLE HouseTransfers (
WorldID INTEGER NOT NULL,
HouseID INTEGER NOT NULL,
NewOwnerID INTEGER NOT NULL,
Price INTEGER NOT NULL,
PRIMARY KEY (WorldID, HouseID)
);
CREATE TABLE HouseAuctionExclusions (
CharacterID INTEGER NOT NULL,
Issued TIMESTAMPTZ NOT NULL,
Until TIMESTAMPTZ NOT NULL,
BanishmentID INTEGER NOT NULL
);
CREATE INDEX HouseAuctionExclusionsIndex ON HouseAuctionExclusions(CharacterID, Until);
CREATE TABLE HouseAssignments (
WorldID INTEGER NOT NULL,
HouseID INTEGER NOT NULL,
OwnerID INTEGER NOT NULL,
Price INTEGER NOT NULL,
Timestamp TIMESTAMPTZ NOT NULL
);
CREATE INDEX HouseAssignmentsHouseIndex ON HouseAssignments(WorldID, HouseID);
CREATE INDEX HouseAssignmentsTimeIndex ON HouseAssignments(WorldID, Timestamp);
CREATE INDEX HouseAssignmentsOwnerIndex ON HouseAssignments(OwnerID);
-- Banishment Tables
--==============================================================================
CREATE TABLE Banishments (
BanishmentID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY,
AccountID INTEGER NOT NULL,
IPAddress INET NOT NULL,
GamemasterID INTEGER NOT NULL,
Reason TEXT NOT NULL,
Comment TEXT NOT NULL,
FinalWarning BOOLEAN NOT NULL,
Issued TIMESTAMPTZ NOT NULL,
Until TIMESTAMPTZ NOT NULL,
PRIMARY KEY (BanishmentID)
);
CREATE INDEX BanishmentsAccountIndex ON Banishments(AccountID, Until, FinalWarning);
CREATE TABLE IPBanishments (
CharacterID INTEGER NOT NULL,
IPAddress INET NOT NULL,
GamemasterID INTEGER NOT NULL,
Reason TEXT NOT NULL,
Comment TEXT NOT NULL,
Issued TIMESTAMPTZ NOT NULL,
Until TIMESTAMPTZ NOT NULL
);
CREATE INDEX IPBanishmentsAddressIndex ON IPBanishments(IPAddress);
CREATE INDEX IPBanishmentsCharacterIndex ON IPBanishments(CharacterID);
CREATE TABLE Namelocks (
CharacterID INTEGER NOT NULL,
IPAddress INET NOT NULL,
GamemasterID INTEGER NOT NULL,
Reason TEXT NOT NULL,
Comment TEXT NOT NULL,
Attempts INTEGER NOT NULL DEFAULT 0,
Approved BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (CharacterID)
);
CREATE TABLE Notations (
CharacterID INTEGER NOT NULL,
IPAddress INET NOT NULL,
GamemasterID INTEGER NOT NULL,
Reason TEXT NOT NULL,
Comment TEXT NOT NULL
);
CREATE INDEX NotationsCharacterIndex ON Notations(CharacterID);
CREATE TABLE Statements (
WorldID INTEGER NOT NULL,
Timestamp TIMESTAMPTZ NOT NULL,
StatementID INTEGER NOT NULL,
CharacterID INTEGER NOT NULL,
Channel TEXT NOT NULL,
Text TEXT NOT NULL,
PRIMARY KEY (WorldID, Timestamp, StatementID)
);
CREATE INDEX StatementsCharacterIndex ON Statements(CharacterID, Timestamp);
CREATE TABLE ReportedStatements (
WorldID INTEGER NOT NULL,
Timestamp TIMESTAMPTZ NOT NULL,
StatementID INTEGER NOT NULL,
CharacterID INTEGER NOT NULL,
BanishmentID INTEGER NOT NULL,
ReporterID INTEGER NOT NULL,
Reason TEXT NOT NULL,
Comment TEXT NOT NULL,
PRIMARY KEY (WorldID, Timestamp, StatementID)
);
CREATE INDEX ReportedStatementsCharacterIndex ON ReportedStatements(CharacterID, Timestamp);
CREATE INDEX ReportedStatementsBanishmentIndex ON ReportedStatements(BanishmentID);
-- Info Tables
--==============================================================================
CREATE TABLE KillStatistics (
WorldID INTEGER NOT NULL,
RaceName TEXT NOT NULL COLLATE NOCASE,
TimesKilled INTEGER NOT NULL,
PlayersKilled INTEGER NOT NULL,
PRIMARY KEY (WorldID, RaceName)
);
CREATE TABLE OnlineCharacters (
WorldID INTEGER NOT NULL,
Name TEXT NOT NULL COLLATE NOCASE,
Level SMALLINT NOT NULL,
Profession TEXT NOT NULL,
PRIMARY KEY (WorldID, Name)
);
COMMIT;