database.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. # Copyright 2014-2016 OpenMarket Ltd
  2. # Copyright 2020 The Matrix.org Foundation C.I.C.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. import os
  17. from synapse.config._base import Config, ConfigError
  18. logger = logging.getLogger(__name__)
  19. NON_SQLITE_DATABASE_PATH_WARNING = """\
  20. Ignoring 'database_path' setting: not using a sqlite3 database.
  21. --------------------------------------------------------------------------------
  22. """
  23. DEFAULT_CONFIG = """\
  24. ## Database ##
  25. # The 'database' setting defines the database that synapse uses to store all of
  26. # its data.
  27. #
  28. # 'name' gives the database engine to use: either 'sqlite3' (for SQLite) or
  29. # 'psycopg2' (for PostgreSQL).
  30. #
  31. # 'txn_limit' gives the maximum number of transactions to run per connection
  32. # before reconnecting. Defaults to 0, which means no limit.
  33. #
  34. # 'args' gives options which are passed through to the database engine,
  35. # except for options starting 'cp_', which are used to configure the Twisted
  36. # connection pool. For a reference to valid arguments, see:
  37. # * for sqlite: https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
  38. # * for postgres: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
  39. # * for the connection pool: https://twistedmatrix.com/documents/current/api/twisted.enterprise.adbapi.ConnectionPool.html#__init__
  40. #
  41. #
  42. # Example SQLite configuration:
  43. #
  44. #database:
  45. # name: sqlite3
  46. # args:
  47. # database: /path/to/homeserver.db
  48. #
  49. #
  50. # Example Postgres configuration:
  51. #
  52. #database:
  53. # name: psycopg2
  54. # txn_limit: 10000
  55. # args:
  56. # user: synapse_user
  57. # password: secretpassword
  58. # database: synapse
  59. # host: localhost
  60. # port: 5432
  61. # cp_min: 5
  62. # cp_max: 10
  63. #
  64. # For more information on using Synapse with Postgres,
  65. # see https://matrix-org.github.io/synapse/latest/postgres.html.
  66. #
  67. database:
  68. name: sqlite3
  69. args:
  70. database: %(database_path)s
  71. """
  72. class DatabaseConnectionConfig:
  73. """Contains the connection config for a particular database.
  74. Args:
  75. name: A label for the database, used for logging.
  76. db_config: The config for a particular database, as per `database`
  77. section of main config. Has three fields: `name` for database
  78. module name, `args` for the args to give to the database
  79. connector, and optional `data_stores` that is a list of stores to
  80. provision on this database (defaulting to all).
  81. """
  82. def __init__(self, name: str, db_config: dict):
  83. db_engine = db_config.get("name", "sqlite3")
  84. if db_engine not in ("sqlite3", "psycopg2"):
  85. raise ConfigError("Unsupported database type %r" % (db_engine,))
  86. if db_engine == "sqlite3":
  87. db_config.setdefault("args", {}).update(
  88. {"cp_min": 1, "cp_max": 1, "check_same_thread": False}
  89. )
  90. data_stores = db_config.get("data_stores")
  91. if data_stores is None:
  92. data_stores = ["main", "state"]
  93. self.name = name
  94. self.config = db_config
  95. # The `data_stores` config is actually talking about `databases` (we
  96. # changed the name).
  97. self.databases = data_stores
  98. class DatabaseConfig(Config):
  99. section = "database"
  100. def __init__(self, *args, **kwargs):
  101. super().__init__(*args, **kwargs)
  102. self.databases = []
  103. def read_config(self, config, **kwargs):
  104. # We *experimentally* support specifying multiple databases via the
  105. # `databases` key. This is a map from a label to database config in the
  106. # same format as the `database` config option, plus an extra
  107. # `data_stores` key to specify which data store goes where. For example:
  108. #
  109. # databases:
  110. # master:
  111. # name: psycopg2
  112. # data_stores: ["main"]
  113. # args: {}
  114. # state:
  115. # name: psycopg2
  116. # data_stores: ["state"]
  117. # args: {}
  118. multi_database_config = config.get("databases")
  119. database_config = config.get("database")
  120. database_path = config.get("database_path")
  121. if multi_database_config and database_config:
  122. raise ConfigError("Can't specify both 'database' and 'databases' in config")
  123. if multi_database_config:
  124. if database_path:
  125. raise ConfigError("Can't specify 'database_path' with 'databases'")
  126. self.databases = [
  127. DatabaseConnectionConfig(name, db_conf)
  128. for name, db_conf in multi_database_config.items()
  129. ]
  130. if database_config:
  131. self.databases = [DatabaseConnectionConfig("master", database_config)]
  132. if database_path:
  133. if self.databases and self.databases[0].name != "sqlite3":
  134. logger.warning(NON_SQLITE_DATABASE_PATH_WARNING)
  135. return
  136. database_config = {"name": "sqlite3", "args": {}}
  137. self.databases = [DatabaseConnectionConfig("master", database_config)]
  138. self.set_databasepath(database_path)
  139. def generate_config_section(self, data_dir_path, **kwargs):
  140. return DEFAULT_CONFIG % {
  141. "database_path": os.path.join(data_dir_path, "homeserver.db")
  142. }
  143. def read_arguments(self, args):
  144. """
  145. Cases for the cli input:
  146. - If no databases are configured and no database_path is set, raise.
  147. - No databases and only database_path available ==> sqlite3 db.
  148. - If there are multiple databases and a database_path raise an error.
  149. - If the database set in the config file is sqlite then
  150. overwrite with the command line argument.
  151. """
  152. if args.database_path is None:
  153. if not self.databases:
  154. raise ConfigError("No database config provided")
  155. return
  156. if len(self.databases) == 0:
  157. database_config = {"name": "sqlite3", "args": {}}
  158. self.databases = [DatabaseConnectionConfig("master", database_config)]
  159. self.set_databasepath(args.database_path)
  160. return
  161. if self.get_single_database().name == "sqlite3":
  162. self.set_databasepath(args.database_path)
  163. else:
  164. logger.warning(NON_SQLITE_DATABASE_PATH_WARNING)
  165. def set_databasepath(self, database_path):
  166. if database_path != ":memory:":
  167. database_path = self.abspath(database_path)
  168. self.databases[0].config["args"]["database"] = database_path
  169. @staticmethod
  170. def add_arguments(parser):
  171. db_group = parser.add_argument_group("database")
  172. db_group.add_argument(
  173. "-d",
  174. "--database-path",
  175. metavar="SQLITE_DATABASE_PATH",
  176. help="The path to a sqlite database to use.",
  177. )
  178. def get_single_database(self) -> DatabaseConnectionConfig:
  179. """Returns the database if there is only one, useful for e.g. tests"""
  180. if not self.databases:
  181. raise Exception("More than one database exists")
  182. return self.databases[0]