mixins.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. import inspect
  18. from flask import Markup
  19. from flask_babel import lazy_gettext as _
  20. from sqlalchemy import MetaData
  21. from superset import security_manager
  22. from superset.exceptions import SupersetException
  23. from superset.utils import core as utils
  24. from superset.views.database.filters import DatabaseFilter
  25. class DatabaseMixin:
  26. list_title = _("Databases")
  27. show_title = _("Show Database")
  28. add_title = _("Add Database")
  29. edit_title = _("Edit Database")
  30. list_columns = [
  31. "database_name",
  32. "backend",
  33. "allow_run_async",
  34. "allow_dml",
  35. "allow_csv_upload",
  36. "expose_in_sqllab",
  37. "creator",
  38. "modified",
  39. ]
  40. order_columns = [
  41. "database_name",
  42. "allow_run_async",
  43. "allow_dml",
  44. "modified",
  45. "allow_csv_upload",
  46. "expose_in_sqllab",
  47. ]
  48. add_columns = [
  49. "database_name",
  50. "sqlalchemy_uri",
  51. "cache_timeout",
  52. "expose_in_sqllab",
  53. "allow_run_async",
  54. "allow_csv_upload",
  55. "allow_ctas",
  56. "allow_dml",
  57. "force_ctas_schema",
  58. "impersonate_user",
  59. "allow_multi_schema_metadata_fetch",
  60. "extra",
  61. "encrypted_extra",
  62. ]
  63. search_exclude_columns = (
  64. "password",
  65. "tables",
  66. "created_by",
  67. "changed_by",
  68. "queries",
  69. "saved_queries",
  70. "encrypted_extra",
  71. )
  72. edit_columns = add_columns
  73. show_columns = [
  74. "tables",
  75. "cache_timeout",
  76. "extra",
  77. "database_name",
  78. "sqlalchemy_uri",
  79. "perm",
  80. "created_by",
  81. "created_on",
  82. "changed_by",
  83. "changed_on",
  84. ]
  85. base_order = ("changed_on", "desc")
  86. description_columns = {
  87. "sqlalchemy_uri": utils.markdown(
  88. "Refer to the "
  89. "[SqlAlchemy docs]"
  90. "(https://docs.sqlalchemy.org/en/rel_1_2/core/engines.html#"
  91. "database-urls) "
  92. "for more information on how to structure your URI.",
  93. True,
  94. ),
  95. "expose_in_sqllab": _("Expose this DB in SQL Lab"),
  96. "allow_run_async": _(
  97. "Operate the database in asynchronous mode, meaning "
  98. "that the queries are executed on remote workers as opposed "
  99. "to on the web server itself. "
  100. "This assumes that you have a Celery worker setup as well "
  101. "as a results backend. Refer to the installation docs "
  102. "for more information."
  103. ),
  104. "allow_ctas": _("Allow CREATE TABLE AS option in SQL Lab"),
  105. "allow_dml": _(
  106. "Allow users to run non-SELECT statements "
  107. "(UPDATE, DELETE, CREATE, ...) "
  108. "in SQL Lab"
  109. ),
  110. "force_ctas_schema": _(
  111. "When allowing CREATE TABLE AS option in SQL Lab, "
  112. "this option forces the table to be created in this schema"
  113. ),
  114. "extra": utils.markdown(
  115. "JSON string containing extra configuration elements.<br/>"
  116. "1. The ``engine_params`` object gets unpacked into the "
  117. "[sqlalchemy.create_engine]"
  118. "(https://docs.sqlalchemy.org/en/latest/core/engines.html#"
  119. "sqlalchemy.create_engine) call, while the ``metadata_params`` "
  120. "gets unpacked into the [sqlalchemy.MetaData]"
  121. "(https://docs.sqlalchemy.org/en/rel_1_0/core/metadata.html"
  122. "#sqlalchemy.schema.MetaData) call.<br/>"
  123. "2. The ``metadata_cache_timeout`` is a cache timeout setting "
  124. "in seconds for metadata fetch of this database. Specify it as "
  125. '**"metadata_cache_timeout": {"schema_cache_timeout": 600, '
  126. '"table_cache_timeout": 600}**. '
  127. "If unset, cache will not be enabled for the functionality. "
  128. "A timeout of 0 indicates that the cache never expires.<br/>"
  129. "3. The ``schemas_allowed_for_csv_upload`` is a comma separated list "
  130. "of schemas that CSVs are allowed to upload to. "
  131. 'Specify it as **"schemas_allowed_for_csv_upload": '
  132. '["public", "csv_upload"]**. '
  133. "If database flavor does not support schema or any schema is allowed "
  134. "to be accessed, just leave the list empty"
  135. "4. the ``version`` field is a string specifying the this db's version. "
  136. "This should be used with Presto DBs so that the syntax is correct",
  137. True,
  138. ),
  139. "encrypted_extra": utils.markdown(
  140. "JSON string containing additional connection configuration.<br/>"
  141. "This is used to provide connection information for systems like "
  142. "Hive, Presto, and BigQuery, which do not conform to the username:password "
  143. "syntax normally used by SQLAlchemy.",
  144. True,
  145. ),
  146. "impersonate_user": _(
  147. "If Presto, all the queries in SQL Lab are going to be executed as the "
  148. "currently logged on user who must have permission to run them.<br/>"
  149. "If Hive and hive.server2.enable.doAs is enabled, will run the queries as "
  150. "service account, but impersonate the currently logged on user "
  151. "via hive.server2.proxy.user property."
  152. ),
  153. "allow_multi_schema_metadata_fetch": _(
  154. "Allow SQL Lab to fetch a list of all tables and all views across "
  155. "all database schemas. For large data warehouse with thousands of "
  156. "tables, this can be expensive and put strain on the system."
  157. ),
  158. "cache_timeout": _(
  159. "Duration (in seconds) of the caching timeout for charts of this database. "
  160. "A timeout of 0 indicates that the cache never expires. "
  161. "Note this defaults to the global timeout if undefined."
  162. ),
  163. "allow_csv_upload": _(
  164. "If selected, please set the schemas allowed for csv upload in Extra."
  165. ),
  166. }
  167. base_filters = [["id", DatabaseFilter, lambda: []]]
  168. label_columns = {
  169. "expose_in_sqllab": _("Expose in SQL Lab"),
  170. "allow_ctas": _("Allow CREATE TABLE AS"),
  171. "allow_dml": _("Allow DML"),
  172. "force_ctas_schema": _("CTAS Schema"),
  173. "database_name": _("Database"),
  174. "creator": _("Creator"),
  175. "changed_on_": _("Last Changed"),
  176. "sqlalchemy_uri": _("SQLAlchemy URI"),
  177. "cache_timeout": _("Chart Cache Timeout"),
  178. "extra": _("Extra"),
  179. "encrypted_extra": _("Secure Extra"),
  180. "allow_run_async": _("Asynchronous Query Execution"),
  181. "impersonate_user": _("Impersonate the logged on user"),
  182. "allow_csv_upload": _("Allow Csv Upload"),
  183. "modified": _("Modified"),
  184. "allow_multi_schema_metadata_fetch": _("Allow Multi Schema Metadata Fetch"),
  185. "backend": _("Backend"),
  186. }
  187. def _pre_add_update(self, database):
  188. self.check_extra(database)
  189. self.check_encrypted_extra(database)
  190. database.set_sqlalchemy_uri(database.sqlalchemy_uri)
  191. security_manager.add_permission_view_menu("database_access", database.perm)
  192. # adding a new database we always want to force refresh schema list
  193. for schema in database.get_all_schema_names():
  194. security_manager.add_permission_view_menu(
  195. "schema_access", security_manager.get_schema_perm(database, schema)
  196. )
  197. def pre_add(self, database):
  198. self._pre_add_update(database)
  199. def pre_update(self, database):
  200. self._pre_add_update(database)
  201. def pre_delete(self, obj): # pylint: disable=no-self-use
  202. if obj.tables:
  203. raise SupersetException(
  204. Markup(
  205. "Cannot delete a database that has tables attached. "
  206. "Here's the list of associated tables: "
  207. + ", ".join("{}".format(o) for o in obj.tables)
  208. )
  209. )
  210. def check_extra(self, database): # pylint: disable=no-self-use
  211. # this will check whether json.loads(extra) can succeed
  212. try:
  213. extra = database.get_extra()
  214. except Exception as e:
  215. raise Exception("Extra field cannot be decoded by JSON. {}".format(str(e)))
  216. # this will check whether 'metadata_params' is configured correctly
  217. metadata_signature = inspect.signature(MetaData)
  218. for key in extra.get("metadata_params", {}):
  219. if key not in metadata_signature.parameters:
  220. raise Exception(
  221. "The metadata_params in Extra field "
  222. "is not configured correctly. The key "
  223. "{} is invalid.".format(key)
  224. )
  225. def check_encrypted_extra(self, database): # pylint: disable=no-self-use
  226. # this will check whether json.loads(secure_extra) can succeed
  227. try:
  228. database.get_encrypted_extra()
  229. except Exception as e:
  230. raise Exception(f"Secure Extra field cannot be decoded as JSON. {str(e)}")