# Database schema management in Quassel IRC

## Introduction

Quassel IRC supports two database backends for storing chat history, user
sessions, etc:

* [PostgreSQL][postgres-home], in the [`PostgreSQL`](PostgreSQL/) folder
* [SQLite][sqlite-home], in the [`SQLite`](SQLite/) folder

For the most part, Quassel strives to have consistency between the two
backends, offering the same user experience and similar `.sql` query files,
though there are times when one database supports features that another does
not.

All of the current schema version queries are stored at the top level of the
backend subdirectory, with schema upgrade queries stored in
`[backend]/version/##` folders.

At compile time, the build system generates and reads a Qt resource file to
know which queries to include.  For past Quassel contributors, this replaces
the classic `sql.qrc` file and `updateSQLResource.sh` script.

## Managing the database outside of Quassel

Whenever possible, it's recommended to use Quassel itself to make changes, such
as deleting chats or changing user passwords.

However, some tasks aren't yet possible in Quassel, and you should
[read the documentation on managing the database][docs-wiki-db-manage] if the
need arises.

## Making changes to the database
### Changes to existing queries, no new/moved files

If you're only modifying existing queries in a way that does **not** require
any schema changes (*e.g. `ALTER TABLE`, `CREATE TABLE`, `CREATE INDEX`
statements*), you can just modify the `.sql` files in the appropriate backends.

However, any database schema changes must fulfill a number of requirements.

### New queries, schema changes, etc

> One does not simply 'upgrade the schema'...

When changing Quassel's database schema, you need to follow several steps to
handle all cases (*upgrade, Postgres migration, etc*).  Some of these steps may
not apply to all schema upgrades.

1.  **Run [`upgradeSchema.sh`][file-sh-upgradeschema] script on *both*
PostgreSQL and SQLite directories**

Make sure you're on the Git branch you want to modify

*Example:*
```sh
./upgradeSchema.sh "PostgreSQL"
./upgradeSchema.sh "SQLite"
```

Or, you can manually create new `[backend]/version/##` folders for both
`PostgreSQL` and `SQLite`.  Pick the next higher number from the largest
version number for each (*the exact version numbers will usually differ; that's
fine*).

2.  **Modify queries and setup scripts to handle your change**

The specifics depend on your change; in general, you'll want to modify any
query files that select from or insert to a modified table.  Then, modify the
`setup_###_[...].sql` files to include your changes on new database installs.

> *Example: modifying the `ircserver` table to add column `test`*
>
> Modify all query/setup `.sql` files that touch the `ircserver` table for
> *both* `PostgreSQL` and `SQLite`.

3.  **Create upgrade scripts for *both* PostgreSQL and SQLite**

These should go in the newest `[backend]/version/##` folders that were created
in Step 1.

Outside of special circumstances, do **not** modify the files in lower-numbered
version folders.  Existing Quassel cores have already run those statements and
will not run them if changed.

> *Example: modifying the `ircserver` table to add column `test`*
>
> Add the file `upgrade_000_alter_ircserver_add_test.sql` with contents:
>
> ```sql
> ALTER TABLE ircserver
> ADD COLUMN test [additional column-specific details]
> ```

4.  **Update the pair of migration scripts for moving from SQLite to
PostgreSQL**

For any table changes, you'll need to update the relevant
`SQLite/migrate_read_[table].sql` to read the existing data, and the
`PostgreSQL/migrate_write_[table].sql` to insert this data.

> *Example: modifying the `ircserver` table to add column `test`*
>
> Modify `SQLite/migrate_read_ircserver.sql` to select from new column
>
> Modify `PostgreSQL/migrate_write_ircserver.sql` to insert to new column

5.  **Update the migration logic in
[`src/core/abstractsqlstorage.cpp`][file-cpp-abstract], and the storage
backends [`postgresqlstorage.cpp`][file-cpp-postgres] and
[`sqlitestorage.cpp`][file-cpp-sqlite]**

Make sure to read the data from SQLite in the right types and order, then write
it to PostgreSQL also with the right types and order.  SQLite and PostgreSQL
may use different database representations of certain types.

> *Example: modifying the `ircserver` table to add column `test`*
>
> Modify struct `IrcServerMO` in [`abstractsqlstorage.h`][file-h-abstract],
> adding an entry for `test` of the appropriate data-type.
>
> Modify `SqliteMigrationReader::readMo(IrcServerMO &ircserver)` in
> [`sqlitestorage.cpp`][file-cpp-sqlite] to read from the new column and store
> it in the migration object.  You may need to convert from SQLite's looser
> types.
>
> Modify `PostgreSqlMigrationWriter::writeMo(const IrcServerMO &ircserver)` in
> [`postgresqlstorage.cpp`][file-cpp-postgres] to write to the new column from
> the data in the migration object.

6.  **Update any affected queries in storage backends
[`postgresqlstorage.cpp`][file-cpp-postgres] and
[`sqlitestorage.cpp`][file-cpp-sqlite], and any related synchronized
`src/common` classes.**

> *Example: modifying the `ircserver` table to add column `test`*
>
> Update `network.h` to add new column to Server structure:
> ```diff
>    QString proxyPass;                                    // Existing code
> +  Typename test;                                        // New column 'test'
>    [...]
> -  Server() : port(6667), ..., proxyPort(8080) {}
> +  Server() : port(6667), ..., proxyPort(8080), test("defaultValue") {}
> ```
>
> Modify reading data in `[PostgreSql/Sqlite]Storage::networks(...)`:
> ```diff
>    server.proxyPass = serversQuery.value(10).toString(); // Existing code
> +  server.test = serversQuery.value(11).toType();        // New column 'test'
>    servers << server;                                    // Existing code
> ```
>
> Modify writing data in `[PostgreSql/Sqlite]Storage::bindServerInfo(...)`:
> ```diff
>    query.bindValue(":proxypass", server.proxyPass);      // Existing code
> +  query.bindValue(":test", server.test);                // New column 'test'
> ```

7.  **If protocol changed (*add a setting, etc*), add a new `Feature` flag**

Newer clients need to detect when they're on an older core to disable the
feature.  Use the `Feature` enum in [`quassel.h`][file-h-quassel].

In client-side code, test for a feature with...

```cpp
if (Client::isCoreFeatureEnabled(Quassel::Feature::FeatureName)) { ... }
```

Depending on the type of protocol change, you might also need to update
[`serializers.cpp`][file-cpp-serializers] and related files.

8.  **Test everything!  Upgrade, migrate, new setups, new client/old core,
old client/new core, etc.**

More specifically, you should try the following combinations, especially if you
change the protocol.  Check if any data or settings get lost or corrupted, or
if anything unusual shows up in the log.  Restart the Quassel core and client,
to check that data persists.

---

**Linux/macOS**

*Fresh configuration (initialize the database and settings from scratch)*

Test conditions  | Result  | Remarks
-----------------|---------|-------------
SQLite, new core, new client | ❓ **TODO** |
SQLite, new core, old client | ❓ **TODO** |
SQLite, old core, new client | ❓ **TODO** |
SQLite, new monolithic | ❓ **TODO** |
Postgres, new core, new client | ❓ **TODO** |
Postgres, new core, old client | ❓ **TODO** |
Postgres, old core, new client | ❓ **TODO** |

*Migration (set up SQLite, then `--select-backend PostgreSQL`)*

Test conditions  | Result  | Remarks
-----------------|---------|-------------
SQLite → Postgres, new core, new client | ❓ **TODO** |

*Upgrading existing (set up a copy from `master`, then upgrade to your branch)*

Test conditions  | Result  | Remarks
-----------------|---------|-------------
SQLite, old → new core | ❓ **TODO** |
SQLite, old monolithic → new monolithic | ❓ **TODO** |
Postgres, old → new core | ❓ **TODO** |

**Windows**

*Fresh configuration (initialize the database and settings from scratch)*

Test conditions  | Result  | Remarks
-----------------|---------|-------------
SQLite, new core, new client | ❓ **TODO** |
SQLite, new core, old client | ❓ **TODO** |
SQLite, old core, new client | ❓ **TODO** |
SQLite, new monolithic | ❓ **TODO** |

*Upgrading existing (set up a copy from `master`, then upgrade to your branch)*

Test conditions  | Result  | Remarks
-----------------|---------|-------------
SQLite, old → new core | ❓ **TODO** |
SQLite, old monolithic → new monolithic | ❓ **TODO** |

*If someone figures out how Postgres runs on Windows with Quassel, please
update this...*

---

Yes, this looks excessive, and maybe it is.  But it's easy to overlook
some minor typo that breaks the client/core for a certain configuration.
People may get unhappy and rioting might happen in the streets.  And we don't
want that, do we?

Thank you for reading this guide and good luck with your changes!

[postgres-home]: https://www.postgresql.org/
[sqlite-home]: https://sqlite.org/
[docs-wiki-db-manage]: https://bugs.quassel-irc.org/projects/quassel-irc/wiki/Database_Management
[file-cpp-abstract]: ../abstractsqlstorage.cpp
[file-h-abstract]: ../abstractsqlstorage.h
[file-cpp-postgres]: ../postgresqlstorage.cpp
[file-cpp-sqlite]: ../sqlitestorage.cpp
[file-sh-upgradeschema]: upgradeSchema.sh
[file-h-quassel]: ../../common/quassel.h
[file-cpp-serializers]: ../../common/serializers/serializers.cpp
