sql.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # Copyright 2021-2025 Avaiga Private Limited
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  4. # the License. You may obtain a copy of the License at
  5. #
  6. # http://www.apache.org/licenses/LICENSE-2.0
  7. #
  8. # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  9. # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  10. # specific language governing permissions and limitations under the License.
  11. from datetime import datetime, timedelta
  12. from typing import Dict, List, Optional, Set
  13. from sqlalchemy import text
  14. from .._version._version_manager_factory import _VersionManagerFactory
  15. from ..common.scope import Scope
  16. from ..exceptions.exceptions import MissingAppendQueryBuilder, MissingRequiredProperty
  17. from ._abstract_sql import _AbstractSQLDataNode
  18. from .data_node_id import DataNodeId, Edit
  19. class SQLDataNode(_AbstractSQLDataNode):
  20. """Data Node stored in a SQL database.
  21. The *properties* attribute must contain the following mandatory entries:
  22. - *has_header* (`bool`): If True, indicates that the SQL query has a header.
  23. - *exposed_type* (`str`): The exposed type of the data read from SQL query. The default value is `pandas`.
  24. - *db_name* (`str`): The database name, or the name of the SQLite database file.
  25. - *db_engine* (`str`): The database engine. Possible values are *sqlite*, *mssql*,
  26. *mysql*, or *postgresql*.
  27. - *read_query* (`str`): The SQL query string used to read the data from the database.
  28. - *write_query_builder* `(Callable)`: A callback function that takes the data as an input
  29. parameter and returns a list of SQL queries to be executed when writing data to the data
  30. node.
  31. - *append_query_builder* (`Callable`): A callback function that takes the data as an input
  32. parameter and returns a list of SQL queries to be executed when appending data to the
  33. data node.
  34. - *db_username* (`str`): The database username.
  35. - *db_password* (`str`): The database password.
  36. - *db_host* (`str`): The database host. The default value is *localhost*.
  37. - *db_port* (`int`): The database port. The default value is 1433.
  38. - *db_driver* (`str`): The database driver.
  39. The *properties* attribute can also contain the following optional entries:
  40. - *sqlite_folder_path* (str): The path to the folder that contains SQLite file. The default value
  41. is the current working folder.
  42. - *sqlite_file_extension* (str): The filename extension of the SQLite file. The default value is ".db".
  43. - *db_extra_args* (`Dict[str, Any]`): A dictionary of additional arguments to be passed into database
  44. connection string.
  45. """
  46. __STORAGE_TYPE = "sql"
  47. __READ_QUERY_KEY = "read_query"
  48. _WRITE_QUERY_BUILDER_KEY = "write_query_builder"
  49. _APPEND_QUERY_BUILDER_KEY = "append_query_builder"
  50. def __init__(
  51. self,
  52. config_id: str,
  53. scope: Scope,
  54. id: Optional[DataNodeId] = None,
  55. owner_id: Optional[str] = None,
  56. parent_ids: Optional[Set[str]] = None,
  57. last_edit_date: Optional[datetime] = None,
  58. edits: Optional[List[Edit]] = None,
  59. version: Optional[str] = None,
  60. validity_period: Optional[timedelta] = None,
  61. edit_in_progress: bool = False,
  62. editor_id: Optional[str] = None,
  63. editor_expiration_date: Optional[datetime] = None,
  64. properties: Optional[Dict] = None,
  65. ) -> None:
  66. if properties is None:
  67. properties = {}
  68. if properties.get(self.__READ_QUERY_KEY) is None:
  69. raise MissingRequiredProperty(f"Property {self.__READ_QUERY_KEY} is not informed and is required.")
  70. if properties.get(self._WRITE_QUERY_BUILDER_KEY) is None:
  71. raise MissingRequiredProperty(f"Property {self._WRITE_QUERY_BUILDER_KEY} is not informed and is required.")
  72. super().__init__(
  73. config_id,
  74. scope,
  75. id,
  76. owner_id,
  77. parent_ids,
  78. last_edit_date,
  79. edits,
  80. version or _VersionManagerFactory._build_manager()._get_latest_version(),
  81. validity_period,
  82. edit_in_progress,
  83. editor_id,
  84. editor_expiration_date,
  85. properties=properties,
  86. )
  87. self._TAIPY_PROPERTIES.update(
  88. {
  89. self.__READ_QUERY_KEY,
  90. self._WRITE_QUERY_BUILDER_KEY,
  91. self._APPEND_QUERY_BUILDER_KEY,
  92. }
  93. )
  94. @classmethod
  95. def storage_type(cls) -> str:
  96. """Return the storage type of the data node: "sql"."""
  97. return cls.__STORAGE_TYPE
  98. def _get_base_read_query(self) -> str:
  99. return self.properties.get(self.__READ_QUERY_KEY)
  100. def _do_append(self, data, engine, connection) -> None:
  101. append_query_builder_fct = self.properties.get(self._APPEND_QUERY_BUILDER_KEY)
  102. if not append_query_builder_fct:
  103. raise MissingAppendQueryBuilder
  104. queries = append_query_builder_fct(data)
  105. self.__execute_queries(queries, connection)
  106. def _do_write(self, data, engine, connection) -> None:
  107. queries = self.properties.get(self._WRITE_QUERY_BUILDER_KEY)(data)
  108. self.__execute_queries(queries, connection)
  109. def __execute_queries(self, queries, connection) -> None:
  110. if not isinstance(queries, List):
  111. queries = [queries]
  112. for query in queries:
  113. if isinstance(query, str):
  114. connection.execute(text(query))
  115. else:
  116. statement = query[0]
  117. parameters = query[1]
  118. connection.execute(text(statement), parameters)